嵌入式Linux驱动笔记(十二)------通俗易懂式分析了解spi框架
来源:互联网 发布:咖啡机推荐 知乎 编辑:程序博客网 时间:2024/06/16 04:14
你好!这里是风筝的博客,
欢迎和我一起交流。
之前讲过i2c框架:通俗易懂式分析了解i2c框架
如果之前你看懂了,那其实spi框架也差不多。
同样的,先上张图:
老规则,从上往下看起,以kernel4.8.17为例:
在mach-smdk2440.c文件里:
static struct platform_device *smdk2440_devices[] __initdata = { &s3c_device_ohci, &s3c_device_lcd, &s3c_device_wdt, &s3c_device_i2c0, &s3c_device_iis, &smdk2440_device_eth,};
没有spi的device,我们需要自己手动添加进去:
&s3c_device_spi0,
可以看到,他和s3c_device_i2c0是类似的:
static struct resource s3c_spi0_resource[] = { [0] = DEFINE_RES_MEM(S3C24XX_PA_SPI, SZ_32), [1] = DEFINE_RES_IRQ(IRQ_SPI0),};struct platform_device s3c_device_spi0 = { .name = "s3c2410-spi", .id = 0, .num_resources = ARRAY_SIZE(s3c_spi0_resource), .resource = s3c_spi0_resource, .dev = { .dma_mask = &samsung_device_dma_mask, .coherent_dma_mask = DMA_BIT_MASK(32), }};
name为”s3c2410-spi”。
接着看spi-s3c24xx.c文件:
static struct platform_driver s3c24xx_spi_driver = { .probe = s3c24xx_spi_probe, .remove = s3c24xx_spi_remove, .driver = { .name = "s3c2410-spi", .pm = S3C24XX_SPI_PMOPS, },};module_platform_driver(s3c24xx_spi_driver);
这里名字匹配,会调用probe函数:
static int s3c24xx_spi_probe(struct platform_device *pdev){ /*省略......*/ master = spi_alloc_master(&pdev->dev, sizeof(struct s3c24xx_spi)); if (master == NULL) { dev_err(&pdev->dev, "No memory for spi_master\n"); return -ENOMEM; } hw = spi_master_get_devdata(master); hw->master = master; hw->pdata = pdata = dev_get_platdata(&pdev->dev); hw->dev = &pdev->dev; /*省略......*/ /* the spi->mode bits understood by this driver: */ master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; master->num_chipselect = hw->pdata->num_cs; master->bus_num = pdata->bus_num; master->bits_per_word_mask = SPI_BPW_MASK(8); /* setup the state for the bitbang driver */ hw->bitbang.master = hw->master; hw->bitbang.setup_transfer = s3c24xx_spi_setupxfer; hw->bitbang.chipselect = s3c24xx_spi_chipsel; hw->bitbang.txrx_bufs = s3c24xx_spi_txrx; hw->master->setup = s3c24xx_spi_setup; /*省略......*/ err = devm_request_irq(&pdev->dev, hw->irq, s3c24xx_spi_irq, 0, pdev->name, hw); /*省略......*/ s3c24xx_spi_initialsetup(hw); /* register our spi controller */ err = spi_bitbang_start(&hw->bitbang); if (err) { dev_err(&pdev->dev, "Failed to register SPI master\n"); goto err_register; } return 0;}
这里就涉及到spi_master了,SPI控制器负责按照设定的物理信号格式在主控和spi设备之间交换数据,SPI控制器数据是如何被传输的,而不关心数据的内容。SPI通用接口层用spi_master结构来表示一个spi控制器。
probe里会对master里的各种数据和回调函数进行填充,如:
hw->bitbang.setup_transfer:计算预分频系数并写入寄存器
master->num_chipselect:控制器支持的片选数量,即能支持多少个spi设备
hw->bitbang.chipselect:对硬件的设置,禁止或使能CS信号
hw->master->setup:设置SPI模式
以及设置中断:s3c24xx_spi_irq
注意这一句:hw->bitbang.txrx_bufs = s3c24xx_spi_txrx;
也就是s3c24xx_spi_txrx函数,注意一下,这个我们待会讲。
之后主要就是调用spi_bitbang_start进行register SPI master了:
int spi_bitbang_start(struct spi_bitbang *bitbang){ struct spi_master *master = bitbang->master; int ret; if (!master || !bitbang->chipselect) return -EINVAL; mutex_init(&bitbang->lock); if (!master->mode_bits) master->mode_bits = SPI_CPOL | SPI_CPHA | bitbang->flags; 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 = spi_bitbang_transfer_one; master->set_cs = spi_bitbang_set_cs; if (!bitbang->txrx_bufs) { bitbang->use_dma = 0; bitbang->txrx_bufs = spi_bitbang_bufs; if (!master->setup) { if (!bitbang->setup_transfer) bitbang->setup_transfer = spi_bitbang_setup_transfer; master->setup = spi_bitbang_setup; master->cleanup = spi_bitbang_cleanup; } } /* driver may get busy before register() returns, especially * if someone registered boardinfo for devices */ ret = spi_register_master(spi_master_get(master)); if (ret) spi_master_put(master); return 0;}
这里就是开始对master进行设置了:
master->prepare_transfer_hardware:在发起一个数据传送过程前,给控制器驱动一个机会,申请或释放某些必要的硬件资源。
master->unprepare_transfer_hardware:在发起一个数据传送过程后,清理回调函数。
master->setup:修改控制器的工作模式或参数
bitbang->setup_transfer:设置数据传输速率。
master->cleanup:SPI从设备被释放时,该回调函数会被调用,以便释放该从设备所占用的硬件资源。
等等……
这里也要注意一个:master->transfer_one = spi_bitbang_transfer_one;
这个master->transfer_one和master->transfer_one_message很像,不要搞混了。先留意一下spi_bitbang_transfer_one这个函数。
这里注意了,不要对master->transfer || master->transfer_one_message进行回调函数填充!
接着调用spi_register_master函数:
int spi_register_master(struct spi_master *master){ /*省略......*/ /* 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; } 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; /* register the device, then userspace will see it. * registration fails if the bus ID is in use. */ 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 we're using a queued driver, start the queue */ 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; } } /* add statistics */ spin_lock_init(&master->statistics.lock); mutex_lock(&board_lock); 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); mutex_unlock(&board_lock); /* Register devices from the device tree and ACPI */ of_register_spi_devices(master); acpi_register_spi_devices(master);done: return status;}
这算是一个比较关键的函数,里面:
master->bus_num,代表的是挂在哪条总线上,spi0或者spi1之类的。
接着初始化master->queue,这里,queue,队列!为什么会有队列呢?
为了解决多个不同的SPI设备共享SPI控制器而带来的访问冲突,spi_bitbang使用内核提供的工作队列(workqueue)。workqueue是Linux内核中定义的一种回调处理方式。采用这种方式需要传输数据时,不直接完成数据的传输,而是将要传输的工作分装成相应的消息(spi_message),发送给对应的workqueue,由与workqueue关联的内核守护线程(daemon)负责具体的执行。由于workqueue会将收到的消息按时间先后顺序排列,这样就是对设备的访问严格串行化,解决了冲突。
所以SPI的通信是通过消息队列机制。
接着设置名字:dev_set_name(&master->dev, “spi%u”, master->bus_num);
就是刚刚说的spi0、spi1……
然后,
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;
}
}
之前说过,我们没有对master->transfer进行填充,所以会进入spi_master_initialize_queue函数,
最后,
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);
将master->list添加到spi_master_list列表,循环遍历spi设备配置结构体,然后与spi控制的总线号匹配,成功则生成新spi设备。
我们来看下spi_master_initialize_queue函数:
static int spi_master_initialize_queue(struct spi_master *master){ int ret; master->transfer = spi_queued_transfer; if (!master->transfer_one_message) master->transfer_one_message = spi_transfer_one_message; /* Initialize and start queue */ ret = spi_init_queue(master); if (ret) { dev_err(&master->dev, "problem initializing queue\n"); goto err_init_queue; } master->queued = true; ret = spi_start_queue(master); if (ret) { dev_err(&master->dev, "problem starting queue\n"); goto err_start_queue; } return 0;err_start_queue: spi_destroy_queue(master);err_init_queue: return ret;}
这里,系统就会自动帮我们填充master->transfer以及master->transfer_one_message了。
就是spi的通信函数。从名字看,这两个都是通信函数,那他们两个是什么关系呢?别急,待会讲。
然后调用spi_init_queue和spi_start_queue函数初始化队列并启动工作线程(spi_pump_messages函数):init_kthread_work(&master->pump_messages, spi_pump_messages)
queue_kthread_work(&master->kworker, &master->pump_messages)
.
最后,我们来看spi是怎么读写的,在spi设备驱动层提供了两种数据传输方式。一种是半双工方式,另一种就是全双工方式。write方法提供了半双工读访问,read方法提供了半双工写访问。
在spi.h文件里,以写函数为例:
static inline intspi_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);}
初始化spi_message,然后把spi_transfer链接到spi_message尾部。最后调用spi_sync函数,调用过程:
spi_sync __spi_sync(spi, message); __spi_queued_transfer(spi, message, false);//这个就是master->transfer __spi_pump_messages(master, false); wait_for_completion(&done);
记得之前说的master->transfer和master->transfer_one_message的关系吗?
先进入__spi_queued_transfer函数:
static int __spi_queued_transfer(struct spi_device *spi, struct spi_message *msg, bool need_pump){ struct spi_master *master = spi->master; unsigned long flags; spin_lock_irqsave(&master->queue_lock, flags); if (!master->running) { spin_unlock_irqrestore(&master->queue_lock, flags); return -ESHUTDOWN; } msg->actual_length = 0; msg->status = -EINPROGRESS; list_add_tail(&msg->queue, &master->queue); if (!master->busy && need_pump) queue_kthread_work(&master->kworker, &master->pump_messages); spin_unlock_irqrestore(&master->queue_lock, flags); return 0;}
这里,master->transfer里,先将携带数据的结构体spi_message挂到master->queue上。每一次数据传输都将要传输的数据包装成结构体spi_message传递。
接着将该传输任务添加到工作队列头master->kworker,在某个合适的时间, master->pump_messages将被调度运行,也就是spi_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); __spi_pump_messages(master, true);}
再看__spi_pump_messages函数,里面有一句:
ret = master->transfer_one_message(master, master->cur_msg); if (ret) { dev_err(&master->dev, "failed to transfer one message from queue\n"); goto out; }
这里就会调用到master->transfer_one_message了,进行数据传输。
最后就会wait_for_completion阻塞等待传输完成了。
那他到底是怎么传输的呢?看下master->transfer_one_message函数:
static int spi_transfer_one_message(struct spi_master *master, struct spi_message *msg){ struct spi_transfer *xfer; bool keep_cs = false; int ret = 0; unsigned long long ms = 1; struct spi_statistics *statm = &master->statistics; struct spi_statistics *stats = &msg->spi->statistics; spi_set_cs(msg->spi, true);//spi片选激活 SPI_STATISTICS_INCREMENT_FIELD(statm, messages); SPI_STATISTICS_INCREMENT_FIELD(stats, messages); list_for_each_entry(xfer, &msg->transfers, transfer_list) {//从spi_message结构的transfers链表中获取spi_transfer结构 trace_spi_transfer_start(msg, xfer); spi_statistics_add_transfer_stats(statm, xfer, master); spi_statistics_add_transfer_stats(stats, xfer, master); if (xfer->tx_buf || xfer->rx_buf) { reinit_completion(&master->xfer_completion); ret = master->transfer_one(master, msg->spi, xfer); /*部分省略......*/ if (ret > 0) { ret = 0; ms = 8LL * 1000LL * xfer->len; do_div(ms, xfer->speed_hz); ms += ms + 100; /* some tolerance */ if (ms > UINT_MAX) ms = UINT_MAX; ms = wait_for_completion_timeout(&master->xfer_completion, msecs_to_jiffies(ms)); } /*部分省略......*/ } else { if (xfer->len) dev_err(&msg->spi->dev, "Bufferless transfer has length %u\n", xfer->len); } trace_spi_transfer_stop(msg, xfer); if (msg->status != -EINPROGRESS) goto out; if (xfer->delay_usecs) udelay(xfer->delay_usecs); if (xfer->cs_change) { if (list_is_last(&xfer->transfer_list, &msg->transfers)) { keep_cs = true;//不用重新片选,继续下一个message的传输 } else { spi_set_cs(msg->spi, false); udelay(10); spi_set_cs(msg->spi, true); } } msg->actual_length += xfer->len; }out: if (ret != 0 || !keep_cs) spi_set_cs(msg->spi, false); if (msg->status == -EINPROGRESS) msg->status = ret;//所用spi_message传输完毕 if (msg->status && master->handle_err) master->handle_err(master, msg); spi_res_release(master, msg); spi_finalize_current_message(master); return ret;}
这里可以看出调用了master->transfer_one(master, msg->spi, xfer)以及spi_finalize_current_message(master);
master->transfer_one就是我们之前说的,spi_finalize_current_message函数里会调用mesg->complete,使得wait_for_completion不需要再阻塞等待了。
我们继续看下master->transfer_one函数:
static int spi_bitbang_transfer_one(struct spi_master *master, struct spi_device *spi, struct spi_transfer *transfer){ struct spi_bitbang *bitbang = spi_master_get_devdata(master); int status = 0; if (bitbang->setup_transfer) { status = bitbang->setup_transfer(spi, transfer); if (status < 0) goto out; } if (transfer->len) status = bitbang->txrx_bufs(spi, transfer); if (status == transfer->len) status = 0; else if (status >= 0) status = -EREMOTEIO;out: spi_finalize_current_transfer(master); return status;}
函数里面,一开始就调用bitbang->setup_transfer对SPI进行设置,然后就会调用bitbang->txrx_bufs了,记得之前说过的吗?
static int s3c24xx_spi_txrx(struct spi_device *spi, struct spi_transfer *t){ struct s3c24xx_spi *hw = to_hw(spi); hw->tx = t->tx_buf; hw->rx = t->rx_buf; hw->len = t->len; hw->count = 0; init_completion(&hw->done); hw->fiq_inuse = 0; if (s3c24xx_spi_usefiq(hw) && t->len >= 3) s3c24xx_spi_tryfiq(hw); /* send the first byte */ writeb(hw_txbyte(hw, 0), hw->regs + S3C2410_SPTDAT); wait_for_completion(&hw->done); return hw->count;}
这里就是SPI的传输了,看注释,这里虽然只发送第一字节,但是中断里会发送完其它的字节(并接收数据), 直到所有的数据发送完毕且所要接收的数据接收完毕(首要)才返回。
static irqreturn_t s3c24xx_spi_irq(int irq, void *dev){ struct s3c24xx_spi *hw = dev; unsigned int spsta = readb(hw->regs + S3C2410_SPSTA); unsigned int count = hw->count; if (spsta & S3C2410_SPSTA_DCOL) { dev_dbg(hw->dev, "data-collision\n");//检测冲突 complete(&hw->done); goto irq_done; } if (!(spsta & S3C2410_SPSTA_READY)) { dev_dbg(hw->dev, "spi not ready for tx?\n");//设备忙 complete(&hw->done); goto irq_done; } if (!s3c24xx_spi_usingfiq(hw)) { hw->count++; if (hw->rx) hw->rx[count] = readb(hw->regs + S3C2410_SPRDAT);//接收数据 count++; if (count < hw->len) writeb(hw_txbyte(hw, count), hw->regs + S3C2410_SPTDAT);//发送需要的数据或者发送0 else complete(&hw->done);//发送接收完毕 } else { hw->count = hw->len; hw->fiq_inuse = 0; if (hw->rx) hw->rx[hw->len-1] = readb(hw->regs + S3C2410_SPRDAT); complete(&hw->done); } irq_done: return IRQ_HANDLED;}
所以说,spi_sync函数是发起一个同步传输的阻塞API,它会通过master->transfer把spi_message结构挂在spi_master的queue字段下,然后启动专门为spi传输准备的内核工作线程spi_pump_messages,把该spi_message从队列中移除,然后调用master->prepare_transfer_hardware回调来让控制器驱动准备必要的硬件资源,最后 master->transfer_one_message来实际处理message的传输工作,然后等待传输的完成后调用mesg->complete,表明传输完成,以通知协议驱动程序准备下一帧数据,wait_for_completion不需要再等待了。
最后,还有其他的API传输函数供我们使用:
int spi_write_then_read(struct spi_device *spi, const void *txbuf, unsigned n_tx, void *rxbuf, unsigned n_rx); —- 先写后读。
static inline ssize_t spi_w8r8(struct spi_device *spi, u8 cmd) —- 写8位,然后读8位。
static inline ssize_t spi_w8r16(struct spi_device *spi, u8 cmd) —- 写8位,然后读16位。
下一章讲2440怎么移植使用SPI驱动:spi设备之RFID-rc522驱动
- 嵌入式Linux驱动笔记(十二)------通俗易懂式分析了解spi框架
- 嵌入式Linux驱动笔记(十)------通俗易懂式分析了解i2c框架
- spi驱动框架分析
- linux驱动基础系列--linux spi驱动框架分析
- Linux驱动修炼之道-SPI驱动框架源码分析
- Linux驱动修炼之道-SPI驱动框架源码分析
- Linux驱动修炼之道-SPI驱动框架源码分析
- 嵌入式linux之SPI驱动
- linux spi驱动分析
- linux spi驱动分析
- linux spi驱动分析
- linux spi驱动分析
- linux spi驱动分析
- linux spi驱动分析
- linux spi驱动分析
- LINUX SPI驱动笔记
- spi驱动框架源码分析
- SPI驱动框架源码分析
- HTML中对VALUE属性的理解
- POJ 1991 Turning in Homework 区间DP
- LocalDB的使用详解
- <统计学习方法>5 逻辑斯蒂回归与最大熵模型
- CvMat,Mat和IplImage之间的转化和拷贝
- 嵌入式Linux驱动笔记(十二)------通俗易懂式分析了解spi框架
- 多个数组间元素排列组合问题求解(Java实现) 标签: 递归排列组合循环
- python 深拷贝&浅拷贝
- HDU 1869 六度分离 floyd
- StringMVC面试题
- windows、ubuntu双系统下修改默认启动项
- Java SE8:Lambda快速入门
- 求最大子序列之和问题
- 神奇的口袋