Linux设备驱动剖析之SPI(一)
写在前面
初次接触SPI是因为几年前玩单片机的时候,由于普通的51单片机没有SPI控制器,所以只好用IO口去模拟。最近一次接触SPI是大三时参加的校内选拔赛,当时需要用2440去控制nrf24L01,从而实现数据的无线传输。nrf24L01是一种典型的SPI接口的2.4GHz无线收发器,当时搞了很久,由于时间比较紧,而且当时根本不熟悉Linux的SPI子系统,最后虽然采用IO口模拟SPI的方式勉强实现了,但是这根本就不符合Linux驱动的编程规范,或者说是在破坏Linux、污染kernel。
根据我个人所知道的,Linux SPI一直是处于被“忽略”的角色,市场上大部分板子在板级文件里都没有关于SPI的相关代码,而大部分讲驱动的书籍也没有专门的一章来讲述关于Linux SPI方面的内容,与IIC相比,SPI就是一个不被重视的“家伙”,为什么?我也不知道。为了帮SPI抱打不平,我决定基于Linux-2.6.36,说说Linux中SPI子系统。
先给出Linux SPI子系统的体系结构图:
SPI子系统体系结构
下面开始分析SPI子系统。
Linux中SPI子系统的初始化是从drivers/spi/spi.c文件中的spi_init函数开始的,看看它的定义:
static int __init spi_init(void)
{
int status; buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);
if (!buf) {
status = -ENOMEM;
goto err0;
} status = bus_register(&spi_bus_type);
if (status < )
goto err1; status = class_register(&spi_master_class);
if (status < )
goto err2;
return ; err2:
bus_unregister(&spi_bus_type);
err1:
kfree(buf);
buf = NULL;
err0:
return status;
}
1029行,分配spi buf内存,其中buf和SPI_BUFSIZ都在spi.c文件中定义:
#define SPI_BUFSIZ max(32,SMP_CACHE_BYTES) static u8 *buf;
1035行,注册spi总线,同样是在spi.c文件中:
struct bus_type spi_bus_type = {
.name = "spi",
.dev_attrs = spi_dev_attrs,
.match = spi_match_device,
.uevent = spi_uevent,
.suspend = spi_suspend,
.resume = spi_resume,
};
146行,总线的名字就叫spi。
148行,比较重要的,spi_match_device是spi总线上匹配设备和设备驱动的函数,同样是在spi.c文件中:
static int spi_match_device(struct device *dev, struct device_driver *drv)
{
const struct spi_device *spi = to_spi_device(dev);
const struct spi_driver *sdrv = to_spi_driver(drv); /* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
return ; if (sdrv->id_table)
return !!spi_match_id(sdrv->id_table, spi); return strcmp(spi->modalias, drv->name) == ;
}
写过驱动的都应该知道platform总线有struct platform_device和struct platform_driver,到了SPI总线,当然也有对应的struct spi_device和struct spi_driver,如87、88行所示。87行,关于struct spi_device的定义是在include/linux/spi/spi.h中:
struct spi_device {
struct device dev;
struct spi_master *master;
u32 max_speed_hz;
u8 chip_select;
u8 mode;
#define SPI_CPHA 0x01 /* clock phase */
#define SPI_CPOL 0x02 /* clock polarity */
#define SPI_MODE_0 (0|0) /* (original MicroWire) */
#define SPI_MODE_1 (0|SPI_CPHA)
#define SPI_MODE_2 (SPI_CPOL|0)
#define SPI_MODE_3 (SPI_CPOL|SPI_CPHA)
#define SPI_CS_HIGH 0x04 /* chipselect active high? */
#define SPI_LSB_FIRST 0x08 /* per-word bits-on-wire */
#define SPI_3WIRE 0x10 /* SI/SO signals shared */
#define SPI_LOOP 0x20 /* loopback mode */
#define SPI_NO_CS 0x40 /* 1 dev/bus, no chipselect */
#define SPI_READY 0x80 /* slave pulls low to pause */
u8 bits_per_word;
int irq;
void *controller_state;
void *controller_data;
char modalias[SPI_NAME_SIZE]; /*
00000094 * likely need more hooks for more protocol options affecting how
00000095 * the controller talks to each chip, like:
00000096 * - memory packing (12 bit samples into low bits, others zeroed)
00000097 * - priority
00000098 * - drop chipselect after each word
00000099 * - chipselect delays
00000100 * - ...
00000101 */
};
70行,dev,嵌入到设备模型中用的。
71行,master,spi设备的更高层描述,每一个spi控制器就对应一个master,一个spi设备必须对应一个master,master下可以有多个spi设备。
72,73行没什么好说的,从变量的名字就可以明白。
74行,mode,针对时钟相位CPHA(0或1)和时钟极性CPOL(0或1)的不同组合,将spi分成四种模式,就是77至80行这四种。CPOL表示当空闲时(没有进行数据传输)时钟信号的电平,CPOL=0表示低电平,CPOL=1表示高电平。每个时钟周期都有两次电平的跳变,上升沿和下降沿,CPHA就表示在每个时钟周期里是第一个边沿采样数据还是第二个边沿采样数据,具体第一个边沿或者第二个边沿是上升沿还是下降沿则由CPOL决定。看下面这张图就明白了。蓝色箭头表示对数据进行采样。
87行,如果传输是以字节为单位的话就设置为8,相应地,如果是以2个字节为单位则设置为16。
88行,中断号。89行,没有使用,在后面会看到这个值会被设置为NULL。90行,后面讲到具体的控制器再说。91行,很重要,一般来说设备与驱动能否匹配起来就要看它,注意,这里只是说一般,因为还有另外两种匹配的方法,不过大部分设备和驱动能否匹配起来都是根据名字是否相等,当然像USB驱动就不是根据名字来匹配的,而是根据与id table。struct spi_driver的定义也是在include/linux/spi/spi.h:
struct spi_driver {
const struct spi_device_id *id_table;
int (*probe)(struct spi_device *spi);
int (*remove)(struct spi_device *spi);
void (*shutdown)(struct spi_device *spi);
int (*suspend)(struct spi_device *spi, pm_message_t mesg);
int (*resume)(struct spi_device *spi);
struct device_driver driver;
};
182行,driver就是在设备模型中使用的那个device_driver,其他都是一些函数指针的定义,挺熟悉的了,就不多说了。
回到spi_match_device函数,91行和95行就是设备和驱动匹配的另外两种方法,因为后文要讲的spi驱动使用的是第三种方法,因此这里就不讨论这两种方法了。97行,根据设备名和驱动名是否相等进行匹配,相等则返回1,表示匹配成功,此时驱动里的probe函数将会被调用,这也是我们最希望看到的,返回0则表示匹配失败。
我们知道,对于具体的平台,nand、iic、frame buffer等这些都是平台设备,spi当然也一样是平台设备,对于平台设备,大部分情况下是先注册设备再注册驱动。因此下面就以tiny6410为具体平台,按照这种先后顺序来讲述。
S3c6410有两个SPI控制器,以SPI0为例就可以了。首先看s3c6410中关于SPI0控制器的描述,在arch/arm/mach-s3c64xx/dev-spi.c文件中:
struct platform_device s3c64xx_device_spi0 = {
.name = "s3c64xx-spi",
.id = ,
.num_resources = ARRAY_SIZE(s3c64xx_spi0_resource),
.resource = s3c64xx_spi0_resource,
.dev = {
.dma_mask = &spi_dmamask,
.coherent_dma_mask = DMA_BIT_MASK(),
.platform_data = &s3c64xx_spi0_pdata,
},
};
102行,驱动能否与这个设备匹配,就看这个名字了,因此对应的驱动名字必须与之一样。103行,SPI控制器的ID,SPI0控制器就为0,SPI1控制器就为1。
104和105行是关于IO口资源、DMA资源和中断资源的。107、108行是关于DMA的,不说了。109行,给驱动用的,在驱动那里再说。在板初始化函数mini6410_machine_init里调用platform_add_devices函数就可以将SPI0设备注册到platform总线上。
S3c6410的SPI控制器驱动在drivers/spi/spi_s3c64xx.c文件里。初始化函数:
static int __init s3c64xx_spi_init(void)
{
return platform_driver_probe(&s3c64xx_spi_driver, s3c64xx_spi_probe);
}
1185行,s3c64xx_spi_driver是struct platform_driver的实例,也在spi_s3c64xx.c文件中定义:
static struct platform_driver s3c64xx_spi_driver = {
.driver = {
.name = "s3c64xx-spi",
.owner = THIS_MODULE,
},
.remove = s3c64xx_spi_remove,
.suspend = s3c64xx_spi_suspend,
.resume = s3c64xx_spi_resume,
};
1174行,看到了没?和之前在s3c64xx_device_spi0里定义的名字是一样的,这样它们就可以匹配起来,1185行的s3c64xx_spi_probe驱动探测函数就会被调用,看下它的定义:
static int __init s3c64xx_spi_probe(struct platform_device *pdev)
{
struct resource *mem_res, *dmatx_res, *dmarx_res;
struct s3c64xx_spi_driver_data *sdd;
struct s3c64xx_spi_info *sci;
struct spi_master *master;
int ret; if (pdev->id < ) {
dev_err(&pdev->dev,
"Invalid platform device id-%d\n", pdev->id);
return -ENODEV;
} if (pdev->dev.platform_data == NULL) {
dev_err(&pdev->dev, "platform_data missing!\n");
return -ENODEV;
} sci = pdev->dev.platform_data;
if (!sci->src_clk_name) {
dev_err(&pdev->dev,
"Board init must call s3c64xx_spi_set_info()\n");
return -EINVAL;
} /* Check for availability of necessary resource */ dmatx_res = platform_get_resource(pdev, IORESOURCE_DMA, );
if (dmatx_res == NULL) {
dev_err(&pdev->dev, "Unable to get SPI-Tx dma resource\n");
return -ENXIO;
} dmarx_res = platform_get_resource(pdev, IORESOURCE_DMA, );
if (dmarx_res == NULL) {
dev_err(&pdev->dev, "Unable to get SPI-Rx dma resource\n");
return -ENXIO;
} mem_res = platform_get_resource(pdev, IORESOURCE_MEM, );
if (mem_res == NULL) {
dev_err(&pdev->dev, "Unable to get SPI MEM resource\n");
return -ENXIO;
} master = spi_alloc_master(&pdev->dev,
sizeof(struct s3c64xx_spi_driver_data));
if (master == NULL) {
dev_err(&pdev->dev, "Unable to allocate SPI Master\n");
return -ENOMEM;
} platform_set_drvdata(pdev, master); sdd = spi_master_get_devdata(master);
sdd->master = master;
sdd->cntrlr_info = sci;
sdd->pdev = pdev;
sdd->sfr_start = mem_res->start;
sdd->tx_dmach = dmatx_res->start;
sdd->rx_dmach = dmarx_res->start; sdd->cur_bpw = ; master->bus_num = pdev->id;
master->setup = s3c64xx_spi_setup;
master->transfer = s3c64xx_spi_transfer;
master->num_chipselect = sci->num_cs;
master->dma_alignment = ;
/* the spi->mode bits understood by this driver: */ master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; if (request_mem_region(mem_res->start,
resource_size(mem_res), pdev->name) == NULL) {
dev_err(&pdev->dev, "Req mem region failed\n");
ret = -ENXIO;
goto err0;
} sdd->regs = ioremap(mem_res->start, resource_size(mem_res));
if (sdd->regs == NULL) {
dev_err(&pdev->dev, "Unable to remap IO\n");
ret = -ENXIO;
goto err1;
} if (sci->cfg_gpio == NULL || sci->cfg_gpio(pdev)) {
dev_err(&pdev->dev, "Unable to config gpio\n");
ret = -EBUSY;
goto err2;
} /* Setup clocks */
sdd->clk = clk_get(&pdev->dev, "spi");
if (IS_ERR(sdd->clk)) {
dev_err(&pdev->dev, "Unable to acquire clock 'spi'\n");
ret = PTR_ERR(sdd->clk);
goto err3;
} if (clk_enable(sdd->clk)) {
dev_err(&pdev->dev, "Couldn't enable clock 'spi'\n");
ret = -EBUSY;
goto err4;
} sdd->src_clk = clk_get(&pdev->dev, sci->src_clk_name);
if (IS_ERR(sdd->src_clk)) {
dev_err(&pdev->dev,
"Unable to acquire clock '%s'\n", sci->src_clk_name);
ret = PTR_ERR(sdd->src_clk);
goto err5;
} if (clk_enable(sdd->src_clk)) {
dev_err(&pdev->dev, "Couldn't enable clock '%s'\n",
sci->src_clk_name);
ret = -EBUSY;
goto err6;
} sdd->workqueue = create_singlethread_workqueue(
dev_name(master->dev.parent));
if (sdd->workqueue == NULL) {
dev_err(&pdev->dev, "Unable to create workqueue\n");
ret = -ENOMEM;
goto err7;
} /* Setup Deufult Mode */
s3c64xx_spi_hwinit(sdd, pdev->id); spin_lock_init(&sdd->lock);
init_completion(&sdd->xfer_completion);
INIT_WORK(&sdd->work, s3c64xx_spi_work);
INIT_LIST_HEAD(&sdd->queue); if (spi_register_master(master)) {
dev_err(&pdev->dev, "cannot register SPI master\n");
ret = -EBUSY;
goto err8;
} dev_dbg(&pdev->dev, "Samsung SoC SPI Driver loaded for Bus SPI-%d "
"with %d Slaves attached\n",
pdev->id, master->num_chipselect);
dev_dbg(&pdev->dev, "\tIOmem=[0x%x-0x%x]\tDMA=[Rx-%d, Tx-%d]\n",
mem_res->end, mem_res->start,
sdd->rx_dmach, sdd->tx_dmach); return ; err8:
destroy_workqueue(sdd->workqueue);
err7:
clk_disable(sdd->src_clk);
err6:
clk_put(sdd->src_clk);
err5:
clk_disable(sdd->clk);
err4:
clk_put(sdd->clk);
err3:
err2:
iounmap((void *) sdd->regs);
err1:
release_mem_region(mem_res->start, resource_size(mem_res));
err0:
platform_set_drvdata(pdev, NULL);
spi_master_put(master); return ret;
}
函数很长,但做的东西却很简单。919至923行,SPI控制器的ID是从0开始的,小于0的话,没门,出错。
925至928行,必须要有platform_data,否则出错。930行,如果platform_data存在的话就把它取出来。
931至935行,如果src_clk_name为0,则表示在板初始化函数里没有调用s3c64xx_spi_set_info函数。
939至955行,获取在设备里定义的IO口和DMA资源。
Linux设备驱动剖析之SPI(一)的更多相关文章
- 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设备驱动剖析之SPI(四)
781行之前没什么好说的,直接看783行,将work投入到工作队列里,然后就返回,在这里就可以回答之前为什么是异步的问题.以后在某个合适的时间里CPU会执行这个work指定的函数,这里是s3c64xx ...
- Linux设备驱动剖析之IIC(一)
写在前面 由于IIC总线只需要两根线就可以完成读写操作,而且通信协议简单,一条总线上可以挂载多个设备,因此被广泛使用.但是IIC总线有一个缺点,就是传输速率比较低.本文基于Linux-2.6.36版本 ...
- Linux设备驱动剖析之Input(四)
static void input_pass_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) ...
- Linux设备驱动剖析之Input(一)
前言 以前在移植Qt到开发板上时只知道在配置文件中需要指定触摸屏的设备文件/dev/input/event0,仅此而已.直到一年半前突然想到用红外遥控器控制Tiny6410开发板上的Android系统 ...
- Linux设备驱动剖析之Input(二)
分别是总线类型.厂商号.产品号和版本号. 1156行,evbit,设备支持的事件类型的位图,每一位代表一种事件,比如EV_KEY.EV_REL事件等等.BITS_TO_LONGS(nr)是一个宏,假设 ...
- Linux设备驱动剖析之IIC(二)
953行,适配器的编号大于MAX_ID_MASK是不行的,MAX_ID_MASK是一个宏,展开后的值为61. 957至968行,关于管理小整形ID数的,没怎么了解,略过. 974行,调用i2c_reg ...
- Linux设备驱动剖析之Input(三)
/* get current state of buttons */ ; i < pdata->nbuttons; i++) gpio_keys_report_event(&dda ...
随机推荐
- Java如何获取正在运行的线程的Id?
在Java编程中,如何获取正在运行的线程的Id? 以下示例演示如何使用getThreadId()方法获取正在运行的线程的Id. package com.yiibai; public class IdT ...
- e814. 创建一个可监听选择状态的菜单项
A menu item can receive notification of selection changes by overriding its menuSelectionChanged() m ...
- ViewBag和ViewDate以及TempDate的区别
简单的说,就是 ViewBag 和 ViewData 是数据共享的(他们都是共享 ViewData 的数据),ViewBag 实际就是对 ViewData的一个操作的封装. 区别 : View ...
- Linux 查看操作系统版本
在下载软件的时候,需要知道操作系统对应的版本,通过 cat /etc/issue 可以查看对应信息 运行的结果如下: CentOS release 6.7 (Final) Kernel \r on a ...
- ASP.NET MVC Castle Windsor 教程
一.[转]ASP.NET MVC中使用Castle Windsor 二.[转]Castle Windsor之组件注册 平常用Inject比较多,今天接触到了Castle Windsor.本篇就来体验其 ...
- HTML5 3D爱心动画及其制作过程
之前有看到过很多基于HTML5或者CSS3制作的爱心动画,不过基本上都是2D平面的,今天在国外的网站上看到一个基于HTML5 3D的爱心动画,对于HTML5爱好者来说,不免兴奋了一把.下面将分享一下这 ...
- 删除mac系统win10启动选择项
打开终端输入:diskutil list找到EFI这个分区,挂载EFI分区diskutil mount /dev/disk0s1 回到Finder 删除除apple之外的两个文件夹就可以了(删除win ...
- Java并发包学习一 ThreadFactory介绍
ThreadFactory翻译过来是线程工厂,顾名思义,就是用来创建线程的,它用到了工厂模式的思想.它通常和线程池一起使用,主要用来控制创建新线程时的一些行为,比如设置线程的优先级,名字等等.它是一个 ...
- 常用的网络上的Webservice接口
商业和贸易: 1.股票行情数据 WEB 服务(支持香港.深圳.上海基金.债券和股票:支持多股票同时查询) Endpoint: http://webservice.webxml.com.cn/WebSe ...
- linux系统负载
系统负载System Load:系统CPU繁忙程度的度量,即有多少进程在等待被CPU调度 平均负载(Load Average):一段时间内系统的平均负载,这个一段时间一般取1分钟.5分钟.15分钟 查 ...