Linux设备驱动模型之SPI
SPI:Serial Peripheral Interface,串行外设接口,主要用于控制器与外部传感器进行数据通信的接口,它是一种同步、全双工、主从式接口。
SPI接口介绍
接口定义
SPI接口有4根信号线,分别是片选信号、时钟信号、串行输出数据线、串行输入数据线。
- SS:从设备使能信号,由SPI主设备控制;
- SCLK:时钟信号,由主设备产生;
- MOSI:主设备数据输出,从设备数据输入;
- MISO:主设备数据输入,从设备数据输出;
产生时钟信号的设备为主设备,主设备和从设备之间传输的数据与主机产生的时钟同步。SPI接口只能有一个主设备,可有多个从设备。
时钟极性与相位
在SPI中,主机可以选择时钟极性和时钟相位。在空闲状态期间,CPOL位设置时钟信号的极性。空闲状态是指传输开始时CS为高电平且在向低电平转变的期间,以及传输结束时CS为低电平且在向高电平转变的期间。CPHA位选择时钟相位。
根据CPHA位的状态,使用时钟上升沿或下降沿来采样和/或移位数据。主机必须根据从机的要求选择时钟极性和时钟相位。根据CPOL和CPHA位的选择,有四种SPI模式可用。
SPI模式 | CPOL | CPHA | 空闲状态下的时钟极性 | 用于采样和移位数据的时钟相位 |
---|---|---|---|---|
0 | 0 | 0 | 逻辑低电平 | 数据在上升沿采样,在下降沿移出 |
1 | 0 | 1 | 逻辑低电平 | 数据在下降沿采样,在上升沿移出 |
2 | 1 | 1 | 高电平 | 数据在下降沿采样,在上升沿移出 |
3 | 1 | 0 | 高电平 | 数据在上升沿采样,在下降沿移出 |
上述简单介绍SPI的接口,下面看看Linux中,是如何注册SPI控制器,如何使用SPI与SPI从设备进行通信。
Linux SPI控制器注册
下面以全志平台以Linux4.9内核为基础,介绍SPI控制器是怎么注册到内核。
dts等配置
Linux内核通过设备树描述设备信息,spi设备的描述信息如下:
spi0: spi@04025000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "allwinner,sun8i-spi";
device_type = "spi0";
reg = <0x0 0x04025000 0x0 0x1000>; /*spi0控制器配置寄存器信息 */
interrupts = <GIC_SPI 15 IRQ_TYPE_LEVEL_HIGH>; /* 描述spi0中断信息 */
clocks = <&clk_pll_periph0300m>, <&clk_spi0>; /* 描述spi0时钟配置 */
clock-frequency = <100000000>; /* spi0支持的最大时钟 */
pinctrl-0 = <&spi0_pins_a &spi0_pins_b>; /* spi0引脚配置信息 */
pinctrl-1 = <&spi0_pins_c>;
pinctrl-names = "default", "sleep";
spi_slave_mode = <0>; /* spi从模式标志 */
spi0_cs_number = <1>; /* spi片选信息 */
status = "okay";
spi_board0 {
/* 挂载到spi0上的从设备 */
device_type = "spi_board0";
compatible = "spi-sensor";
spi-max-frequency = <1000000>; /* 从设备支持的最大通信频率 */
reg = <0x0>;
spi-rx-bus-width=<0x04>; /* 对从设备读取数据时使用的data线数量 */
spi-tx-bus-width=<0x04>; /* 对从设备写入数据时使用的data线数量 */
status="okay";
};
};
设备树配置信息之后,开机时系统将会解析设备树,并根据设备树信息注册platform设备,而驱动端将解析dts信息,向内核注册spi控制器,全志平台的spi控制器驱动是drivers/spi/spi-sunxi.c,在该驱动中,将会看到以下代码:
static const struct of_device_id sunxi_spi_match[] = {
{
.compatible = "allwinner,sun8i-spi", },
{
.compatible = "allwinner,sun50i-spi", },
{
},
};
MODULE_DEVICE_TABLE(of, sunxi_spi_match);
static struct platform_driver sunxi_spi_driver = {
.probe = sunxi_spi_probe,
.remove = sunxi_spi_remove,
.driver = {
.name = SUNXI_SPI_DEV_NAME,
.owner = THIS_MODULE,
.pm = SUNXI_SPI_DEV_PM_OPS,
.of_match_table = sunxi_spi_match,
},
};
static int __init sunxi_spi_init(void)
{
return platform_driver_register(&sunxi_spi_driver);
}
static void __exit sunxi_spi_exit(void)
{
platform_driver_unregister(&sunxi_spi_driver);
}
fs_initcall_sync(sunxi_spi_init);
module_exit(sunxi_spi_exit);
通过Linux设备驱动模型,我们知道在加载spi-sunxi驱动的时候,将会匹配调用到sunxi_spi_probe()函数,在该函数中进行dts的解析以及spi控制器的登记注册。
static int sunxi_spi_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct resource *mem_res;
struct sunxi_spi *sspi;
struct sunxi_spi_platform_data *pdata;
struct spi_master *master;
struct sunxi_slave *slave;
...
/* 从dts中获取知道是哪个spi控制器 */
pdev->id = of_alias_get_id(np, "spi");
if (pdev->id < 0) {
SPI_ERR("SPI failed to get alias id\n");
return -EINVAL;
}
...
/* 从dts获取spi控制器寄存器信息,实际上就是dts中的reg */
mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (mem_res == NULL) {
SPI_ERR("Unable to get spi MEM resource\n");
ret = -ENXIO;
goto err0;
}
/* 获取中断号,dts中的interrupts */
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
SPI_ERR("No spi IRQ specified\n");
ret = -ENXIO;
goto err0;
}
/* create spi master */
/* 创建spi控制器句柄 */
master = spi_alloc_master(&pdev->dev, sizeof(struct sunxi_spi));
if (master == NULL) {
SPI_ERR("Unable to allocate SPI Master\n");
ret = -ENOMEM;
goto err0;
}
platform_set_drvdata(pdev, master);
sspi = spi_master_get_devdata(master);
memset(sspi, 0, sizeof(struct sunxi_spi));
sspi->master = master;
sspi->mode_type = MODE_TYPE_NULL;
/* 初始化spi控制器所需信息 */
master->dev.of_node = pdev->dev.of_node;
master->bus_num = pdev->id; /* 标注spi控制器索引 */
master->max_speed_hz = SPI_MAX_FREQUENCY; /* 支持的最大传输速率 */
master->setup = sunxi_spi_setup; /* spi设备开始传输数据之前可能调用该函数设置模式或时钟 */
master->can_dma = sunxi_spi_can_dma; /* 用于配置使用DMA传输 */
master->transfer_one = sunxi_spi_transfer_one; /* 传输数据函数 */
master->set_cs = sunxi_spi_cs_control; /* 设备片选信号 */
master->num_chipselect = pdata->cs_num; /* 告知有多少个片选信号 */
master->bits_per_word_mask = SPI_BPW_MASK(8);
/* the spi->mode bits understood by this driver: */
/* 配置spi控制器支持的模式 */
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LSB_FIRST |
SPI_TX_DUAL | SPI_TX_QUAD | SPI_RX_DUAL | SPI_RX_QUAD;
...
snprintf(sspi->dev_name, sizeof(sspi->dev_name), SUNXI_SPI_DEV_NAME"%d", pdev->id);
/* 向系统注册spi中断处理函数 */
err = devm_request_irq(&pdev->dev, irq, sunxi_spi_handler, 0, sspi->dev_name, sspi);
if (err) {
SPI_ERR("[spi%d] Cannot request IRQ\n", sspi->master->bus_num);
ret = -EINVAL;
...
/* 映射spi控制器寄存器 */
sspi->base_addr = ioremap(mem_res->start, resource_size(mem_res));
if (sspi->base_addr == NULL) {
SPI_ERR("[spi%d] Unable to remap IO\n", sspi->master->bus_num);
ret = -ENXIO;
goto err3;
}
sspi->base_addr_phy = mem_res->start;
...
/* spi控制器初始化 */
ret = sunxi_spi_hw_init(sspi, pdata, &pdev->dev);
if (ret != 0) {
SPI_ERR("[spi%d] spi hw init failed!\n", sspi->master->bus_num);
ret = -EINVAL;
goto err4;
}
/* 注册spi控制器 */
if (spi_register_master(master)) {
SPI_ERR("[spi%d] cannot register SPI master\n", sspi->master->bus_num);
ret = -EBUSY;
goto err6;
}
...
}
在经过上述的操作之后,系统已经有一个spi控制器,在/dev目录下将会可以看到spiX节点,但是spi_register_master()函数具体是如何注册的,还没有介绍,下面继续。
spi_register_master
int spi_register_master(struct spi_master *master)
{
...
/* spi是一个多从设备的接口,所以需要确认片选信息是否都已经有了 */
status = of_spi_register_master(master);
if (status)
return status;
/* even if it's just one always-selected device, there must
* be at least one chipselect
*/
if (master->num_chipselect == 0)
return -EINVAL;
if ((master->bus_num < 0) && master->dev.of_node)
master->bus_num = of_alias_get_id(master->dev.of_node, "spi");
/* convention: dynamically assigned bus IDs count down from the max */
if (master->bus_num < 0) {
/* FIXME switch to an IDR based scheme, something like
* I2C now uses, so we can't run out of "dynamic" IDs
*/
master->bus_num = atomic_dec_return(&dyn_bus_id);
dynamic = 1;
}
/* 初始化master的各类锁 */
INIT_LIST_HEAD(&master->queue);
spin_lock_init(&master->queue_lock);
spin_lock_init(&master->bus_lock_spinlock);
mutex_init(&master->bus_lock_mutex);
mutex_init(&master->io_mutex);
master->bus_lock_flag = 0;
init_completion(&master->xfer_completion);
if (!master->max_dma_len)
master->max_dma_len = INT_MAX;
/* 向系统注册spi master设备:/dev/spiX */
dev_set_name(&master->dev, "spi%u", master->bus_num);
status = device_add(&master->dev);
if (status < 0)
goto done;
dev_dbg(dev, "registered master %s%s\n", dev_name(&master->dev),
dynamic ? " (dynamic)" : "");
if (master->transfer)
dev_info(dev, "master is unqueued, this is deprecated\n");
else {
/* 初始化控制器的数据传输队列 */
status = spi_master_initialize_queue(master);
if (status) {
device_del(&master->dev);
goto done;
}
}
mutex_lock(&board_lock);
/* 将spi控制器添加到spi_master_list链表 */
list_add_tail(&master->list, &spi_master_list);
list_for_each_entry(bi, &board_list, list)
/* 从board_list链表中查看是否有匹配该控制器的从设备,实际上就是看spi总线索引是否一致 */
spi_match_master_to_boardinfo(master, &bi->board_info);
mutex_unlock(&board_lock);
/* 从设备树中解析注册该控制的从设备 */
of_register_spi_devices(master);
}
在这里,介绍了spi控制器的注册流程,但是spi的从设备又是如何注册的呢?
spi_add_device
在前面,我们介绍了注册spi控制器的时候,将会用过of_register_spi_devices()函数注册挂载到该总线上的spi从设备。of_register_spi_devices()解析dts信息之后,初始化spi_device,然后调用spi_add_device(),而spi_add_device的实现也非常简单。
int spi_add_device(struct spi_device *spi)
{
static DEFINE_MUTEX(spi_add_lock);
struct spi_master *master = spi->master;
struct device *dev = master->dev.parent;
int status;
/* 设置spi从设备名称,一般命令为spiX.Y,
* X是spi控制器索引,而Y则是从设备片选索引
*/
/* Set the bus ID string */
spi_dev_set_name(spi);
/* 接下里将会确认从设备的片选信号是否没有被占用,
* 然后通过spi_setup()配置spi控制器
*/
/* Device may be bound to an active driver when this returns */
/* 最后通过device_add()函数添加设备,此时从设备的父设备是spi控制器 */
status = device_add(&spi->dev);
return 0;
}
此时,spi控制器驱动已经注册,spi从设备也已经注册,但是spi从设备如何通过spi接口进行数据通信呢?继续。
spi从设备驱动
Linux系统中,有设备之后,也需要有相应的驱动才可以正常的使用,否则设备也只是一个没用的东西,不能发挥它真实的功能,上面已经注册了spi控制器,spi从设备,可以通过spi从设备操作spi控制器,但是,spi从设备也需要有相应的驱动,才知道应该如何使用,接下里就是给大家介绍这一点。
在上面dts章节,我们已经介绍了spi从设备的信息如下:
spi_board0 {
/* 挂载到spi0上的从设备 */
device_type = "spi_board0";
compatible = "spi_sensor";
spi-max-frequency = <1000000>; /* 从设备支持的最大通信频率 */
reg = <0x0>;
spi-rx-bus-width=<0x04>; /* 对从设备读取数据时使用的data线数量 */
spi-tx-bus-width=<0x04>; /* 对从设备写入数据时使用的data线数量 */
status="okay";
};
设备与驱动的compatible信息需要匹配上,才会调用驱动的probe,所以,我们的spi从设备,先这样写:
static int spi_senspr_probe(struct spi_device *spi)
{
/* 此时可以在这里解析上述spi从设备的dts信息,并进行有效的初始化,
* 比如IMU将会调用 iio_device_register 注册iio设备,应用将通
* 过 /sys/bus/iio/devices/iio:deviceX 节点最终操作到spi;
* 比如spi flash,将会调用 mtd_device_register 注册mtd设备;
* 不同的驱动可以有不一样的需求,实现对用户空间的交互。
*/
return 0;
}
static const struct spi_device_id spi_sensor_ids[] = {
{
"spi_sensor", 0},
};
static const struct of_device_id of_match_spi_sensor[] = {
{
.compatible = "spi_sensor"},
};
static struct spi_driver spi_sensor_driver = {
.probe = spi_sensor_probe,
.remove = spi_sensor_remove,
.driver = {
.name = "spi_sensor",
.owner = THIS_MODULE,
.of_match_table = of_match_spi_sensor,
},
.id_table = spi_sensor_ids,
};
static int __init spi_sensor_init(void)
{
return spi_register_driver(&spi_sensor_driver);
}
module_init(spi_sensor_init);
上面介绍spi从设备的驱动匹配,匹配之后根据实际情况透传接口给用户空间进行操作,这个差异较大,不详细介绍,下面继续介绍spi从设备中使用到的spi接口。
在介绍驱动使用spi的接口之前,先介绍一些重要的数据结构。
struct spi_transfer,spi传输的读写数据的buffer单元。
struct spi_transfer {
const void *tx_buf; /* 待发送的数据 */
void *rx_buf; /* 待接收数据的缓冲区 */
unsigned len; /* 数据长度 */
dma_addr_t tx_dma; /* tx_buf的DMA地址 */
dma_addr_t rx_dma; /* rx_buf的DMA地址 */
struct sg_table tx_sg;
struct sg_table rx_sg;
unsigned cs_change:1;
unsigned tx_nbits:3; /* 多少bit一起发送 */
unsigned rx_nbits:3; /* 一次接收多少bit */
#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;
};
struct spi_message,一次数据交互单元。
struct spi_message {
struct list_head transfers; /* 链表,保存需要进行交互的struct spi_transfer */
struct spi_device *spi;
unsigned is_dma_mapped:1; /* 标记tx_dma和rx_dma是否有效 */
/* REVISIT: we might want a flag affecting the behavior of the
* last transfer ... allowing things like "read 16 bit length L"
* immediately followed by "read L bytes". Basically imposing
* a specific message scheduling algorithm.
*
* Some controller drivers (message-at-a-time queue processing)
* could provide that as their default scheduling algorithm. But
* others (with multi-message pipelines) could need a flag to
* tell them about such special cases.
*/
/* completion is reported through a callback */
void (*complete)(void *context); /* 异步传输时的传输完成回调函数指针 */
void *context;
unsigned frame_length; /* 帧数据长度 */
unsigned actual_length;
int status; /* 本次传输的完成状态 */
};
一些函数:
函数原型 | 作用 |
---|---|
void spi_message_init(struct spi_message *m) | 初始化struct spi_message |
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m) | 将spi_transfer添加到spi_message |
int spi_sync(struct spi_device *spi, struct spi_message *message) | 同步传输message |
int spi_async(struct spi_device *spi, struct spi_message *message) | 异步传输message,传输完成后回调 |
int spi_read(struct spi_device *spi, void *buf, size_t len) | 读数据 |
int spi_write(struct spi_device *spi, const void *buf, size_t len) | 写数据 |
Linux内核原生自带了一个spi从设备的范例,路径是 drivers/spi/spidev.c,另外用户空间的使用范例也可以参考内核 tools/spi/spidev_test.c。
spi设备进行数据传输时,函数调用过程如下:
整体框架图
参考文章: