写在前面

初次接触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资源。

最新文章

  1. htm常用标签总结
  2. Redis 的安装与使用(linux)
  3. JavaScript对象的chapterI
  4. android px dp sp
  5. HTML 标题
  6. 自定义获取html元素对象的7种方法。
  7. Android Studio SDK更新失败解决方法
  8. hdu Just a Hook
  9. 14行脚本配置Linux下一个Java环境变量
  10. Appium Server 源码分析之启动运行Express http服务器
  11. FZU Problem 2213 Common Tangents
  12. 自己封装的一个js方法用于获取显示的星期和日期时间
  13. 访问网站出现 Directory Listing Denied This Virtual Directory 
  14. java之jsp页面语法
  15. 关于std::thread
  16. Stream 与 byte[] 互转
  17. 安装sublime3
  18. 基于tensorflow2.0 使用tf.keras实现Fashion MNIST
  19. ASP.NET Identity系列02,在ASP.NET MVC中增删改查用户
  20. Rikka with Parenthesis II---hdu5831(括号匹配)

热门文章

  1. 弹出输入框后,将listview内容遮住,解决方案
  2. POI导出Word插入复选框
  3. 如何去掉jQWidgets中TreeGrid和Grid右下角的链接
  4. js模拟键盘事件
  5. Windows7下安装cpu版的Tensorflow
  6. Objective-c: 移除字符串中的指定字符
  7. TradeStation简介
  8. Link1123:转换到COFF期间失败:文件无效或损坏
  9. npm install mongoose错误解决
  10. Fedora 安装oracle11g 之最简洁方式