Linux设备驱动剖析之SPI(四)
781行之前没什么好说的,直接看783行,将work投入到工作队列里,然后就返回,在这里就可以回答之前为什么是异步的问题。以后在某个合适的时间里CPU会执行这个work指定的函数,这里是s3c64xx_spi_work函数,看它的定义:
static void s3c64xx_spi_work(struct work_struct *work)
{
struct s3c64xx_spi_driver_data *sdd = container_of(work,
struct s3c64xx_spi_driver_data, work);
unsigned long flags; /* Acquire DMA channels */
while (!acquire_dma(sdd))
msleep(); spin_lock_irqsave(&sdd->lock, flags); while (!list_empty(&sdd->queue)
&& !(sdd->state & SUSPND)) { struct spi_message *msg; msg = container_of(sdd->queue.next, struct spi_message, queue); list_del_init(&msg->queue); /* Set Xfer busy flag */
sdd->state |= SPIBUSY; spin_unlock_irqrestore(&sdd->lock, flags); handle_msg(sdd, msg); spin_lock_irqsave(&sdd->lock, flags); sdd->state &= ~SPIBUSY;
} spin_unlock_irqrestore(&sdd->lock, flags); /* Free DMA channels */
s3c2410_dma_free(sdd->tx_dmach, &s3c64xx_spi_dma_client);
s3c2410_dma_free(sdd->rx_dmach, &s3c64xx_spi_dma_client);
}
730行,申请DMA,关于DMA的就不说,一是我对DMA没什么了解,二是这里基本上用不到,后面就知道什么时候才会用到DMA。
735至754行,循环取出队列里的message并调用749行的handle_msg函数进行处理,handle_msg函数的定义如下:
static void handle_msg(struct s3c64xx_spi_driver_data *sdd,
struct spi_message *msg)
{
struct s3c64xx_spi_info *sci = sdd->cntrlr_info;
struct spi_device *spi = msg->spi;
struct s3c64xx_spi_csinfo *cs = spi->controller_data;
struct spi_transfer *xfer;
int status = , cs_toggle = ;
u32 speed;
u8 bpw; /* If Master's(controller) state differs from that needed by Slave */
if (sdd->cur_speed != spi->max_speed_hz
|| sdd->cur_mode != spi->mode
|| sdd->cur_bpw != spi->bits_per_word) {
sdd->cur_bpw = spi->bits_per_word;
sdd->cur_speed = spi->max_speed_hz;
sdd->cur_mode = spi->mode;
s3c64xx_spi_config(sdd);
} /* Map all the transfers if needed */
if (s3c64xx_spi_map_mssg(sdd, msg)) {
dev_err(&spi->dev,
"Xfer: Unable to map message buffers!\n");
status = -ENOMEM;
goto out;
} /* Configure feedback delay */
writel(cs->fb_delay & 0x3, sdd->regs + S3C64XX_SPI_FB_CLK); list_for_each_entry(xfer, &msg->transfers, transfer_list) { unsigned long flags;
int use_dma; INIT_COMPLETION(sdd->xfer_completion); /* Only BPW and Speed may change across transfers */
bpw = xfer->bits_per_word ? : spi->bits_per_word;
speed = xfer->speed_hz ? : spi->max_speed_hz; if (bpw != sdd->cur_bpw || speed != sdd->cur_speed) {
sdd->cur_bpw = bpw;
sdd->cur_speed = speed;
s3c64xx_spi_config(sdd);
} /* Polling method for xfers not bigger than FIFO capacity */ if (xfer->len <= ((sci->fifo_lvl_mask >> ) + ))
use_dma = ;
else
use_dma = ; spin_lock_irqsave(&sdd->lock, flags); /* Pending only which is to be done */
sdd->state &= ~RXBUSY;
sdd->state &= ~TXBUSY; enable_datapath(sdd, spi, xfer, use_dma); /* Slave Select */
enable_cs(sdd, spi); /* Start the signals */
S3C64XX_SPI_ACT(sdd); spin_unlock_irqrestore(&sdd->lock, flags); status = wait_for_xfer(sdd, xfer, use_dma); /* Quiese the signals */
S3C64XX_SPI_DEACT(sdd); if (status) {
dev_err(&spi->dev, "I/O Error: "
"rx-%d tx-%d res:rx-%c tx-%c len-%d\n",
xfer->rx_buf ? : , xfer->tx_buf ? : ,
(sdd->state & RXBUSY) ? 'f' : 'p',
(sdd->state & TXBUSY) ? 'f' : 'p',
xfer->len); if (use_dma) {
if (xfer->tx_buf != NULL
&& (sdd->state & TXBUSY))
s3c2410_dma_ctrl(sdd->tx_dmach,
S3C2410_DMAOP_FLUSH);
if (xfer->rx_buf != NULL
&& (sdd->state & RXBUSY))
s3c2410_dma_ctrl(sdd->rx_dmach,
S3C2410_DMAOP_FLUSH);
} goto out;
} if (xfer->delay_usecs)
udelay(xfer->delay_usecs); if (xfer->cs_change) {
/* Hint that the next mssg is gonna be
00000672 for the same device */
if (list_is_last(&xfer->transfer_list,
&msg->transfers))
cs_toggle = ;
else
disable_cs(sdd, spi);
} msg->actual_length += xfer->len; flush_fifo(sdd);
} out:
if (!cs_toggle || status)
disable_cs(sdd, spi);
else
sdd->tgl_spi = spi; s3c64xx_spi_unmap_mssg(sdd, msg); msg->status = status; if (msg->complete)
msg->complete(msg->context);
}
函数很长,580至587行,如果一路走来speed、bpw和mode的值与spi设备的不一致就调用s3c64xx_spi_config函数重新配置,s3c64xx_spi_config函数里面就是对SPI寄存器进行设置的。
590至595行,关于DMA映射的,略过。
598行,设置feedback寄存器。
600行,遍历每一个transfer。605行,又初始化一个完成量,注意这里与之前的那个完成量是不一样的,这里的完成量只有使用DMA传输时才会用得到,接下来很快就可以看到。
608至615行,也是一些关于设置值的检查。
619至622行,只有发送或者接收的数据长度大于fifo的深度(这里是64个字节)设置use_dma为1,也即使用DMA进行传输,否则不使用DMA。
630行,enable_datapath函数的定义为:
static void enable_datapath(struct s3c64xx_spi_driver_data *sdd,
struct spi_device *spi,
struct spi_transfer *xfer, int dma_mode)
{
struct s3c64xx_spi_info *sci = sdd->cntrlr_info;
void __iomem *regs = sdd->regs;
u32 modecfg, chcfg; modecfg = readl(regs + S3C64XX_SPI_MODE_CFG);
modecfg&=~(S3C64XX_SPI_MODE_TXDMA_ON|S3C64XX_SPI_MODE_RXDMA_ON); chcfg = readl(regs + S3C64XX_SPI_CH_CFG);
chcfg &= ~S3C64XX_SPI_CH_TXCH_ON; if (dma_mode) {
chcfg &= ~S3C64XX_SPI_CH_RXCH_ON;
} else {
/* Always shift in data in FIFO, even if xfer is Tx only,
00000250 * this helps setting PCKT_CNT value for generating clocks
00000251 * as exactly needed.
00000252 */
chcfg |= S3C64XX_SPI_CH_RXCH_ON;
writel(((xfer->len * / sdd->cur_bpw) & 0xffff)
| S3C64XX_SPI_PACKET_CNT_EN,
regs + S3C64XX_SPI_PACKET_CNT);
} if (xfer->tx_buf != NULL) {
sdd->state |= TXBUSY;
chcfg |= S3C64XX_SPI_CH_TXCH_ON;
if (dma_mode) {
modecfg |= S3C64XX_SPI_MODE_TXDMA_ON;
s3c2410_dma_config(sdd->tx_dmach, );
s3c2410_dma_enqueue(sdd->tx_dmach, (void *)sdd,
xfer->tx_dma, xfer->len);
s3c2410_dma_ctrl(sdd->tx_dmach, S3C2410_DMAOP_START);
} else {
unsigned char *buf = (unsigned char *) xfer->tx_buf;
int i = ;
while (i < xfer->len)
writeb(buf[i++], regs + S3C64XX_SPI_TX_DATA);
}
} if (xfer->rx_buf != NULL) {
sdd->state |= RXBUSY; if (sci->high_speed && sdd->cur_speed >= 30000000UL
&& !(sdd->cur_mode & SPI_CPHA))
chcfg |= S3C64XX_SPI_CH_HS_EN; if (dma_mode) {
modecfg |= S3C64XX_SPI_MODE_RXDMA_ON;
chcfg |= S3C64XX_SPI_CH_RXCH_ON;
writel(((xfer->len * / sdd->cur_bpw) & 0xffff)
| S3C64XX_SPI_PACKET_CNT_EN,
regs + S3C64XX_SPI_PACKET_CNT);
s3c2410_dma_config(sdd->rx_dmach, );
s3c2410_dma_enqueue(sdd->rx_dmach, (void *)sdd,
xfer->rx_dma, xfer->len);
s3c2410_dma_ctrl(sdd->rx_dmach, S3C2410_DMAOP_START);
}
} writel(modecfg, regs + S3C64XX_SPI_MODE_CFG);
writel(chcfg, regs + S3C64XX_SPI_CH_CFG);
}
240至244行,读取模式配置和通道配置寄存器。
246至257行,根据是否采用DMA模式设置接收计数寄存器。
259行,很早就为tx_buf分配内存,因此条件成立。因为不考虑DMA模式,因此略过262至268行。
269至272行,循环将发送数据写入到发送寄存器。
276至294行,由于rx_buf为NULL,因此直接略过277至294行。
296、297行,将之前的值写入到寄存器中。
回到handle_msg函数,633行,选中从设备。636行,设置寄存器,开始数据传输。
640行,wait_for_xfer函数的定义:
static int wait_for_xfer(struct s3c64xx_spi_driver_data *sdd,
struct spi_transfer *xfer, int dma_mode)
{
struct s3c64xx_spi_info *sci = sdd->cntrlr_info;
void __iomem *regs = sdd->regs;
unsigned long val;
int ms; /* millisecs to xfer 'len' bytes @ 'cur_speed' */
ms = xfer->len * * / sdd->cur_speed;
ms += ; /* some tolerance */ if (dma_mode) {
val = msecs_to_jiffies(ms) + ;
val = wait_for_completion_timeout(&sdd->xfer_completion, val);
} else {
u32 status;
val = msecs_to_loops(ms);
do {
status = readl(regs + S3C64XX_SPI_STATUS);
} while (RX_FIFO_LVL(status, sci) < xfer->len && --val);
} if (!val)
return -EIO; if (dma_mode) {
u32 status; /*
00000349 * DmaTx returns after simply writing data in the FIFO,
00000350 * w/o waiting for real transmission on the bus to finish.
00000351 * DmaRx returns only after Dma read data from FIFO which
00000352 * needs bus transmission to finish, so we don't worry if
00000353 * Xfer involved Rx(with or without Tx).
00000354 */
if (xfer->rx_buf == NULL) {
val = msecs_to_loops();
status = readl(regs + S3C64XX_SPI_STATUS);
while ((TX_FIFO_LVL(status, sci)
|| !S3C64XX_SPI_ST_TX_DONE(status, sci))
&& --val) {
cpu_relax();
status = readl(regs + S3C64XX_SPI_STATUS);
} if (!val)
return -EIO;
}
} else {
unsigned char *buf;
int i; /* If it was only Tx */
if (xfer->rx_buf == NULL) {
sdd->state &= ~TXBUSY;
return ;
} i = ;
buf = xfer->rx_buf;
while (i < xfer->len)
buf[i++] = readb(regs + S3C64XX_SPI_RX_DATA); sdd->state &= ~RXBUSY;
} return ;
}
328行,根据发送速率计算需要等待的时间。331至334行,与DMA相关的,略过。
335至339行,不断地读状态寄存器,如果接收到的数据长度等于发送数据的长度或超时则退出循环。
342、343行,如果是超时退出循环的,则返回出错。
345至368行,DMA相关的,略过。
369至383行,如果只是发送数据,则直接返回0。否则从接收寄存器里将接收到的数据读出来。
回到handle_msg函数,643行,停止传输。645至665行,如果之前wait_for_xfer函数返回大于0的值,则表示出错,这里就打印一些信息。
667、668行,之前如果有设置延时的话这里就延时。
670至678行,是否需要每个transfer完成都改变片选信号。
680行,累加所有transfer成功发送的数据。
682行,清发送和接收寄存器。
691行,取消DMA映射。
693行,记录状态信息。
695、696行,唤醒之前等待的完成量。
到这里,已经说了整个write过程,不容易啊!。其他的read/ioctl过程是大同小异的。
总结
spidev.c是一个通用的SPI驱动,因此它不处理任何有关具体驱动的逻辑,这就需要在用户空间来完成具体的逻辑。其实这符合了Android驱动的思想,这也是Android HAL层存在的目的:内核驱动只完成硬件操作,具体逻辑放在HAL层,这样就有利于保护厂家、开发者的知识产权。
在用户空间使用ioctl就可以完成write、read操作。
Linux设备驱动剖析之SPI(四)的更多相关文章
- Linux设备驱动剖析之SPI(一)
写在前面 初次接触SPI是因为几年前玩单片机的时候,由于普通的51单片机没有SPI控制器,所以只好用IO口去模拟.最近一次接触SPI是大三时参加的校内选拔赛,当时需要用2440去控制nrf24L01, ...
- Linux设备驱动剖析之SPI(三)
572至574行,分配内存,注意对象的类型是struct spidev_data,看下它在drivers/spi/spidev.c中的定义: struct spidev_data { dev_t de ...
- Linux设备驱动剖析之SPI(二)
957至962行,一个SPI控制器用一个master来描述.这里使用SPI核心的spi_alloc_master函数请求分配master.它在drivers/spi/spi.c文件中定义: struc ...
- linux设备驱动归纳总结(四):5.多处理器下的竞态和并发【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-67673.html linux设备驱动归纳总结(四):5.多处理器下的竞态和并发 xxxxxxxxxx ...
- linux设备驱动归纳总结(四):4.单处理器下的竞态和并发【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-67005.html linux设备驱动归纳总结(四):4.单处理器下的竞态和并发 xxxxxxxxxx ...
- linux设备驱动归纳总结(四):3.抢占和上下文切换【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-65711.html linux设备驱动归纳总结(四):3.抢占和上下文切换 xxxxxxxxxxxxx ...
- linux设备驱动归纳总结(四):2.进程调度的相关概念【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-65555.html linux设备驱动归纳总结(四):2.进程调度的相关概念 xxxxxxxxxxxx ...
- linux设备驱动归纳总结(四):1.进程管理的相关概念【转】
本文转载自;http://blog.chinaunix.net/uid-25014876-id-64866.html linux设备驱动归纳总结(四):1.进程管理的相关概念 xxxxxxxxxxxx ...
- 【Linux开发】linux设备驱动归纳总结(四):5.多处理器下的竞态和并发
linux设备驱动归纳总结(四):5.多处理器下的竞态和并发 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
随机推荐
- 使用3ds Max制作简单卧室
一.介绍 学习目标:熟练使用“标准基本体”和“扩展基本体”内的按钮来创建对象. 软件环境:3ds Max2015 二.实验步骤 1,启动3ds Max,使用“长方体”工具在场景中创建一个长方体作为空间 ...
- Asp.Net MVC :路由器
特性路由 特性路由是对现有路由系统的扩展,提供了一种针对某个具体Controller类型或Action方法的路由注册方式.从而可以对路由规则进行细粒度的设计. 特性路由(Attribute Route ...
- [mysql] linux下使用yum安装mysql
From: http://www.2cto.com/database/201207/141878.html linux下使用yum安装mysql 1.安装 查看有没有安装过: ...
- Mongodb学习笔记(1)--入门
文档 多个键及关联的值有序的放置在一起就是文档,如"greeting":"Hello World!" 特点 文档中键值对是有序的 除了字符串还可以是其他类型:& ...
- 调用 COM 对象
调用 COM 对象 大多数 Windows 程序猿都熟悉组件对象模型(Component Object Model,COM).在某程度上..NET 框架 就是为了替换 COM,可是.系统仍然保留了这个 ...
- Linux服务器svn与项目同步
命令:svn checkout svn://192.168.67.131/trunk/w1
- iOS多版本多设备适配的问题
好吧,能找到这文章的,一般是接到了如下需求: 我是从raywenderlich抽了点内容出来做日记,另外,本文说的不是布局的适配,而是因为ios的升级带来的各版本代码上的不兼容. Deploymen ...
- JavaBridge
有的时候我们需要在PHP里调用JAVA平台封装好的jar包里的class类和方法 一般的做法是采用php-java-bridge做桥接 1.实现原理: 先打开java的一个监听端口,php调用java ...
- Android : Your APK does not seem to be designed for tablets.
1. 解决办法: Add these config in AndroidManifest.xml <supports-screens android:smallScreens="tru ...
- 好用的图片缩放JS
<!DOCTYPE HTML> <meta charset="UTF-8"> <head> <script src="jquer ...