一:SPI驱动框架简介
SPI驱动框架同I2C类似,分为主机控制器驱动和设备驱动。
1、SPI主机驱动
SPI主机驱动就是SOC的SPI控制器驱动,Linux内核使用spi_master表示SPI主机驱动:
struct spi_master {struct device dev;struct list_head list;s16 bus_num;u16 num_chipselect;u16 dma_alignment;/* spi_device.mode flags understood by this controller driver */u16 mode_bits;/* bitmask of supported bits_per_word for transfers */u32 bits_per_word_mask;
... .../* limits on transfer speed */u32 min_speed_hz;u32 max_speed_hz;
... .../* other constraints relevant to this driver */u16 flags;/* lock and mutex for SPI bus locking */spinlock_t bus_lock_spinlock;struct mutex bus_lock_mutex;bool bus_lock_flag;int (*setup)(struct spi_device *spi);int (*transfer)(struct spi_device *spi,struct spi_message *mesg);... ...int (*transfer_one_message)(struct spi_master *master,struct spi_message *mesg);
... ...
};
其中transfer函数和i2c中的master_xfer函数一样是控制器数据传输函数。transfer_one_message函数也用于SPI数据发送,用于发送一个spi_message,SPI的数据会打包成spi_message,然后以队列的方式发送出去。
SPI主机驱动的核心就是申请spi_master,然后初始化spi_master,最后想内核注册spi_master。
(1)spi_master申请与释放
struct spi_master *spi_alloc_master(struct device *dev, unsigned size)static inline void spi_master_put(struct spi_master *master)
(2)spi_master注册与注销
int spi_register_master(struct spi_master *master)void spi_unregister_master(struct spi_master *master)
2、SPI设备驱动
Linux内核使用spi_driver结构体来表示spi设备驱动,这个需要我们来实现:
struct spi_driver {const struct spi_device_id *id_table;int (*probe)(struct spi_device *spi);int (*remove)(struct spi_device *spi);void (*shutdown)(struct spi_device *spi);struct device_driver driver;
};
可以看出当spi设备和驱动匹配成功后probe函数就会被执行。
spi_driver初始化完成后还需要向Linux内核注册:
int spi_register_driver(struct spi_driver *sdrv)
注销函数:
void spi_unregister_driver(struct spi_driver *sdrv)
spi_driver注册示例:
/* probe 函数 */
static int xxx_probe(struct spi_device *spi)
{/* 具体函数内容 */return 0;
}/* remove 函数 */
static int xxx_remove(struct spi_device *spi)
{/* 具体函数内容 */return 0;
}
/* 传统匹配方式 ID 列表 */
static const struct spi_device_id xxx_id[] = {{"xxx", 0},{}
};/* 设备树匹配列表 */
static const struct of_device_id xxx_of_match[] = {{ .compatible = "xxx" },{ /* Sentinel */ }
};/* SPI 驱动结构体 */
static struct spi_driver xxx_driver = {.probe = xxx_probe,.remove = xxx_remove,.driver = {.owner = THIS_MODULE,.name = "xxx",.of_match_table = xxx_of_match,},.id_table = xxx_id,
};/* 驱动入口函数 */
static int __init xxx_init(void)
{return spi_register_driver(&xxx_driver);
}/* 驱动出口函数 */
static void __exit xxx_exit(void)
{spi_unregister_driver(&xxx_driver);
}module_init(xxx_init);
module_exit(xxx_exit);
3、SPI设备和驱动匹配过程
spi设备和驱动匹配过程是由spi总线来完成的:
struct bus_type spi_bus_type = {.name = "spi",.dev_groups = spi_dev_groups,.match = spi_match_device,.uevent = spi_uevent,
};
spi的匹配函数:
static int spi_match_device(struct device *dev, struct device_driver *drv)
{const struct spi_device *spi = to_spi_device(dev);const struct spi_driver *sdrv = to_spi_driver(drv);/* Attempt an OF style match */if (of_driver_match_device(dev, drv))return 1;/* Then try ACPI */if (acpi_driver_match_device(dev, drv))return 1;if (sdrv->id_table)return !!spi_match_id(sdrv->id_table, spi);return strcmp(spi->modalias, drv->name) == 0;
}
二:SPI主机驱动分析
根据硬件信息,设备连接在imx6ull系列开发板的spi3接口上,查询共用设备树文件中的spi3节点:
ecspi3: ecspi@02010000 {#address-cells = <1>;#size-cells = <0>;compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";reg = <0x02010000 0x4000>;interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;clocks = <&clks IMX6UL_CLK_ECSPI3>,<&clks IMX6UL_CLK_ECSPI3>;clock-names = "ipg", "per";dmas = <&sdma 7 7 1>, <&sdma 8 7 2>;dma-names = "rx", "tx";status = "disabled";
};
根据compatible属性即可找到Linux内核中SPI3主机控制器驱动:
static struct platform_device_id spi_imx_devtype[] = {... ...{.name = "imx51-ecspi",.driver_data = (kernel_ulong_t) &imx51_ecspi_devtype_data,}, {.name = "imx6ul-ecspi",.driver_data = (kernel_ulong_t) &imx6ul_ecspi_devtype_data,}, {/* sentinel */}
};static const struct of_device_id spi_imx_dt_ids[] = {... ...{ .compatible = "fsl,imx51-ecspi", .data = &imx51_ecspi_devtype_data, },{ .compatible = "fsl,imx6ul-ecspi", .data = &imx6ul_ecspi_devtype_data, },{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, spi_imx_dt_ids);static struct platform_driver spi_imx_driver = {.driver = {.name = DRIVER_NAME,.of_match_table = spi_imx_dt_ids,.pm = IMX_SPI_PM,},.id_table = spi_imx_devtype,.probe = spi_imx_probe,.remove = spi_imx_remove,
};
module_platform_driver(spi_imx_driver);
其中spi_imx_devtype数据为SPI无设备树匹配表,spi_imx_dt_ids为SPI设备树匹配表,上一章中的I2C同理。
spi_imx_probe函数会从设备树中读取相应的节点属性值,申请并初始化spi_master,最后调用spi_bitbang_start(注册函数为spi_register_master)函数向Linux内核注册spi_master:
-->>static int spi_imx_probe(struct platform_device *pdev)-->>spi_bitbang_start(&spi_imx->bitbang);-->>spi_register_master(spi_master_get(master));
spi_imx_probe函数中有两个设置很重要,一个是SPI主机的最终数据收发函数spi_imx_transfer,一个是设置收发函数指向的函数spi_imx_setupxfer:
spi_imx_probe:spi_imx->bitbang.setup_transfer = spi_imx_setupxfer;
spi_imx->bitbang.txrx_bufs = spi_imx_transfer;-->>spi_imx_transfer-->>spi_imx_pio_transfer-->>spi_imx_push-->>spi_imx->tx(spi_imx);-->>spi_imx_setupxfer-->>/* Initialize the functions for transfer */if (config.bpw <= 8) {spi_imx->rx = spi_imx_buf_rx_u8;spi_imx->tx = spi_imx_buf_tx_u8;spi_imx->tx_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;spi_imx->rx_config.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;} else if (config.bpw <= 16) {spi_imx->rx = spi_imx_buf_rx_u16;spi_imx->tx = spi_imx_buf_tx_u16;spi_imx->tx_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;spi_imx->rx_config.src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;} else {spi_imx->rx = spi_imx_buf_rx_u32;spi_imx->tx = spi_imx_buf_tx_u32;spi_imx->tx_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;spi_imx->rx_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;}#define MXC_SPI_BUF_TX(type) \
static void spi_imx_buf_tx_##type(struct spi_imx_data *spi_imx) \
{ \type val = 0; \\if (spi_imx->tx_buf) { \val = *(type *)spi_imx->tx_buf; \spi_imx->tx_buf += sizeof(type); \} \\spi_imx->count -= sizeof(type); \\writel(val, spi_imx->base + MXC_CSPITXDATA); \
}MXC_SPI_BUF_RX(u8)
MXC_SPI_BUF_TX(u8)
MXC_SPI_BUF_RX(u16)
MXC_SPI_BUF_TX(u16)
MXC_SPI_BUF_RX(u32)
MXC_SPI_BUF_TX(u32)
三:SPI设备驱动分析
1、初始化驱动数据
前面已经讲过注册spi_driver,现在就可以对设备进行读写操作,其中有两个结构体需要熟知:
spi_transfer :tx_buf保存着要发送的数据,rx_buf保存接收到的数据,len是要进行传输的数据长度。
struct spi_transfer {const void *tx_buf;void *rx_buf;unsigned len;dma_addr_t tx_dma;dma_addr_t rx_dma;struct sg_table tx_sg;struct sg_table rx_sg;unsigned cs_change:1;unsigned tx_nbits:3;unsigned rx_nbits:3;
#define SPI_NBITS_SINGLE 0x01 /* 1bit transfer */
#define SPI_NBITS_DUAL 0x02 /* 2bits transfer */
#define SPI_NBITS_QUAD 0x04 /* 4bits transfer */u8 bits_per_word;u16 delay_usecs;u32 speed_hz;struct list_head transfer_list;
};
spi_message:在使用spi_message之前需要对其进行初始化,初始化函数为spi_message_init
struct spi_message {struct list_head transfers;struct spi_device *spi;unsigned is_dma_mapped:1;void (*complete)(void *context);void *context;unsigned frame_length;unsigned actual_length;int status;struct list_head queue;void *state;
};void spi_message_init(struct spi_message *m)
初始化完成之后需要将spi_transfer添加到spi_message队列中:spi_message_add_tail
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
2、数据收发
当spi数据准备好之后就可以进行数据传输了,数据传输分为同步传输和异步传输。
同步传输:会阻塞等待SPI数据传输完成
int spi_sync(struct spi_device *spi, struct spi_message *message)
异步传输:不会阻塞,需要设置spi_message中的complete成员变量,它是一个回调函数,当SPI异步传输完成以后此函数就会被调用。
int spi_async(struct spi_device *spi, struct spi_message *message)
3、读写操作示例
/* SPI 多字节发送 */
static int spi_send(struct spi_device *spi, u8 *buf, int len)
{int ret;struct spi_message m;struct spi_transfer t = {.tx_buf = buf,.len = len,};spi_message_init(&m); /* 初始化 spi_message */spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message 队列 */ret = spi_sync(spi, &m); /* 同步传输 */return ret;
}/* SPI 多字节接收 */
static int spi_receive(struct spi_device *spi, u8 *buf, int len)
{int ret;struct spi_message m;struct spi_transfer t = {.rx_buf = buf,.len = len,};spi_message_init(&m); /* 初始化 spi_message */spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message 队列 */ret = spi_sync(spi, &m); /* 同步传输 */return ret;
}
四:示例
1、修改设备树
(1)添加ICM20608设备的IO引脚
pinctrl_ecspi3: icm20608 {fsl,pins = <MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x10b0MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x10b1MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x10b1MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x10b1>;
};
(2)在ecspi3节点中增加icm20608子节点
&ecspi3 {fsl,spi-num-chipselects = <1>; /* 设置当前片选数量为1 */cs-gpio = <&gpio1 20 GPIO_ACTIVE_LOW>; /* 选用自定义“cs-gpio”属性,而非系统片选属性*/pinctrl-names = "default"; pinctrl-0 = <&pinctrl_ecspi3>; /* 设置IO要使用的pinctrl子节点 */status = "okay";spidev: icm20608@0 { /* 设备连接在ecspi3的第0个通道上 */compatible = "alientek, icm20608";spi-max-frequency = <8000000>; /* SPI最大时钟频率为8MHz */reg = <0>;};
};
2、驱动
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "icm20608reg.h"#define ICM20608_CNT 1
#define ICM20608_NAME "icm20608"struct icm20608_dev {dev_t devid;struct cdev cdev;struct class *class;struct device *device;struct device_node *nd;int major;void *private_data;int cs_gpio;signed int gyro_x_adc;signed int gyro_y_adc;signed int gyro_z_adc;signed int accel_x_adc;signed int accel_y_adc;signed int accel_z_adc;signed int temp_adc;
};static struct icm20608_dev icm20608dev;static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{int ret;unsigned char txdata[len];struct spi_message m;struct spi_transfer *t;struct spi_device *spi = (struct spi_device *)dev->private_data;gpio_set_value(dev->cs_gpio, 0); /* 片选拉低,选中ICM20608 */t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); /* 申请内存 *//* 第1次,发送要读取的寄存地址 */txdata[0] = reg | 0x80; /* 写数据的时候寄存器地址bit8要置1 */t->tx_buf = txdata; /* 要发送的数据 */t->len = 1; /* 1个字节 */spi_message_init(&m); /* 初始化spi_message */spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */ret = spi_sync(spi, &m); /* 同步发送 *//* 第2次,读取数据 */txdata[0] = 0xff; /* 随便一个值,此处无意义 */t->rx_buf = buf; /* 读取到的数据 */t->len = len; /* 要读取的数据长度 */spi_message_init(&m); /* 初始化spi_message */spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */ret = spi_sync(spi, &m); /* 同步发送 */kfree(t); /* 释放内存 */gpio_set_value(dev->cs_gpio, 1); /* 片选拉高,释放ICM20608 */return ret;
}static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len)
{uint8_t char txdata[len];struct spi_message m;struct spi_transfer *t;struct spi_device *spi = (struct spi_device *)dev->private_data;t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);gpio_set_value(dev->cs_gpio, 0);/* 第1次,发送要读取的寄存地址 */txdata[0] = reg & ~0x80;t->tx_buf = txdata;t->led = 1;spi_message_init(&m);spi_message_add_tail(t, &m);ret = spi_sync(spi, &m);/* 第2次,发送要写入的数据 */t->tx_buf = buf; /* 要写入的数据 */t->len = len; /* 写入的字节数 */spi_message_init(&m); /* 初始化spi_message */spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */ret = spi_sync(spi, &m); /* 同步发送 */kfree(t); /* 释放内存 */gpio_set_value(dev->cs_gpio, 1);/* 片选拉高,释放ICM20608 */return ret;
}static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{u8 data = 0;icm20608_read_regs(dev, reg, &data, 1);return data;
}static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
{u8 buf = value;icm20608_write_regs(dev, reg, &buf, 1);
}void icm20608_readdata(struct icm20608_dev *dev)
{uint8_t data[14];icm20608_read_onereg(dev, ICM20_ACCEL_XOUT_H, data, 14);dev->accel_x_adc = (signed short)((data[0] << 8) | data[1]); dev->accel_y_adc = (signed short)((data[2] << 8) | data[3]); dev->accel_z_adc = (signed short)((data[4] << 8) | data[5]); dev->temp_adc = (signed short)((data[6] << 8) | data[7]); dev->gyro_x_adc = (signed short)((data[8] << 8) | data[9]); dev->gyro_y_adc = (signed short)((data[10] << 8) | data[11]);dev->gyro_z_adc = (signed short)((data[12] << 8) | data[13]);
}static int icm20608_open(struct inode *inode, struct file *filp)
{filp->private_data = &icm20608dev;return 0;
}static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{int16_t data[7];long err = 0;struct icm20608_dev *dev = (struct icm20608_dev *)filp->private_data;icm20608_readdata(dev);data[0] = dev->gyro_x_adc;data[1] = dev->gyro_y_adc;data[2] = dev->gyro_z_adc;data[3] = dev->accel_x_adc;data[4] = dev->accel_y_adc;data[5] = dev->accel_z_adc;data[6] = dev->temp_adc;err = copy_to_user(buf, data, sizeof(data));return 0;
}static int icm20608_release(struct inode *inode, struct file *filp)
{return 0;
}static struct file_operations icm20608_fops = {.owner = THIS_MODULE,.open = icm20608_open,.read = icm20608_read,.release = icm20608_release,
};void icm20608_reginit(void)
{uint8_t value = 0;icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x80);mdelay(50);icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x01);mdelay(50);value = icm20608_read_onereg(&icm20608dev, ICM20_WHO_AM_I);printk("ICM20608 ID = %#X\r\n", value); icm20608_write_onereg(&icm20608dev, ICM20_SMPLRT_DIV, 0x00); /* 输出速率是内部采样率 */icm20608_write_onereg(&icm20608dev, ICM20_GYRO_CONFIG, 0x18); /* 陀螺仪±2000dps量程 */icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG, 0x18); /* 加速度计±16G量程 */icm20608_write_onereg(&icm20608dev, ICM20_CONFIG, 0x04); /* 陀螺仪低通滤波BW=20Hz */icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG2, 0x04); /* 加速度计低通滤波BW=21.2Hz */icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_2, 0x00); /* 打开加速度计和陀螺仪所有轴 */icm20608_write_onereg(&icm20608dev, ICM20_LP_MODE_CFG, 0x00); /* 关闭低功耗 */icm20608_write_onereg(&icm20608dev, ICM20_FIFO_EN, 0x00); /* 关闭FIFO */
}static int icm20608_probe(struct spi_device *spi)
{int ret = 0;if(icm20608dev.major){icm20608dev.devid = MKDEV(icm20608dev.major, 0);register_chrdev_region(icm20608dev.devid, ICM20608_CNT, ICM20608_NAME);}else {alloc_chrdev_region(&icm20608dev.devid, 0, ICM20608_CNT, ICM20608_NAME);icm20608dev.major = MAJOR(icm20608dev.devid);}cdev_init(&icm20608dev.cdev, &icm20608_fops);cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME);icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.device, NULL, ICM20608_NAME);icm20608dev.nd = of_find_node_by_path("/soc/aips-bus@02000000/spba-bus@02000000/ecspi@02010000");icm20608dev.cs_gpio = of_get_named_gpio(icm20608dev.nd, "cs-gpio", 0);ret = gpio_direction_output(icm20608dev.cs_gpio, 1);if(ret < 0){printk("can't set gpio!\r\n");}spi->mode = SPI_MODE_0;spi_setup(spi);icm20608dev.private_data = spi;icm20608_reginit();return 0;
}static int icm20608_remove(struct spi_device *spi)
{device_destroy(icm20608dev.class, icm20608dev.devid);class_destroy(icm20608dev.class);cdev_del(&icm20608dev.cdev);unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);return 0;
}static const struct spi_device_id icm20608_id[] = {{ "alientek, icm20608", 0 },{}
};static const struct of_device_id icm20608_of_match[] = {{ .compatible = "alientek, icm20608" },{}
};static struct spi_driver icm20608_driver = {.probe = icm20608_probe,.remove = icm20608_remove,.driver = {.owner = THIS_MODULE,.name = "icm20608",.of_match_table = icm20608_of_match,},.id_table = icm20608_id,
};static int __init icm20608_init(void)
{return spi_register_driver(&icm20608_driver);
}static void __exit icm20608_exit(void)
{spi_unregister_driver(&icm20608_driver);
}module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");