SPI设备驱动的整体结构
设备驱动的抽象
使用 spi_driver 结构体来表示一个SPI设备驱动。
SPI从设备的抽象
使用 spi_device 结构体来表示SPI总线上匹配到的从设备,通常它被包含在设备的私有结构体中;在设备驱动中操作SPI设备时,需要先获得此结构体的实例。
SPI数据传输的抽象
使用 spi_message 结构体来描述一次完整的SPI传输,它通常包含一个或多个 spi_transfer 结构体。
通过 spi_message_init() 来初始化一个spi_messgae;通过 spi_message_add_tail() 将 spi_transfer 添加到 spi_message 的队列中。
/* liunux/spi/spi.h */
static void spi_message_init(struct spi_message *m);
static void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m);
驱动代码分析
init, exit
初始化 spi_driver 结构体
static struct spi_driver xxx_driver = {
.driver = {
.name = "xxx",
.owner = THIS_MODULE,
.pm = &xxx_pm,
},
.probe = xxx_probe,
.remove = __devexit_p(xxx_remove),
};
驱动模块的 init 和 exit 函数
/* linux/spi/spi.h */
int spi_register_driver(struct spi_driver *sdrv);
void spi_unregister_driver(struct spi_driver *sdrv);
static int __init xxx_init(void)
{
return spi_register_driver(&xxx_driver);
}
static void __exit xxx_exit(void)
{
spi_unregister_driver(&xxx_driver);
}
moudle_init(xxx_init);
moudle_exit(xxx_exit);
也可以直接使用 module_spi_driver() 宏来注册 spi 驱动
module_spi_driver(xxx_driver);
probe, remove
一个spi外设在设备树中提供其片选序号、数据比特率、SPI传输模式(CPOL、CPHA)等信息,如:
&mcspi1{
pinctrl-names = "default";
pinctrl-0 = <&mcspi1_pins>;
ads7846@0 {
pinctrl-names = "default";
pinctrl-0 = <&ads7846_pins>;
compatible = "ti, ads7846";
vcc-supply = <&ads7846_reg>;
reg = 0; /* CS0 */
spi-max-frequency = <1500000>;
interrupt-parent = <&gpio4>;
interrupts = <180>;
pendown-gpio = <&gpio4 180>;
...
}
}
SPI 总线将设备与驱动匹配之后,spi_device 结构体被实例化,并且将设备对应的主机控制器填充到其 master 成员, 片选序号填充到 chip_select 成员等。
然后,这个 spi_device 实例被传入其匹配的驱动的 probe 函数,以通过解析设备树完成进一步的设置。
SPI 设备驱动的probe函数和remove函数的原型如下:
static int __devinit xxx_probe(struct spi_device *spi);
static int __devexit xxx_remove(struct spi_device *spi);
probe 函数的主要工作
(1) 根据spi_device结构体的信息,设置spi设备的传输模式、时钟频率等;初始化 spi_message
(2) 分配设备的私有数据结构体(包含spi_device),并将这个私有数据结构体放到 spi_device 的 driver_data 中
(3) 设置和初始化私有数据结构体的其它成员:通常包括向其它子系统注册的成员(即SPI外设本身所属的设备类型),如 input_dev, hwmon 等;以及用于控制并发和同步的结构体,如 mutex, wait_queue 等
(4) 申请相关的硬件资源,如中断、GPIO等
remove 函数的主要工作
(1) 私有数据结构体中的成员的注销及释放
(2) 硬件退出的相关操作,释放硬件资源
(3) 释放私有数据结构体
相关 API
/* drivers/spi/spi.c */
/* 设置SPI模式和时钟频率 */
int spi_setup(struct spi_device *spi);
/* drivers/base/dd.c */
/* dev_set_drvdata(&spi->dev, xxx_data); */
int dev_set_drvdata(struct device *dev, void *data);
/* struct xxx_data *xxx_data = dev_get_drvdata(&spi->dev); */
void *dev_get_drvdata(const struct device *dev);
SPI 数据传输
spi_message 的传输有两种方式:同步和异步,可以调用 spi_sync() 和 spi_async() API来实现
/* drivers/spi/spi.c */
int spi_sync(struct spi_device *spi, struct spi_message *message);
int spi_async(struct spi_device *spi, struct spi_message *message);
spi_message 通常在设备驱动的 porbe 函数中被初始化好,之后在中断服务函数或者其它需要进行SPI传输的函数中调用以上数据传输函数。
也可以使用SPI核心层提供的便捷API来完成数据传输,它们只是将 spi_transfer 和 spi_message 的初始化以及SPI数据传输封装在一起。如以下的 spi_wrie(), spi_read() 和 spi_write_then_read() 函数:
/* linux/spi/spi.h */
/* 写 - 同步传输 */
static inline int
spi_write(struct spi_device *spi, const void *buf, size_t len)
{
struct spi_transfer t = {
.tx_buf = buf, //指定传出的数据的缓冲区
.len = len,
};
struct spi_message m;
spi_message_init(&m);
spi_message_add_tail(&t, &m);
return spi_sync(spi, &m);
}
/* 读 - 同步传输 */
static inline int
spi_read(struct spi_device *spi, void *buf, size_t len)
{
struct spi_transfer t = {
.rx_buf = buf, //指定接收的数据的缓冲区
.len = len,
};
struct spi_message m;
spi_message_init(&m);
spi_message_add_tail(&t, &m);
return spi_sync(spi, &m);
}
/* drivers/spi/spi.c */
/* 先写后读 - 同步传输 */
int spi_write_then_read(struct spi_device *spi,
const void *txbuf, unsigned n_tx,
void *rxbuf, unsigned n_rx)
{
static DEFINE_MUTEX(lock);
int status;
struct spi_message message;
struct spi_transfer x[2];
u8 *local_buf;
/* Use preallocated DMA-safe buffer. We can't avoid copying here,
* (as a pure convenience thing), but we can keep heap costs
* out of the hot path ...
*/
if ((n_tx + n_rx) > SPI_BUFSIZ)
return -EINVAL;
spi_message_init(&message);
memset(x, 0, sizeof x);
if (n_tx) {
x[0].len = n_tx;
spi_message_add_tail(&x[0], &message);
}
if (n_rx) {
x[1].len = n_rx;
spi_message_add_tail(&x[1], &message);
}
/* ... unless someone else is using the pre-allocated buffer */
if (!mutex_trylock(&lock)) {
local_buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);
if (!local_buf)
return -ENOMEM;
} else
local_buf = buf;
memcpy(local_buf, txbuf, n_tx);
x[0].tx_buf = local_buf;
x[1].rx_buf = local_buf + n_tx;
/* do the i/o */
status = spi_sync(spi, &message);
if (status == 0)
memcpy(rxbuf, x[1].rx_buf, n_rx);
if (x[0].tx_buf == buf)
mutex_unlock(&lock);
else
kfree(local_buf);
return status;
}
EXPORT_SYMBOL_GPL(spi_write_then_read);
参考源码
linux/spi/spi.h
drivers/spi/spi.c
drivers/input/touchscreen/ads7846.c