最近分析了一下Linux 中的SPI驱动框架,将自己的理解总结一下,不足之处还请斧正!
1、SPI通信基础知识
SPI(Serial Peripheral Interface)是一种串行(一次发送1bit数据),同步(使用同一个时钟,时钟信号有SPI的Master发出),全双工(收发能够同时进行)通信接口,速度可达到十几Mbps,SPI通信分为主从设备,在主设备(Master)上可挂接多个从设备(Slave),Master通过CS片选信号决定与哪个具体的Slave进行通信
SPI使用4线:CS(片选线)、CLK(时钟线)、MISO(Master in and Slave out)与MOSI(Master out and Slave in)
SPI的4种工作模式:工作模式描述是SPI数据传输的时序问题,由时钟极性(CPOL---Clock Polarity)和时钟相位(CPHA---Clock Phase)控制,CPOL表示CLK空闲时电平的高低,CPHA表示数据传输发生在CLK的上升沿还是下降沿,通过CPOL和CPHA组合成4种工作状态,例如模式0:CPOL : CPHA(0:0)表示CLK空闲时为低电平,并在时钟上升沿进行数据传输(具体时序图可自行百度)
2、SPI驱动分析(Linux Kernel Version 3.14.52,SoC为freescale imx6)
在Linux中将其分为3层:
第一层:SPI Core层,由Linux Kernel维护者进行编写与维护,对SPI通信的抽象,为Master和Slave提供接口,将Master和Slave分离开,Master不需要操心将来会挂载哪个Slave,Slave也不需要知道自己会被挂载到谁上
第二层:SPI Master层,由SoC厂商根据SoC中的SPI Master特性实例化SPI Core层提供的接口,实现对Master进行的硬件操作,并注册到SPI总线上
第三层:SPI Device层(或者SPI Slave层),具体SPI Slave设备的初始化等工作,例如我使用的XRM117X,通过调用SPI Core提供的接口实现Slave 和Master的对接工作.
来一张SPI驱动框架思维导图(嗯!图片有点大,实际调用过程其实并没有那么复杂,不想看图的可以看后面的代码分析)
这里有个pdf的文档 https://download.csdn.net/download/qq_37809296/10914161
注:本思维导图仅分析SPI相关的部分,和Linux驱动框架相关的未进行分析
注:结合上面的思维导图和接下的代码分析可能更快理解整个系统过程
分析主要根据前面所说的三层进行:分析过程请注意参数的初始化的传递过程
第一层SPI Core
1、SPI Core的注册
// 文件位置 kernel/driver/spi/spi.c
// 去掉了一些安全检查等代码
static int __init spi_init(void)
{
int status;
buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL); // buf为全局变量在传输过程(spi_write_then_read)中会用到如
status = bus_register(&spi_bus_type);// 总线的注册,关系到Linux中的驱动框架,没有进行分析
status = class_register(&spi_master_class); //类的注册,所有注册到SPI总线上的Master属于这个class,在/sys/class/spi_master可以看到所有的SPI master
return 0;
}
postcore_initcall(spi_init); // 决定了调用顺序,kernel调用init是分等级调用的,没有进行分析
struct bus_type spi_bus_type = {
.name = "spi",
.dev_groups = spi_dev_groups,
.match = spi_match_device,
.uevent = spi_uevent,
.pm = &spi_pm,
};
static struct class spi_master_class = {
.name = "spi_master",
.owner = THIS_MODULE,
.dev_release = spi_master_release,
};
SPI核心层的注册就这么多,主要是注册了一个SPI 总线,和注册一个SPI_master的类,之后的匹配过程就交到了总线上
第二层 SPI Master相关的注册过程
注:我使用的是imx6的板子
// 代码位置 Spi-imx.c (drivers\spi)
// 首先将master注册为platform设备,主要是用来匹配设备树中的device,然后执行porbe函数
// 和platform总线相关暂时不分析,只需要知道他会执行 .probe = spi_imx_probe, 就可以了
static struct platform_driver spi_imx_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.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);// init时调用
经过platform 总线的match之后进入 probe函数
// 代码位置 Spi-imx.c (drivers\spi)
// 去除一些安全检查和无关信息
static int spi_imx_probe(struct platform_device *pdev)
{
// 从设备树中查找fsl,spi-num-chipselects ,表示在该Master中挂载了多少个Slave设备
ret = of_property_read_u32(np, "fsl,spi-num-chipselects", &num_cs);
// 调用spi core提供的接口 分配了两个结构体空间(struct spi_master + struct spi_imx_data)
// spi_master 是spi core提供的表示一个master ,spi_imx_data对master的补充作用
// spi_master 放在前面, spi_imx_data紧随其后,所以我们可以从spi_master得到spi_imx_data
master = spi_alloc_master(&pdev->dev,sizeof(struct spi_imx_data) + sizeof(int) * num_cs);
platform_set_drvdata(pdev, master);
master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32);
master->bus_num = pdev->id;
master->num_chipselect = num_cs;
// 前面说的 从spi_master得到spi_imx_data
spi_imx = spi_master_get_devdata(master);
// 结构体的相互绑定
spi_imx->bitbang.master = master;
// 设置片选信号引脚,并向内核申请
for (i = 0; i < master->num_chipselect; i++) {
int cs_gpio = of_get_named_gpio(np, "cs-gpios", i);
if (!gpio_is_valid(cs_gpio) && mxc_platform_info)
cs_gpio = mxc_platform_info->chipselect[i];
spi_imx->chipselect[i] = cs_gpio;
if (!gpio_is_valid(cs_gpio))
continue;
ret = devm_gpio_request(&pdev->dev, spi_imx->chipselect[i],DRIVER_NAME);
}
// 方法的绑定,将来调用过程都是通过这些函数指针进行的
spi_imx->bitbang.chipselect = spi_imx_chipselect;
spi_imx->bitbang.setup_transfer = spi_imx_setupxfer;
// 从device传输时会调用的函数
spi_imx->bitbang.txrx_bufs = spi_imx_transfer;
spi_imx->bitbang.master->setup = spi_imx_setup;
spi_imx->bitbang.master->cleanup = spi_imx_cleanup;
spi_imx->bitbang.master->prepare_message = spi_imx_prepare_message;
spi_imx->bitbang.master->unprepare_message = spi_imx_unprepare_message;
// 设置SPI的传输模式,前面说的4中传输模式
spi_imx->bitbang.master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
init_completion(&spi_imx->xfer_done);
// 对具体硬件操作的函数
spi_imx->devtype_data = of_id ? of_id->data : (struct spi_imx_devtype_data *) pdev->id_entry->driver_data;
// 获取SPI的地址
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
spi_imx->base = devm_ioremap_resource(&pdev->dev, res);
// 获取中断
spi_imx->irq = platform_get_irq(pdev, 0);
ret = devm_request_irq(&pdev->dev, spi_imx->irq, spi_imx_isr, 0, dev_name(&pdev->dev), spi_imx);
// 时钟相关
spi_imx->clk_ipg = devm_clk_get(&pdev->dev, "ipg");
spi_imx->clk_per = devm_clk_get(&pdev->dev, "per");
ret = clk_prepare_enable(spi_imx->clk_per);
ret = clk_prepare_enable(spi_imx->clk_ipg);
spi_imx->spi_clk = clk_get_rate(spi_imx->clk_per);
// && 表示前面为真才执行后面的函数 对DMA进行初始化
if (spi_imx->devtype_data == &imx51_ecspi_devtype_data&& spi_imx_sdma_init(&pdev->dev, spi_imx, master, res))
// 对spi master进行初始化
spi_imx->devtype_data->reset(spi_imx);
spi_imx->devtype_data->intctrl(spi_imx, 0);
master->dev.of_node = pdev->dev.of_node;
// 重点函数 //SPI Master的注册到SPI 总线上的过程 是SPI Core提供的接口
ret = spi_bitbang_start(&spi_imx->bitbang);
clk_disable_unprepare(spi_imx->clk_ipg);
clk_disable_unprepare(spi_imx->clk_per);
return ret;
}
由probe引入的重点函数进行spi Master向spi 总线注册的过程 spi_bitbang_start
// 代码位置Spi-bitbang.c (drivers\spi)
// 去除一些非重点信息
int spi_bitbang_start(struct spi_bitbang *bitbang)
{
struct spi_master *master = bitbang->master;
// 从这里可以看出一定要有chipselect函数
if (!master || !bitbang->chipselect)
return -EINVAL;
// 未设置spi传输模式就使用默认的传输模式
if (!master->mode_bits)
master->mode_bits = SPI_CPOL | SPI_CPHA | bitbang->flags;
// 不能使用master-》transfer 和 master->transfer_one_message函数指针
if (master->transfer || master->transfer_one_message)
return -EINVAL;
master->prepare_transfer_hardware = spi_bitbang_prepare_hardware;
master->unprepare_transfer_hardware = spi_bitbang_unprepare_hardware;
// 上层传输时将会调用 // 到时候调用到了这个函数指针我希望你还有点印象
master->transfer_one_message = spi_bitbang_transfer_one;
// spi-imx中定义了这个 spi_imx->bitbang.txrx_bufs = spi_imx_transfer;
if (!bitbang->txrx_bufs) {
......
}
// 重点函数对spi进行注册 spi core提供的接口
ret = spi_register_master(spi_master_get(master));
return 0;
}
正式开始向spi core注册spi master调用的是 spi_register_master
// 代码位置 Spi.c (drivers\spi)
int spi_register_master(struct spi_master *master)
{
struct device *dev = master->dev.parent;
status = of_spi_register_master(master);
if (master->num_chipselect == 0)
return -EINVAL;
// 设备驱动模型内容
dev_set_name(&master->dev, "spi%u", master->bus_num);
status = device_add(&master->dev);
// 调用队列的初始化
if (master->transfer)
dev_info(dev, "master is unqueued, this is deprecated\n");
else {
// 重点函数 ,初始化了传输函数 做个标记 在后面分析
status = spi_master_initialize_queue(master);
}
// 将master加到spi core维护的链表 spi_master_list //所有的spi master都在这个链表中
// spi core还维护了一个 device链表
list_add_tail(&master->list, &spi_master_list);
// 使用了设备树方式获取设备信息将不会掉用这里的函数进行匹配
list_for_each_entry(bi, &board_list, list)
spi_match_master_to_boardinfo(master, &bi->board_info);
// 重点函数
of_register_spi_devices(master);
/* Register devices from the device tree and ACPI */
acpi_register_spi_devices(master);
done:
return status;
}
对 of_register_spi_devices 的分析
// 代码位置 Spi.c (drivers\spi)
static void of_register_spi_devices(struct spi_master *master)
{
struct spi_device *spi;
struct device_node *nc;
// 遍历设备树中spi master 节点下的所有 spi device
for_each_available_child_of_node(master->dev.of_node, nc) {
// 重点函数
// SPI从设备的主机端代理之后这个结构体指针将会通过spi总线传递到spi_device驱动中去
spi = spi_alloc_device(master);
if (of_modalias_node(nc, spi->modalias,sizeof(spi->modalias)) < 0)
rc = of_property_read_u32(nc, "reg", &value);
spi->chip_select = value;
// 对模式的设置 //所以可以在设备树中直接指定是spi工作模式
if (of_find_property(nc, "spi-cpha", NULL))
spi->mode |= SPI_CPHA;
if (of_find_property(nc, "spi-cpol", NULL))
spi->mode |= SPI_CPOL;
if (of_find_property(nc, "spi-cs-high", NULL))
spi->mode |= SPI_CS_HIGH;
if (of_find_property(nc, "spi-3wire", NULL))
spi->mode |= SPI_3WIRE;
if (of_find_property(nc, "spi-lsb-first", NULL))
spi->mode |= SPI_LSB_FIRST;
if (!of_property_read_u32(nc, "spi-tx-bus-width", &value))
......
if (!of_property_read_u32(nc, "spi-rx-bus-width", &value))
......
/* Device speed */
rc = of_property_read_u32(nc, "spi-max-frequency", &value);
......
spi->max_speed_hz = value;
/* IRQ */
spi->irq = irq_of_parse_and_map(nc, 0);
/* Store a pointer to the node in the device structure */
of_node_get(nc);
spi->dev.of_node = nc;
/* Register the new device */
request_module("%s%s", SPI_MODULE_PREFIX, spi->modalias);
// 重点函数
rc = spi_add_device(spi);
}
}
// 代码位置 Spi.c (drivers\spi)
对 spi_add_device的分析
// 代码位置 Spi.c (drivers\spi)
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_dev_set_name(spi);
status = bus_for_each_dev(&spi_bus_type, NULL, spi, spi_dev_check);
if (master->cs_gpios)
spi->cs_gpio = master->cs_gpios[spi->chip_select];
// 重点函数 对spi master的设置
status = spi_setup(spi);
status = device_add(&spi->dev);
}
// 代码位置 Spi.c (drivers\spi)
int spi_setup(struct spi_device *spi)
{
......
if (spi->master->setup)
status = spi->master->setup(spi); //这个函数指针的初始化在spi master的probe函数中
......
return status;
}
到此SPI Master端的初始化就构建成功了,主要注意一下Master中有一个 device的代理,通过master和device的桥梁就是通过这个 struct spi_device 这个结构体的挂接实现的
第三层 : spi device层或spi slave层
这里我将不进行XRM117X怎么实现SPI转串口的功能,主要分析XRM117X与imx6通过SPI进行通信的过程
1、注册过程
// 代码位置 Xrm117x.c (drivers\tty\serial)
static int __init xrm117x_init(void)
{
return spi_register_driver(&xrm117x_spi_uart_driver);
}
module_init(xrm117x_init);
// 代码位置 Spi.c (drivers\spi)
int spi_register_driver(struct spi_driver *sdrv)
{
sdrv->driver.bus = &spi_bus_type;
if (sdrv->probe)
sdrv->driver.probe = spi_drv_probe;
if (sdrv->remove)
sdrv->driver.remove = spi_drv_remove;
if (sdrv->shutdown)
sdrv->driver.shutdown = spi_drv_shutdown;
return driver_register(&sdrv->driver); // 驱动模型相关内容,暂时不分析
}
2、对SPI进行读写的过程:主要分析怎么从device中调用到master中的对SPI 控制器具体硬件操作
// 代码位置 Xrm117x.c (drivers\tty\serial)
static u8 xrm117x_port_read(struct uart_port *port, u8 reg)
{
struct xrm117x_port *s = dev_get_drvdata(port->dev);
mutex_lock(&s->mutex_bus_access);
// 重点函数,将进行SPI传输
status = spi_write_then_read(spi_dev, &cmd, 1, &result, 1);
mutex_unlock(&s->mutex_bus_access);
return result;
}
SPI传输过程
// 代码位置 Spi.c (drivers\spi)
int spi_write_then_read(struct spi_device *spi,const void *txbuf, unsigned n_tx,void *rxbuf, unsigned n_rx)
{
struct spi_message message;
struct spi_transfer x[2];
if ((n_tx + n_rx) > SPI_BUFSIZ || !mutex_trylock(&lock)) {
local_buf = kmalloc(max((unsigned)SPI_BUFSIZ, n_tx + n_rx), GFP_KERNEL | GFP_DMA);
} else {
local_buf = buf; // 还记的 spi_init中为全局变量buf分配了内存吗?
}
// 初始化 message //传输都是以message方式传输的,每个message下面都有transfer结构体
spi_message_init(&message);
if (n_tx) {
x[0].len = n_tx;
spi_message_add_tail(&x[0], &message); //将transfer挂载到message中
}
if (n_rx) {
x[1].len = n_rx;
spi_message_add_tail(&x[1], &message);
}
memcpy(local_buf, txbuf, n_tx);
x[0].tx_buf = local_buf;
x[1].rx_buf = local_buf + n_tx;
// 重点函数 进行spi传输
status = spi_sync(spi, &message);
if (status == 0)
memcpy(rxbuf, x[1].rx_buf, n_rx);
return status;
}
int spi_sync(struct spi_device *spi, struct spi_message *message)
{
// 重点函数 进行spi传输
return __spi_sync(spi, message, 0);
}
static int __spi_sync(struct spi_device *spi, struct spi_message *message,
int bus_locked)
{
// 还记得 master中对spi device的代理吗,在那时已经将spi_device结构体和spi_master绑定在一起了
struct spi_master *master = spi->master;
// 重点函数
status = spi_async_locked(spi, message);
if (status == 0) {
wait_for_completion(&done);
status = message->status;
}
return status;
}
// 去掉了很多无用信息
int spi_async_locked(struct spi_device *spi, struct spi_message *message)
{
struct spi_master *master = spi->master;
// 重点函数
ret = __spi_async(spi, message);
return ret;
}
static int __spi_async(struct spi_device *spi, struct spi_message *message)
{
struct spi_master *master = spi->master;
message->spi = spi;
trace_spi_message_submit(message);
// 重点函数
// 你可能很想知道这个函数指针是在哪里初始化的,
// spi.c中搜这个函数试试 spi_master_initialize_queue
// 看下面的分析
return master->transfer(spi, message);
}
补充:spi_master_initialize_queue中初始化 master->transfer函数指针
// 代码位置 Spi.c (drivers\spi)
// 去除了很多无用的东西
static int spi_master_initialize_queue(struct spi_master *master)
{
int ret;
master->transfer = spi_queued_transfer; // 在这里初始化了transfer函数指针//
if (!master->transfer_one_message)
master->transfer_one_message = spi_transfer_one_message;
// 主要工作是建立一个工作线程//绑定线程的工作函数
// 请记住这里,后面一段代码中的疑问的答案在这里
ret = spi_init_queue(master);
ret = spi_start_queue(master);// 启动工作线程
}
继续具体的传输过程;:
// 代码位置 Spi.c (drivers\spi)
// 去除了很多非主线信息
static int spi_queued_transfer(struct spi_device *spi, struct spi_message *msg)
{
struct spi_master *master = spi->master;
list_add_tail(&msg->queue, &master->queue);
// 重点函数 进入工作队列,进行内核线程时的调度
// 执行master->pump_messages这个函数指针
// 那么问题来了,这个函数指针是在哪里初始化的?
if (!master->busy)
queue_kthread_work(&master->kworker, &master->pump_messages);
}
解决上面代码中的问题: 线程初始化时线程工作函数的绑定
// 代码位置 Spi.c (drivers\spi)
// 去除了很多信息
static int spi_init_queue(struct spi_master *master)
{
// 没错这就是线程的工作函数,前面一路的调用就到了这个函数中 spi_pump_messages
init_kthread_work(&master->pump_messages, spi_pump_messages);
}
static void spi_pump_messages(struct kthread_work *work)
{
struct spi_master *master = container_of(work, struct spi_master, pump_messages);
...........
// 去除的代码有点多,主要是线程的一些机制,who care ?
...........
if (!was_busy)
trace_spi_master_busy(master);
if (!was_busy && master->prepare_transfer_hardware) {
ret = master->prepare_transfer_hardware(master);
}
trace_spi_message_start(master->cur_msg);
if (master->prepare_message) {
ret = master->prepare_message(master, master->cur_msg);
}
ret = spi_map_msg(master, master->cur_msg);
// 重点函数 别在问我这个函数指针是在哪里初始化的了
// 还记得 spi_bitbang_start 这个函数吗?
// 不记得就ctl+f搜一下,之前的分析
// 你都能看到这里说明很有耐心呀,马上就到硬件的操作了
ret = master->transfer_one_message(master, master->cur_msg);
}
spi_bitbang_transfer_one 是怎么调用硬件控制器进行传输的呢
// 代码位置Spi-bitbang.c (drivers\spi)
static int spi_bitbang_transfer_one(struct spi_master *master, struct spi_message *m)
{
struct spi_bitbang *bitbang;
bitbang = spi_master_get_devdata(master);
cs_change = 1;
status = 0;
// 对于一个message中有多个transfer的情况,遍历所有transfer
list_for_each_entry(t, &m->transfers, transfer_list) {
/* override speed or wordsize? */
if (t->speed_hz || t->bits_per_word)
do_setup = 1;
// 是否需要调用控制器的setup函数
if (do_setup != 0) {
status = bitbang->setup_transfer(spi, t);
}
// 对片选信号的操作
if (cs_change) {
bitbang->chipselect(spi, BITBANG_CS_ACTIVE);
ndelay(nsecs);
}
cs_change = t->cs_change;
if (t->len) {
// 重点函数 直接进行了硬件信息传输
// 是不是又忘记在哪里初始化了的
// 所以我还是建议你去下载一下那个思维导图
// 直接告诉你在 spi_imx_probe 这个函数中 // spi-imx.c中
// spi_imx->bitbang.txrx_bufs = spi_imx_transfer;
status = bitbang->txrx_bufs(spi, t);
}
if (cs_change &&!list_is_last(&t->transfer_list, &m->transfers)) {
ndelay(nsecs);
bitbang->chipselect(spi, BITBANG_CS_INACTIVE);
ndelay(nsecs);
}
}
if (!(status == 0 && cs_change)) {
ndelay(nsecs);
bitbang->chipselect(spi, BITBANG_CS_INACTIVE);
ndelay(nsecs);
}
spi_finalize_current_message(master);
return status;
}
到这里了也就是具体硬件的操作了
// 代码位置 Spi-imx.c (drivers\spi)
static int spi_imx_transfer(struct spi_device *spi, struct spi_transfer *transfer)
{
struct spi_imx_data *spi_imx = spi_master_get_devdata(spi->master);
// 是否通过DMA进行传输?
// imx6在这里的使用SPI的DMA传输是有条件的,需要每次传输大于64字节才会启动DMA进行传输
if (spi_imx->bitbang.master->can_dma && spi_imx_can_dma(spi_imx->bitbang.master, spi, transfer)) {
ret = spi_imx_dma_transfer(spi_imx, transfer);
}
// 一般情况是通过PIO的模式进行传输的
return spi_imx_pio_transfer(spi, transfer);
}
问题:上面说到imx6要使用SPI的DMA功能必须要求一次transfer大于64字节,但现在的情况是我用的XRM117X转串口,其FIFO最大只有64字节,所以我在这个时候是用不上SPI的DMA功能的,并且发现,每次只传输几个字节,不停的发,占用CPU达50%,因此效率无从谈起,then 有没有人遇到与我相同的困惑,是否能指点一下!!不胜感激!!!!
best regards
Alee