Linux SPI驱动框架分为主机控制器驱动和设备驱动,主机驱动是SOC上的SPI控制器接口,不管是什么SPI设备,控制器部分的驱动都是一样的,因此大多数情况下我们只需要实现自己对应的SPI设备驱动。
1.SPI主机驱动
SPI主机驱动就是SOC的SPI控制器驱动,Linux使用spi_master表示主机驱动,spi_master是定义在include/linux/spi/spi.h中的结构体:其中重要的属性有
/*控制器数据传输函数*/
int (*transfer)(struct spi_device *spi, struct spi_message *mesg);
/*用于发送一个spi_message数据,通过队列方式发送出去,SPI的数据会打包成spi_message*/
int (*transfer_one_message)(struct spi_master *master, struct spi_message *mesg);
SPI最终会通过transfer函数与SPI设备进行通信。SPI主机驱动的核心就是申请spi_master,然后初始化spi_master,最后向Linux内核注册spi_master
(1)spi_master申请与释放
SPI申请
struct spi_master *spi_alloc_master(struct device *dev, unsigned size)
SPI释放
void spi_master_put(struct spi_master *master)
(2)spi_master的注册属于注销
spi_master初始化完成之后需要注册到linux内核中,注册函数为
int spi_register_master(struct spi_master *master)
注销spi_master
void spi_unregister_master(struct spi_master *master)
spi_bitbang_start 注册 spi_master 的话就要使用 spi_bitbang_stop 来注销掉spi_master
2.SPI设备驱动
Linux内核使用spi_driver结构体来表示spi设备驱动,该结构体定义在include/linux/spi/spi.h中,内容如下:
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_driver结构体初始化完成之后,调用spi_register_driver向内核注册,注销使用spi_unregister_driver,函数原型为:
int spi_register_driver(struct spi_driver *sdrv)
void spi_unregister_driver(struct spi_driver *sdrv)
3.SPI设备和驱动匹配过程
SPI设备和驱动的匹配过程是由SPI总线来完成的,SPI总线为spi_bus_type,定义在drivers/spi/spi.c中:
struct bus_type spi_bus_type = {
.name = "spi",
.dev_groups = spi_dev_groups,
.match = spi_match_device,
.uevent = spi_uevent,
};
SPI设备和驱动的匹配函数为spi_match_device,函数原型为:
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);
/* 用于完成设备树设备与驱动的匹配,比较SPI设备节点的compatible属性和of_device_id中的compatible属性是否相等*/
if (of_driver_match_device(dev, drv))
return 1;
/* 用于ACPI形式的匹配*/
if (acpi_driver_match_device(dev, drv))
return 1;
/*用于传统的,无设备树的SPI设备和驱动匹配过程,比较SPI设备名字和spi_deviice_id中的name字段是否相等*/
if (sdrv->id_table)
return !!spi_match_id(sdrv->id_table, spi);
/*比较 spi_device 中 modalias 成员变量和 device_driver 中的 name 成员变量是否相等*/
return strcmp(spi->modalias, drv->name) == 0;
}
4.SPI设备驱动编写流程
SPI设备驱动最主要的工作就是初始化spi_driver结构体并向内核中注册,spi_driver中最主要的是要实现probe函数,并根据设备树中的配置信息进行匹配。
/* 在设备树中进行匹配的compatible属性 */
static struct of_device_id icm20608_of_match[] = {
{.compatible = "wang,icm20608",},
{},
};
/* spi_driver的实现*/
static struct spi_driver icm20608_driver = {
.probe = icm20608_probe,
.remove = icm20608_remove,
.driver = {
.name = "wang,icm20608",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(icm20608_of_match),
},
};
probe函数主要的功能是进行初始化操作和字符设备的申请,为了更好的操作spi设备,将其注册为字符设备,后续在读写操作是只需对字符设备进行读写操作。
当成功注册spi_driver到Linux内核之后,就可以使用SPI核心层提供的API接口进行读写操作了,spi_transfer结构体用于描述SPI传输信息,结构体内容如下:
struct spi_transfer {
/* it's ok if tx_buf == rx_buf (right?)
* for MicroWire, one buffer must be null
* buffers must work with dma_*map_single() calls, unless
* spi_message.is_dma_mapped reports a pre-existing mapping
*/
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_transfer需要组织成spi_message,spi_message也是一个结构体,原型如下:
struct spi_message {
struct list_head transfers;
struct spi_device *spi;
unsigned is_dma_mapped:1;
/* completion is reported through a callback */
void (*complete)(void *context);
void *context;
unsigned frame_length;
unsigned actual_length;
int status;
/* for optional use by whatever driver currently owns the
* spi_message ... between calls to spi_async and then later
* complete(), that's the spi_master controller driver.
*/
struct list_head queue;
void *state;
};
spi_message在使用之前要进行一些操作,具体如下:
/* 首先要进行初始化操作 */
void spi_message_init(struct spi_message *m)
/* 初始化完成之后要将spi_transfer添加到spi_message队列中 */
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m);
/*数据传输分为同步传输和异步传输
同步传输:传输会阻塞直到SPI数据传输完成 spi_sync
异步传输:不会阻塞 spi_async*/
int spi_sync(struct spi_device *spi, struct spi_message *message)
int spi_async(struct spi_device *spi, struct spi_message *message)
SPI数据传输步骤可以总结为以下几点:
(1)申请并初始化spi_transfer,设置spi_transfer中的tx_buf和rx_buf变量,最后设置len成员变量
(2)使用spi_message_init初始化spi_message
(3)使用spi_message_add_tail函数将前面设置好的spi_transfer添加到spi_message队列中
(4)设置数据传输的方式
示例代码如下:
/* 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;
}