linux-3.0.1下spi设备mx25l的驱动(基于OK6410)

总述

学linux也快有一年了,这半年断断续续,忙着杂七杂八的事情,一直没全身心的投入的学。作为一个初学者对复杂而博大精深的linux有太多的话要吐槽:linux涉及的东西太多,即使写一个很简单的驱动也要涉及很多知识。看资料时,一会看看这块,一会又看看另一块,此时又忘了前几天看的那块是什么了,总有种按下葫芦起来瓢的感觉……。某某Jim Collins曾经说过:"if you have more than three priorities, then you don't have any." 所以经过一段时间的积累,我决定不能再这样漂浮在表面了,得自己亲自动手写个完整的驱动。就先从spi驱动下手吧,正好以前也用过具有spi接口的串行flash mx25l3205d。

SPI驱动分为两类:

控制器驱动:它们通常内嵌于片上系统处理器,通常既支持主设备,又支持从设备。这些驱动涉及硬件寄存器,可能使用DMA。或它们使用GPIO引脚成为PIO bitbangers。这部分通常会由特定的开发板提供商提供,不用自己写。

协议驱动:它们通过控制器驱动,以SPI连接的方式在主从设备之间传递信息。这部分涉及具体的SPI从设备,通常需要自己编写。

[参考]: Linux社区 作者:cskywit链接:http://www.linuxidc.com/Linux/2011-04/35262.htm

那么特定的目标板如何让Linux 操控SPI设备?下面以自己编写的MX25LxxxxD系列串行flash设备驱动为例,Linux内核版本为3.0.1,开发板为飞凌的OK6410。本文不涉及控制器及spi总线分析。

linux下让硬件跑起来通常需要注册设备和驱动两部分,设备提供硬件描述,驱动控制硬件工作流程。最简单的方法就是利用系统自带的spidev.c的驱动,只要将spi设备的名字改为:spidev,然后选上spidev设备支持,系统启动后会在/dev/下出现spidev0.0设备,利用/document/下的spidev_test.c测试就可以打开并操作设备,不过设备的具体操作步骤就要在应用程序中编写,驱动简单但应用程序复杂多了(看来世界的矛盾性无处不在,想偷懒还是不行的)。

设备注册篇

其中设备信息在:/arch/arm/mach-s3c64xx/mach-smdk6410.c中,

static structspi_board_info __initdata forlinx6410_mx25l_info[] = {

{

.modalias ="mx25l",//"spidev",    设备名,bus就是用此来match,非常关键的哦

.platform_data = &mx25l_info,//&spidev_info,

.irq = IRQ_EINT(16),

.max_speed_hz = 10*1000*1000,

.bus_num =0,     //总线号

.chip_select = 0,//片选号

.mode = SPI_MODE_0,

.controller_data=&s3c64xx_spi0_csinfo,

},

};

static structmx25l_data mx25l_info = {//spidev_info = {

.oscillator_frequency = 8000000,

.board_specific_setup =mx25l_ioSetup,

.transceiver_enable = NULL,

.power_enable = NULL,

};

static structs3c64xx_spi_csinfos3c64xx_spi0_csinfo = {

.fb_delay=0x3,

.line=S3C64XX_GPC(3),

.set_level=cs_set_level,

};

static intmx25l_ioSetup(struct spi_device *spi)

{

printk(KERN_INFO "mx25l: setupgpio pins CS and External Int\n");

s3c_gpio_setpull(S3C64XX_GPC(3), S3C_GPIO_PULL_NONE);//Manual chip select pin asused in 6410_set_cs

s3c_gpio_cfgpin(S3C64XX_GPC(3), S3C_GPIO_OUTPUT);// Manual chip select pin as usedin 6410_set_cs

return 0;

}

这里我们做修改,其中SPI0的片选CS为GPC3脚,并且没有用到中断。对于其他的spi协议里的参数,大家用过spi的肯定都知道,就不再介绍了。

在系统: include/linux/spi/下加入mx25l.h,加入定义struct mx25l_data为mx25l_info使用。

struct mx25l_data {

unsigned long oscillator_frequency;

unsigned long irq_flags;

int (*board_specific_setup)(structspi_device *spi);

int (*transceiver_enable)(int enable);

int (*power_enable) (int enable);

};

在文件头加入:#include <linux/spi/mx25l.h>,路径要对应好。

至此spi设备已全部注册完成,在系统启动的过程中会扫描设备信息加入到系统的设备链表中,默默地等待与match的驱动相连。

加入设备信息到系统设备链表的程序如下:

static void __initsmdk6410_machine_init(void)

{

……

spi_register_board_info(forlinx6410_mx25l_info,ARRAY_SIZE(forlinx6410_mx25l_info));

}

其中还会匹配设备与主控制器的总线号,如果匹配成功会spi_new_device(master, bi),建立主控器的设备,等待主控制器的驱动(主控制器也是遵循这个流程,先注册设备,然后注册驱动,其中主控制的设备由struct spi_master描述,master还带有一个中重要的函数int (*transfer) (struct spi_device *spi, struct spi_message*mesg),然后transfer把要传输的内容放到一个队列里,最后调用一种类似底半部的机制进行真正的传输。设备程序可以调用transfer函数将spi_message交给spi总线驱动,总线驱动再将message传到底半部排队,实现串行化传输)。

驱动注册篇

这里mx25lxxxxd和sst25l相似,都是SPI串行flash,用到了mtd设备模型。

一般的设备都是利用platform注册时,用platform_driver做驱动,而spi作为单独的一种总线形式,为自己定义了spi_driver,而这两个却又相同,真让人有种乱花渐欲迷人眼的感觉。

struct spi_driver {

const struct spi_device_id *id_table;

int                        (*probe)(structspi_device *spi);

int                        (*remove)(structspi_device *spi);

void                     (*shutdown)(structspi_device *spi);

int                        (*suspend)(structspi_device *spi, pm_message_t mesg);

int                        (*resume)(structspi_device *spi);

struct device_driver     driver;

};

我们可以与platform_driver对比下:

struct platform_driver{

int (*probe)(struct platform_device *);

int (*remove)(struct platform_device*);

void (*shutdown)(struct platform_device*);

int (*suspend)(struct platform_device*, pm_message_t state);

int (*resume)(struct platform_device*);

struct device_driver driver;

const struct platform_device_id*id_table;

};

可以发现内容完全相同,只是调整了成员顺序,并且各个成员函数的传递参数改为struct spi_device *spi。所以spi驱动的注册原理和大家熟悉的驱动注册是一样的,大家可要睁大眼睛,不要被这个换汤不换药的老中医蒙骗了。

mx25lxx驱动spi定义并初始化为:

staticstruct spi_driver mx25l_driver = {

.driver = {

.name        = "mx25l",

.bus   = &spi_bus_type,

.owner       = THIS_MODULE,

},

.probe                  =mx25l_probe,

.remove               =__devexit_p(mx25l_remove),

};

在这个驱动实体定义好后,就可以用它开始注册驱动了,首先要执行的函数是:

static int __initmx25l_init(void)

{

return spi_register_driver(&mx25l_driver);

}

在spi_register_driver(&mx25l_driver)中又给mx25l_driver的driver添加了2个成员(这里第3个为空):

intspi_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;

returndriver_register(&sdrv->driver);

}

此时static struct spi_driver mx25l_driver变为:

static structspi_driver mx25l _driver = {

.driver = {

.name        = " mx25l",

.bus            = &spi_bus_type,

.owner       = THIS_MODULE,

.probe = spi_drv_probe,//后来加入,spi.c里的函数

.remove = spi_drv_remove,//后来加入,spi.c里的函数

.shutdown = spi_drv_shutdown;//后来加入,spi.c里的函数

},

.probe                  =mx25l_probe,

.remove               =__devexit_p(mx25l_remove),

};

其中的spi_drv_probe/remove/shutdown为spi.c里的函数,原函数(以spi_drv_probe为例):

static intspi_drv_probe(struct device *dev)

{

const struct spi_driver           *sdrv =to_spi_driver(dev->driver);

//根据dev->driver找到驱动的地址,这里的spi_driver类型就是前面提到的类型,与定义的mx25l_driver一致。

returnsdrv->probe(to_spi_device(dev));

}

其中to_spi_driver定义如下:

static inlinestruct spi_driver *to_spi_driver(struct device_driver *drv)

{

return drv ? container_of(drv, structspi_driver, driver) : NULL;

}

如果drv不为空,就会顺着drv找到driver的地址(下一篇分析container_of的实现原理),然后返回找到的地址。

to_spi_device定义如下:

static inlinestruct spi_device *to_spi_device(struct device *dev)

{

return dev ? container_of(dev, structspi_device, dev) : NULL;

}

我们看下spi_device的结构:

struct spi_device

struct device dev

struct spi_master *master

u32 max_speed_hz

u8 chip_select

u8 mode

u8 bits_per_word

int irq

void *controller_state

void *controller_data

char modalias[SPI_NAME_SIZE]

其中struct device dev是第一个成员,dev的地址与实参spi_device *dev的地址是一样一样的,或许有人会问这不浪费资源,浪费时间,浪费生命吗?我觉得这样用的原因有两个:1.规范,与to_spi_driver一样,形式上保持一致,让人不会有突兀的感觉,一看到这个函数就知道是做什么用的,也不用再继续深入内核查看了,利于阅读源码。2.安全,谁又能保证不会有某些人再利用struct spi_device封装成其他类型的设备_device,万一他们在封装的时候调整了成员顺序怎么办(就像我们spi驱动重新封装platform_drive一样)。这样就不用再调整spi.c中的代码,保证了我们写个驱动不用考虑的太复杂,否则指针乱了,内核运行乱了,剩下的只有找不出原因的我们在风中凌乱了。

之后继续执行driver_register(&sdrv->driver):

intdriver_register(struct device_driver *drv)

{

int ret;

struct device_driver *other;

……//检查参数

other = driver_find(drv->name,drv->bus);

if (other) {

put_driver(other);

printk(KERN_ERR "Error:Driver '%s' is already registered, ""aborting...\n",drv->name);

return -EBUSY;

}

ret = bus_add_driver(drv);

if (ret)

return ret;

ret = driver_add_groups(drv,drv->groups);

if (ret)

bus_remove_driver(drv);

return ret;

}

这是很多设备注册时常用的注册函数driver_register,至此spi设备驱动注册回到“正轨”上来。其中通过层层调用最终会调用关键的static int __devinit mx25l_probe(structspi_device *spi)函数(插句废话:驱动名字必须和设备名字match才会调用probe)。至于调用过程可用Source Insight追踪,详细过程可参考下一篇分析。

probe函数如下:

static int__devinit mx25l_probe(struct spi_device *spi)

{

struct flash_info *flash_info;

struct mx25l_flash *flash;

struct flash_platform_data *data;

int ret, i;

struct mtd_partition *parts = NULL;

int nr_parts = 0;

flash_info = mx25l_match_device(spi);

if (!flash_info)

return -ENODEV;

flash = kzalloc(sizeof(structmx25l_flash), GFP_KERNEL);

if (!flash)

return -ENOMEM;

flash->spi = spi;

mutex_init(&flash->lock);

dev_set_drvdata(&spi->dev,flash);

data = spi->dev.platform_data;

if (data && data->name)

flash->mtd.name =data->name;

else

flash->mtd.name =dev_name(&spi->dev);

flash->mtd.type           = MTD_NORFLASH;

flash->mtd.flags = MTD_CAP_NORFLASH;

flash->mtd.erasesize   = flash_info->erase_size;

flash->mtd.writesize   = flash_info->page_size;

flash->mtd.size            = flash_info->page_size *flash_info->nr_pages;

flash->mtd.erase                  =mx25l_erase;

flash->mtd.read           = mx25l_read;

flash->mtd.write         = mx25l_write;

dev_info(&spi->dev, "%s(%lld KiB)\n", flash_info->name,

(long long)flash->mtd.size >> 10);

DEBUG(MTD_DEBUG_LEVEL2,

"mtd .name = %s, .size = 0x%llx (%lldMiB) "

".erasesize = 0x%.8x (%uKiB) .numeraseregions = %d\n",

flash->mtd.name,

(long long)flash->mtd.size, (long long)(flash->mtd.size >>20),

flash->mtd.erasesize, flash->mtd.erasesize / 1024,

flash->mtd.numeraseregions);

if (mtd_has_cmdlinepart()) {

static const char *part_probes[]= {"cmdlinepart", NULL};

nr_parts =parse_mtd_partitions(&flash->mtd,

part_probes,

&parts,0);

}

if (nr_parts <= 0 && data&& data->parts) {

parts = data->parts;

nr_parts = data->nr_parts;

}

if (nr_parts > 0) {

for (i = 0; i < nr_parts;i++) {

DEBUG(MTD_DEBUG_LEVEL2,"partitions[%d] = "

"{.name = %s, .offset = 0x%llx,"

".size = 0x%llx (%lldKiB) }\n",

i, parts[i].name,

(long long)parts[i].offset,

(long long)parts[i].size,

(long long)(parts[i].size >> 10));

}

flash->partitioned = 1;

returnmtd_device_register(&flash->mtd, parts,

nr_parts);

}

ret =mtd_device_register(&flash->mtd, NULL, 0);

if (ret == 1) {

kfree(flash);

dev_set_drvdata(&spi->dev,NULL);

return -ENODEV;

}

return 0;

}

其中的match函数如下:

static structflash_info *__devinit mx25l_match_device(struct spi_device *spi)

{

struct flash_info *flash_info = NULL;

struct spi_message m;

struct spi_transfer t;

unsigned char cmd_resp[6];

int i, err;

uint16_t id;

spi_message_init(&m);

memset(&t, 0, sizeof(structspi_transfer));

cmd_resp[0] = MX25L_CMD_READREMS;

cmd_resp[1] = 0;

cmd_resp[2] = 0;

cmd_resp[3] = 0;

cmd_resp[4] = 0xff;

cmd_resp[5] = 0xff;

t.tx_buf = cmd_resp;

t.rx_buf = cmd_resp;

t.len = sizeof(cmd_resp);

spi_message_add_tail(&t, &m);

err = spi_sync(spi, &m);

if (err < 0) {

dev_err(&spi->dev,"error reading device id\n");

return NULL;

}

id = (cmd_resp[4] << 8) |cmd_resp[5];

for (i = 0; i < ARRAY_SIZE(mx25l_flash_info);i++)

if(mx25l_flash_info[i].device_id == id)

flash_info =&mx25l_flash_info[i];

if (!flash_info)

dev_err(&spi->dev, "unknownid %.4x\n", id);

return flash_info;

}

其中结构体为:

struct mx25l_flash{

struct spi_device *spi;

struct mutex                 lock;

struct mtd_info            mtd;

int                       partitioned;

};

struct flash_info {

const char           *name;

uint16_t              device_id;

unsigned             page_size;

unsigned             nr_pages;

unsigned             erase_size;

};

#defineto_mx25l_flash(x) container_of(x, struct mx25l_flash, mtd)

static structflash_info __devinitdata mx25l_flash_info[] = {

{"mx25l1605d", 0xbf8c,MX25L_PAGE_SIZE, MX25L_NUMSECTORS, MX25L_EACHSECTOR_SIZE},

{"mx25l3205d", 0xbf8d,MX25L_PAGE_SIZE, MX25L_NUMSECTORS, MX25L_EACHSECTOR_SIZE},

{"mx25l6405d", 0xbf8e,MX25L_PAGE_SIZE, MX25L_NUMSECTORS, MX25L_EACHSECTOR_SIZE},

};

这里的匹配函数要参考MX25LxxD的datasheet:

首先要使用REMS命令0x90,然后继续读5个字节,最后两个字节即为Manufacturer ID 和 Device ID,其中用到struct spi_message m和struct spi_transfer t两个结构体,m用来存放要发送的命令、发送及接收的数据的地址,以及长度等信息,然后调用 spi_message_add_tail(&t, &m)将m增加到t的末尾(当然这里t队列里只有一个m),最后调用spi_sync(spi, &m)发送。所以match函数就是实现这个流程,同理erase、read、write函数也类似,终于又找到那种不用操作系统裸奔时完全hold住全场的感觉了!

其中read,write,erase函数需要根据mx25l的具体命令做相应的细微调整。驱动编写好后,make,然后insmod mx25l.ko,驱动就被安装到系统中,我这里使用的是mx25l3205d,在match的时候我打印了芯片的id:0xbf25。

安装操作篇

驱动安装成功后要手动建立节点:mknod /dev/mx25l c 90 6,我这里的mtd设备信息如下:

字符类mtd设备的主设备号是:90,块mtd设备的主设备号是:31,这里的三个block设备里分别存放的uboot,linux-3.0.1内核,yaffs文件系统,我们的小容量串行flash就不要去凑热闹了,而字符类中已经存在了5个设备,所以次设备号选为6。

测试程序用普通的open,read,write函数就可以实现对/dev/mx25l设备的操作。

spi设备不像USB设备那样支持热插拔,所以通常都是系统板子上的一部分,故其驱动也常常是随系统一起启动。所以驱动调试完成后,可以修改driver/spi/下的Kconfig,Makefile,在系统make menuconfig时选上mx25l即可。

Questions:

1.       没有实现自动注册设备名,每次都要手动注册char类的mtd设备,下一步要看如何实现自动注册。

2.       关于主控的部分未涉及,所以下一步要看设备驱动提交message之后,调用transfer发送与主控器驱动的关系。

3.       使用单片机时对mx25l擦除是在写时,根据开始地址和写入长度来判断是否到新页来擦除扇区。而这里用到了mtd设备方法,对mtd设备调用过程不详,如:erase函数不知何时调用,可能也是在写入时当遇到新页时会被调用,也需要认真分析。

        以上都是我个人的体会,有错误之处敬请谅解,并请不吝赐教,本人将不胜感激,并积极改正。
最后上一张萌照,据说这是世界上最容易被踹的兔子,各位轻踹啊

linux-3.0.1下spi设备mx25l的驱动相关推荐

  1. linux spidev 应用_Linux下SPI驱动的移植和应用程序的测试

    Linux2.6.32下SPI驱动的移植如下图所示: 下面需要修改部分内核代码,具体操作如下: 1.  修改arch/arm/mach-s3c2440/mach-mini2440.c文件 在inclu ...

  2. WINCE6.0+S3C2443下的usb function(功能)驱动

    ********************************LoongEmbedded************************ 作者:LoongEmbedded(kandi) 时间:201 ...

  3. [Linux]Red Hat Linux 9.0环境下架设Web服务器[2]

    5.MaxKeepAliveRequests 当使用保持连接(Persistent Connection)功能时,可以使用本参数决定每次连接所能发出的要求数目的上限.如果此数值为0,则表示没有限制.建 ...

  4. platform框架--Linux MISC杂项框架--Linux INPUT子系统框架--串行集成电路总线I2C设备驱动框架--串行外设接口SPI 设备驱动框架---通用异步收发器UART驱动框架

    platform框架 input. pinctrl. gpio 子系统都是 Linux 内核针对某一类设备而创建的框架, input子系统是管理输入的子系统 pinctrl 子系统重点是设置 PIN( ...

  5. Linux虚拟化KVM-Qemu分析(十)之virtio驱动

    目录 1. 概述 2. 数据结构 3. 流程分析 3.1 virtio总线创建 3.2 virtio驱动调用流程 参考 <Linux PCI驱动框架分析:(Peripheral Componen ...

  6. linux下spi添加设备,Linux Kernl添加spidev的设备节点

    一.spidev介绍 如果在内核中配置spidev,会在/dev目录下产生设备节点,通过此节点可以操作挂载在该SPI总线上的设备.用户空间通过该节点可以访问内核空间. 二.配置spidev设备步骤 在 ...

  7. Linux SPI设备驱动

    实现了SPI OLED外设驱动,OLED型号为SH1106. 1.主机驱动与外设驱动分离 Linux中的I2C.SPI.USB等总线驱动,都采用了主机(控制器)驱动与外设(设备)驱动分离的思想.主机端 ...

  8. Linux spi驱动分析(四)----SPI设备驱动(W25Q32BV)

    一.W25Q32BV芯片简介 W25X是一系列SPI接口Flash芯片的简称,它采用SPI接口和CPU通信,本文使用的W25Q32BV容量为32M,具体特性如下: 1.1.基本特性 该芯片最大支持10 ...

  9. Linux spi驱动分析----SPI设备驱动(W25Q32BV)

    转载地址:http://blog.chinaunix.net/uid-25445243-id-4026974.html 一.W25Q32BV芯片简介 W25X是一系列SPI接口Flash芯片的简称,它 ...

最新文章

  1. MVC使用Flash来显示图片
  2. 【华为出品】智能体白皮书2020(附全文下载)
  3. linux启动程序api编程,Linux编程中关于API函数与系统调用间关系
  4. java批量生成订单号_【笔记6-支付及订单模块】从0开始 独立完成企业级Java电商网站开发(服务端)...
  5. DataSet case sensitive issue
  6. 数据结构 单链表 (C++)(转载)
  7. Docker资源控制与TLS加密通信
  8. (转)基本光照模型公式
  9. 是时候研读一波导师的论文--一个简单有效的联合模型
  10. mysql alter auto increment_将MySQL列更改为AUTO_INCREMENT
  11. dll注入之SetWindowsHookEx 键盘消息钩子
  12. 创新大赛成就创业梦想 超30%入榜应用获投资意向
  13. 浩万计算机工具,子浩KX3552驱动一键安装工具(Win7/Win8/win10)
  14. python3 爬虫神器pyquery的使用实例之爬网站图片
  15. Tcp/Udp端口对照表
  16. 电视不正常Android镜像投屏,Mirror for Android TV(安卓电视投屏软件) V2.4 Mac版
  17. 【视频】中国首届微博开发者大会杨卫华演讲 | 新浪微博架构分享
  18. 首1标准型和尾1标准型
  19. 51单片机(十一)蜂鸣器
  20. TVM运行demo报错:LLVM ERROR: Cannot select: 0x559166c96d58,最后重新安装了LLVM

热门文章

  1. 吃货食堂-吃货们的天堂
  2. 黄仁勋:英伟达收购 ARM 是谣言,下周发布会有惊喜
  3. 非机动车检测,电动车自行车检测
  4. leetcode解题思路(无代码) 归类汇总版,面试笔试经典例题
  5. 【Python技能树共建】Beautiful Soup
  6. 对gram.y的解析(一)
  7. 项目经理:我是如何进行项目进度管理的
  8. SOUKE组合营销软件v9.1官方版
  9. 赌你无法坚持看完的nginx配置文件解析
  10. 2018年浏览器横向比较