免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1773 | 回复: 0
打印 上一主题 下一主题

Linux 2.6下SPI设备模型--------基于AT91RM9200分析[转] [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2010-02-02 11:58 |只看该作者 |倒序浏览

Linux 2.6下SPI设备模型
--------基于AT91RM9200分析
       Atmel公司的ARM AT系列,其SPI驱动在kernel 2.6.23里已经包含。如果你打了at91-patch补丁的话,则在内核配置时要小心。在Device Drivers---- > Character devices ---- >取消选中SPI Driver(legacy) for at91rm9200 processor 。同时Device Drivers---- >SPI Support ---- > 选中SPI Support ,Atmel SPI Controler,同时选中 User mode SPI device driver support 。
SPI Driver(legacy) for at91rm9200 processor是保留选项,为了兼容以前版本。如果同时选中SPI Driver(legacy) for at91rm9200 processor,则在/sys里无法注册类spidev,也就无法将设备和驱动联系在一起。与现有atmel spi驱动发生冲突。

各选项对应的编译情况如下:
      
  • SPI support ---- Config_SPI  开启SPI功能
          
  • Debug support for SPI drivers ---- config SPI_DEBUG   开启SPI debug调试
           ----SPI Master Controller Drivers ---- depends on SPI_MASTER  生成spi.o
           Atmel SPI Controller ---- config SPI_ATMEL 生成atmel_spi.o
           Bitbanging SPI master ---- config SPI_BITBANG 生成spi_bitbang.o
           AT91RM9200 Bitbang SPI Master  ---- CONFIG_SPI_AT91  spi_at91_bitbang.o
           ---- SPI Protocol Masters ---- depends on SPI_MASTER
          SPI EEPROMs from most vendors ---- config SPI_AT25 生成at25.o
           User mode SPI device driver support ---- config SPI_SPIDEV 生成spidev.o
    总线
    注册SPI总线
    #spi.c
           struct bus_type spi_bus_type = {
           .name             = "spi",   // spi总线名称
           .dev_attrs       = spi_dev_attrs,
           .match           = spi_match_device,
           .uevent           = spi_uevent,
           .suspend  = spi_suspend,
           .resume          = spi_resume,
    };
    spi总线将在sysfs/bus下显示。
    其bus_type 结构表示总线,它的定义在中,如下
    struct bus_type {
           const char             * name;
           struct module         * owner;

           struct kset             subsys;
           struct kset             drivers;
           struct kset             devices;
           struct klist             klist_devices;
           struct klist             klist_drivers;

           struct blocking_notifier_head bus_notifier;

           struct bus_attribute * bus_attrs;
           struct device_attribute    * dev_attrs;
           struct driver_attribute    * drv_attrs;
           struct bus_attribute drivers_autoprobe_attr;
           struct bus_attribute drivers_probe_attr;

           int           (*match)(struct device * dev, struct device_driver * drv);
           int           (*uevent)(struct device *dev, char **envp,
                                  int num_envp, char *buffer, int buffer_size);
           int           (*probe)(struct device * dev);
           int           (*remove)(struct device * dev);
           void        (*shutdown)(struct device * dev);

           int (*suspend)(struct device * dev, pm_message_t state);
           int (*suspend_late)(struct device * dev, pm_message_t state);
           int (*resume_early)(struct device * dev);
           int (*resume)(struct device * dev);

           unsigned int drivers_autoprobe:1;
    };
    其中,当一个总线上的新设备或者新驱动被添加时,*match 函数会被调用。如果指定的驱动程序能够处理指定的设备,该函数返回非零值。
    对于spi总线,我们必须调用bus_register(&spi_bus_type)进行注册。调用如果成功,SPI总线子系统将被添加到系统中,在sysfs的/sys/bus目录下可以看到。然后,我们就可以向这个总线添加设备了。代码见下:
    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 0;

    err2:
           bus_unregister(&spi_bus_type);
    err1:
           kfree(buf);
           buf = NULL;
    err0:
           return status;
    }

    设备
    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 */
           u8                  bits_per_word;
           int                  irq;
           void               *controller_state;
           void               *controller_data;
           const char             *modalias;

           /*
            * likely need more hooks for more protocol options affecting how
            * the controller talks to each chip, like:
            *  - memory packing (12 bit samples into low bits, others zeroed)
            *  - priority
            *  - drop chipselect after each word
            *  - chipselect delays
            *  - ...
            */
    };
    device结构中包含了设备模型核心用来模拟系统的信息。spidev还有设备的其他信息,因此spi设备结构包含在spidev_data结构里。
    struct spidev_data {
           struct device          dev;
           struct spi_device    *spi;
           struct list_head       device_entry;

           struct mutex          buf_lock;
           unsigned         users;
           u8                  *buffer;
    };
    注册spi设备,
    #spidev.c
    static int spidev_probe(struct spi_device *spi)
    {
           …
           …
    status = device_register(&spidev->dev);


    }
    完成这个调用之后,我们就可以在sysfs中看到它了。

    SPI设备驱动程序
    spi驱动程序结构如下:
    struct spi_driver {
           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;
    };
    spi驱动程序注册函数如下:
    int spi_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;
           return driver_register(&sdrv->driver);
    }
    spidev的驱动名如下:
    static struct spi_driver spidev_spi = {
           .driver = {
                  .name =          "spidev",
                  .owner = THIS_MODULE,
           },
           .probe =  spidev_probe,
           .remove =       __devexit_p(spidev_remove),
    };
    一个spi_register_driver调用将spidev添加到系统中。一旦初始化完成,就可以在sysfs中看到驱动程序信息。

    spidev类结构如下:
    static struct class spidev_class = {
           .name             = "spidev",
           .owner           = THIS_MODULE,
           .dev_release    = spidev_classdev_release,
    };

    AT91RM9200 SPIDEV初始化
    AT91RM9200的spi驱动,对于EK板,原先的SPI是用于dataflash的。其代码如下:
    static struct spi_board_info ek_spi_devices[] = {
           {     /* DataFlash chip */
                  .modalias = "mtd_dataflash",
                  .chip_select    = 0,
                  .max_speed_hz      = 15 * 1000 * 1000,
           },
    我们需要将.modalias改成我们自己的spi设备名
    在spi设备初始化代码中,class_register(&spidev_class)注册类,spi_register_driver(&spidev_spi)注册spidev驱动。
    #drivers/spi/spidev.c
    static int __init spidev_init(void)
    {
           int status;

           /* Claim our 256 reserved device numbers.  Then register a class
            * that will key udev/mdev to add/remove /dev nodes.  Last, register
            * the driver which manages those device numbers.
            */
           BUILD_BUG_ON(N_SPI_MINORS > 256);
           status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);
           if (status
                  return status;

           status = class_register(&spidev_class);
           if (status
                  unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name);
                  return status;
           }

           status = spi_register_driver(&spidev_spi);
           if (status
                  class_unregister(&spidev_class);
                  unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name);
           }
           return status;
    }

    挂载/sys
    mount –t sysfs sysfs /sys
    可以看到有/sys/class/spidev/spidev0.0,表明设备已经挂载在总线上了,同时与驱动联系起来。
    使用mdev –s,可以在/dev下看到spidev0.0这个设备了。
    自此,spi设备驱动就可以工作了。

    测试程序:



    #include stdio.h>
    #include unistd.h>
    #include stdlib.h>
    #include fcntl.h>
    #include string.h>

    #include sys/ioctl.h>
    #include sys/types.h>
    #include sys/stat.h>

    #include linux/types.h>
    #include linux/spi/spidev.h>


    static int verbose;

    static void do_read(int fd, int len)
    {
           unsigned char buf[32], *bp;
           int status;

           /* read at least 2 bytes, no more than 32 */
           if (len  2)
                  len = 2;
           else if (len > sizeof(buf))
                  len = sizeof(buf);
           memset(buf, 0, sizeof buf);

           status = read(fd, buf, len);
           if (status  0) {
                  perror("read");
                  return;
           }
           if (status != len) {
                  fprintf(stderr, "short read\n");
                  return;
           }

           printf("read(%2d, %2d): %02x %02x,", len, status,
                  buf[0], buf[1]);
           status -= 2;
           bp = buf + 2;
           while (status-- > 0)
                  printf(" %02x", *bp++);
           printf("\n");
    }

    static void do_msg(int fd, int len)
    {
           struct spi_ioc_transfer xfer[2];
           unsigned char buf[32], *bp;
           int status;

           memset(xfer, 0, sizeof xfer);
           memset(buf, 0, sizeof buf);

           if (len > sizeof buf)
                  len = sizeof buf;

           buf[0] = 0xaa;
           xfer[0].tx_buf = (__u64) buf;
           xfer[0].len = 1;

           xfer[1].rx_buf = (__u64) buf;
           xfer[1].len = len;

           status = ioctl(fd, SPI_IOC_MESSAGE(2), xfer);
           if (status  0) {
                  perror("SPI_IOC_MESSAGE");
                  return;
           }

           printf("response(%2d, %2d): ", len, status);
           for (bp = buf; len; len--)
                  printf(" %02x", *bp++);
           printf("\n");
    }

    static void dumpstat(const char *name, int fd)
    {
           __u8 mode, lsb, bits;
           __u32 speed;

           if (ioctl(fd, SPI_IOC_RD_MODE, &mode)  0) {
                  perror("SPI rd_mode");
                  return;
           }
           if (ioctl(fd, SPI_IOC_RD_LSB_FIRST, &lsb)  0) {
                  perror("SPI rd_lsb_fist");
                  return;
           }
           if (ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits)  0) {
                  perror("SPI bits_per_word");
                  return;
           }
           if (ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed)  0) {
                  perror("SPI max_speed_hz");
                  return;
           }

           printf("%s: spi mode %d, %d bits %sper word, %d Hz max\n",
                  name, mode, bits, lsb ? "(lsb first) " : "", speed);
    }

    int main(int argc, char **argv)
    {
           int c;
           int readcount = 0;
           int msglen = 0;
           int fd;
           const char *name;

           while ((c = getopt(argc, argv, "hm:r:v")) != EOF) {
                  switch (c) {
                  case 'm':
                         msglen = atoi(optarg);
                         if (msglen  0)
                                goto usage;
                         continue;
                  case 'r':
                         readcount = atoi(optarg);
                         if (readcount  0)
                                goto usage;
                         continue;
                  case 'v':
                         verbose++;
                         continue;
                  case 'h':
                  case '?':
    usage:
                         fprintf(stderr,
                                "usage: %s [-h] [-m N] [-r N] /dev/spidevB.D\n",
                                argv[0]);
                         return 1;
                  }
           }

           if ((optind + 1) != argc)
                  goto usage;
           name = argv[optind];

           fd = open(name, O_RDWR);
           if (fd  0) {
                  perror("open");
                  return 1;
           }

           dumpstat(name, fd);

           if (msglen)
                  do_msg(fd, msglen);

           if (readcount)
                  do_read(fd, readcount);

           close(fd);
           return 0;
    }


    备注:
    如果要设置模式,速率等,则可仿照以下语句:
    speed      =10*1000*1000; //10MHz
    if (ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed)
                  perror("SPI max_speed_hz");
                  return;
           }
    默认spi_io_transfer时,每个字节之间有延时。在atmel_spi_setup.c文件里去掉该延时语句:
                  /* TODO: DLYBS and DLYBCT */
           //csr |= SPI_BF(DLYBS, 10);
           //csr |= SPI_BF(DLYBCT, 10);
    这样就可以达到无间隙快速传输批量数据。
    标准read(),write()两个函数仅适用于半双工传输,。在传输之间不激活片选。而SPI_IOC_MESSAGE(N)则是全双工传输,并且片选始终激活。
    SPI_IOC_MESSAGE传输长度有限制,默认是一页的长度,但是可以更改。
    spi_ioc_transfer结构的spi长度 是字节长度,16位传输的时候要注意。


    本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u3/110644/showart_2167176.html
  • 您需要登录后才可以回帖 登录 | 注册

    本版积分规则 发表回复

      

    北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
    未成年举报专区
    中国互联网协会会员  联系我们:huangweiwei@itpub.net
    感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

    清除 Cookies - ChinaUnix - Archiver - WAP - TOP