linux-3.0.1下spi设备mx25l的驱动

来源:互联网 发布:打电话说中文域名到期 编辑:程序博客网 时间:2024/05/29 07:25

 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函数不知何时调用,可能也是在写入时当遇到新页时会被调用,也需要认真分析。

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

原创粉丝点击