Showing posts with label driver. Show all posts
Showing posts with label driver. Show all posts

2010-02-05

实例解析linux内核I2C体系结构(2)转

作者:刘洪涛,华清远见嵌入式学院讲师。
四、在内核里写i2c设备驱动的两种方式
前文介绍了利用/dev/i2c-0在应用层完成对i2c设备的操作,但很多时候我们还是习惯为i2c设备在内核层编写 驱动程序。目前内核支持两种编写i2c驱动程序的方式。下面分别介绍这两种方式的实现。这里分别称这两种方式为“Adapter方式(LEGACY)”和 “Probe方式(new style)”。
(1) Adapter方式(LEGACY)
(下面的实例代码是在2.6.27内核的pca953x.c基础上修改的,原始代码采用的是本文将要讨论的第2种方式,即Probe方式)
●    构建i2c_driver
static struct i2c_driver pca953x_driver = {
                .driver = {
                                    .name= "pca953x", //名称
                                },
                .id= ID_PCA9555,//id号
                .attach_adapter= pca953x_attach_adapter, //调用适配器连接设备
                .detach_client= pca953x_detach_client,//让设备脱离适配器
        };
●    注册i2c_driver
static int __init pca953x_init(void)
        {
                return i2c_add_driver(&pca953x_driver);
        }
        module_init(pca953x_init);
●    attach_adapter动作
执行i2c_add_driver(&pca953x_driver)后会,如果内核中已经注册了i2c适配器,则顺序调用这些适配器来连接我们的i2c设备。此过程是通过调用i2c_driver中的attach_adapter方法完成的。具体实现形式如下:
static int pca953x_attach_adapter(struct i2c_adapter *adapter)
        {
                return i2c_probe(adapter, &addr_data, pca953x_detect);
                /*
                adapter:适配器
                addr_data:地址信息
                pca953x_detect:探测到设备后调用的函数
                */
        }
地址信息addr_data是由下面代码指定的。
        /* Addresses to scan */
        static unsigned short normal_i2c[] = {0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,I2C_CLIENT_END};
        I2C_CLIENT_INSMOD;
注意:normal_i2c里的地址必须是你i2c芯片的地址。否则将无法正确探测到设备。而I2C_ CLIENT_INSMOD是一个宏,它会利用normal_i2c构建addr_data。
●    构建i2c_client,并注册字符设备驱动
i2c_probe在探测到目标设备后,后调用pca953x_detect,并把当时的探测地址address作为参数传入。
static int pca953x_detect(struct i2c_adapter *adapter, int address, int kind)
        {
                struct i2c_client *new_client;
                struct pca953x_chip *chip; //设备结构体
                int err = 0,result;
                dev_t pca953x_dev=MKDEV(pca953x_major,0);//构建设备号,根据具体情况设定,这里我只考虑了normal_i2c中只有一个地址匹配的情况。
                if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA| I2C_FUNC_SMBUS_WORD_DATA))//判定适配器能力
                goto exit;
                if (!(chip = kzalloc(sizeof(struct pca953x_chip), GFP_KERNEL))) {
                        err = -ENOMEM;
                        goto exit;
                }
                /****构建i2c-client****/
                chip->client=kzalloc(sizeof(struct i2c_client),GFP_KERNEL);
                new_client = chip->client;
                i2c_set_clientdata(new_client, chip);
                new_client->addr = address;
                new_client->adapter = adapter;
                new_client->driver = &pca953x_driver;
                new_client->flags = 0;
                strlcpy(new_client->name, "pca953x", I2C_NAME_SIZE);
                if ((err = i2c_attach_client(new_client)))//注册i2c_client
                goto exit_kfree;
                if (err)
                goto exit_detach;
                if(pca953x_major)
                {
                        result=register_chrdev_region(pca953x_dev,1,"pca953x");
                }
                else{
                        result=alloc_chrdev_region(&pca953x_dev,0,1,"pca953x");
                        pca953x_major=MAJOR(pca953x_dev);
                }
                if (result < 0) {
                        printk(KERN_NOTICE "Unable to get pca953x region, error %d\n", result);
                        return result;
                }
                pca953x_setup_cdev(chip,0); //注册字符设备,此处不详解
                return 0;
                exit_detach:
                i2c_detach_client(new_client);
        exit_kfree:
                kfree(chip);
        exit:
                return err;
        }
i2c_check_functionality用来判定设配器的能力,这一点非常重要。你也可以直接查看对应设配器的能力,如
static const struct i2c_algorithm smbus_algorithm = {
                .smbus_xfer= i801_access,
                .functionality= i801_func,
        };
        static u32 i801_func(struct i2c_adapter *adapter)
        {
                        return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
                    I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
                I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_WRITE_I2C_BLOCK
                                | (isich4 ? I2C_FUNC_SMBUS_HWPEC_CALC : 0);
        }
●    字符驱动的具体实现
struct file_operations pca953x_fops = {
                .owner = THIS_MODULE,
                .ioctl= pca953x_ioctl,
                .open= pca953x_open,
                .release =pca953x_release,
        };
字符设备驱动本身没有什么好说的,这里主要想说一下,如何在驱动中调用i2c设配器帮我们完成数据传输。
目前设配器主要支持两种传输方法:smbus_xfer和master_xfer。一般来说,如果设配器支持了master_xfer那么它也可以模拟支持smbus的传输。但如果只实现smbus_xfer,则不支持一些i2c的传输。
int (*master_xfer)(struct i2c_adapter *adap,struct i2c_msg *msgs,int num);
        int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
                                                                                unsigned short flags, char read_write,
                                                                u8 command, int size, union i2c_smbus_data * data);
master_xfer中的参数设置,和前面的用户空间编程一致。现在只是要在驱动中构建相关的参数然后调用i2c_transfer来完成传输既可。
int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num)
smbus_xfer中的参数设置及调用方法如下:
static int pca953x_write_reg(struct pca953x_chip *chip, int reg, uint16_t val)
        {
                int ret;
                ret = i2c_smbus_write_word_data(chip->client, reg << 1, val);
                if (ret < 0) {
                                dev_err(&chip->client->dev, "failed writing register\n");
                                        return -EIO;
                                }
                return 0;
        }
上面函数完成向芯片的地址为reg的寄存器写一个16bit的数据。i2c_smbus_write_word_data的实现如下:
s32 i2c_smbus_write_word_data(struct i2c_client *client, u8 command, u16 value)
        {
                union i2c_smbus_data data;
                data.word = value;
                return i2c_smbus_xfer(client->adapter,client->addr,client->flags,
                                                                        I2C_SMBUS_WRITE,command,
                                                                        I2C_SMBUS_WORD_DATA,&data);
        }
从中可以看出smbus传输一个16位数据的方法。其它操作如:字符写、字符读、字读、块操作等,可以参考内核的i2c-core.c中提供的方法。
●    注销i2c_driver
static void __exit pca953x_exit(void)
        {
                i2c_del_driver(&pca953x_driver);
        }
        module_exit(pca953x_exit);
●    detach_client动作
顺序调用内核中注册的适配器来断开我们注册过的i2c设备。此过程通过调用i2c_driver中的attach_adapter方法完成的。具体实现形式如下:
static int pca953x_detach_client(struct i2c_client *client)
        {
                int err;
                struct pca953x_chip *data;
                if ((err = i2c_detach_client(client)))//断开i2c_client
                return err;
                data=i2c_get_clientdata(client);
                cdev_del(&(data->cdev));
                unregister_chrdev_region(MKDEV(pca953x_major, 0), 1);
                kfree(data->client);
                kfree(data);
                return 0;
        }
(2) Probe方式(new style)
●    构建i2c_driver
和LEGACY方式一样,也需要构建i2c_driver,但是内容有所不同。
static struct i2c_driver pca953x_driver = {
                .driver = {
                        .name= "pca953x",
                        },
                        .probe= pca953x_probe, //当有i2c_client和i2c_driver匹配时调用
                        .remove= pca953x_remove,//注销时调用
                        .id_table= pca953x_id,//匹配规则
        };
●    注册i2c_driver
static int __init pca953x_init(void)
        {
                return i2c_add_driver(&pca953x_driver);
        }
        module_init(pca953x_init);
在注册i2c_driver的过程中,是将driver注册到了i2c_bus_type的总线上。此总线的匹配规则是:
static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
                                                                                                const struct i2c_client *client)
        {
                while (id->name[0]) {
                        if (strcmp(client->name, id->name) == 0)
                                return id;
                        id++;
                }
                return NULL;
        }
可以看出是利用i2c_client的名称和id_table中的名称做匹配的。本驱动中的id_table为
static const struct i2c_device_id pca953x_id[] = {
                { "pca9534", 8, },
                { "pca9535", 16, },
                { "pca9536", 4, },
                { "pca9537", 4, },
                { "pca9538", 8, },
                { "pca9539", 16, },
                { "pca9554", 8, },
                { "pca9555", 16, },
                { "pca9557", 8, },
                { "max7310", 8, },
                { }
        };
看到现在我们应该会有这样的疑问,在Adapter模式中,i2c_client是我们自己构造出来的,而现在的i2c_client是从哪来的呢?看看下面的解释
●    注册i2c_board_info
对于Probe模式,通常在平台代码中要完成i2c_board_info的注册。方法如下:
static struct i2c_board_info __initdata test_i2c_devices[] = {
                {
                        I2C_BOARD_INFO("pca9555", 0x27),//pca9555为芯片名称,0x27为芯片地址
                        .platform_data = &pca9555_data,
                }, {
                        I2C_BOARD_INFO("mt9v022", 0x48),
                        .platform_data = &iclink[0], /* With extender */
                }, {
                        I2C_BOARD_INFO("mt9m001", 0x5d),
                        .platform_data = &iclink[0], /* With extender */
                },
        };
        i2c_register_board_info(0, test_i2c_devices,ARRAY_SIZE(test_i2c_devices)); //注册
i2c_client就是在注册过程中构建的。但有一点需要注意的是i2c_register_board_info并没有EXPORT_SYMBOL给模块使用。
●    字符驱动注册
在Probe方式下,添加字符驱动的位置在pca953x_probe中。
static int __devinit pca953x_probe(struct i2c_client *client,const struct i2c_device_id *id)
        {
                        ……
                        /****字符设备驱动注册位置****/
                        ……
                        return 0;
        }
●    注销i2c_driver
static void __exit pca953x_exit(void)
        {
                i2c_del_driver(&pca953x_driver);
        }
        module_exit(pca953x_exit);
●    注销字符设备驱动
在Probe方式下,注销字符驱动的位置在pca953x_remove中。
static int __devinit pca953x_remove (struct i2c_client *client)
        {
                ……
                /****字符设备驱动注销的位置****/
                ……
                return 0;
        }
●    I2C设备的数据交互方法(即:调用适配器操作设备的方法)和Adapter方式下相同。

实例解析linux内核I2C体系结构(1) 转

一、概述
谈到在linux系统下编写I2C驱动,目前主要有两种方式,一种是把I2C设备当作一个普通的字符设备来处理,另一种是利用linux I2C驱动体系结构来完成。下面比较下这两种驱动。
第一种方法的好处(对应第二种方法的劣势)有:
        ●    思路比较直接,不需要花时间去了解linux内核中复杂的I2C子系统的操作方法。
第一种方法问题(对应第二种方法的好处)有:
        ●    要求工程师不仅要对I2C设备的操作熟悉,而且要熟悉I2C的适配器操作;
        ●    要求工程师对I2C的设备器及I2C的设备操作方法都比较熟悉,最重要的是写出的程序可移植性差;
        ●    对内核的资源无法直接使用。因为内核提供的所有I2C设备器及设备驱动都是基于I2C子系统的格式。I2C适配器的操作简单还好,如果遇到复杂的I2C适配器(如:基于PCI的I2C适配器),工作量就会大很多。
本文针对的对象是熟悉I2C协议,并且想使用linux内核子系统的开发人员。
网络和一些书籍上有介绍I2C子系统的源码结构。但发现很多开发人员看了这些文章后,还是不清楚自己究竟该做些什么。究 其原因还是没弄清楚I2C子系统为我们做了些什么,以及我们怎样利用I2C子系统。本文首先要解决是如何利用现有内核支持的I2C适配器,完成对I2C设 备的操作,然后再过度到适配器代码的编写。本文主要从解决问题的角度去写,不会涉及特别详细的代码跟踪。
二、I2C设备驱动程序编写
首先要明确适配器驱动的作用是让我们能够通过它发出符合I2C标准协议的时序。
在Linux内核源代码中的drivers/i2c/busses目录下包含着一些适配器的驱动。如S3C2410的驱动i2c-s3c2410.c。当适配器加载到内核后,接下来的工作就要针对具体的设备编写设备驱动了。
编写I2C设备驱动也有两种方法。一种是利用系统给我们提供的i2c-dev.c来实现一个i2c适配器的设备文件。然 后通过在应用层操作i2c适配器来控制i2c设备。另一种是为i2c设备,独立编写一个设备驱动。注意:在后一种情况下,是不需要使用i2c-dev.c 的。
1、利用i2c-dev.c操作适配器,进而控制i2c设备
i2c-dev.c并没有针对特定的设备而设计,只是提供了通用的read()、write()和ioctl()等接口,应用层可以借用这些接口访问挂接在适配器上的i2c设备的存储空间或寄存器,并控制I2C设备的工作方式。
需要特别注意的是:i2c-dev.c的read()、write()方法都只适合于如下方式的数据格式(可查看内核相关源码)
图1 单开始信号时序
所以不具有太强的通用性,如下面这种情况就不适用(通常出现在读目标时)。
图2 多开始信号时序
而且read()、write()方法只适用用于适配器支持i2c算法的情况,如:
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
            .master_xfer = s3c24xx_i2c_xfer,
            .functionality = s3c24xx_i2c_func,
        };
而不适合适配器只支持smbus算法的情况,如:
        static const struct i2c_algorithm smbus_algorithm = {
            .smbus_xfer = i801_access,
            .functionality = i801_func,
        };
基于上面几个原因,所以一般都不会使用i2c-dev.c的read()、write()方法。最常用的是ioctl()方法。ioctl()方法可以实现上面所有的情况(两种数据格式、以及I2C算法和smbus算法)。
针对i2c的算法,需要熟悉struct i2c_rdwr_ioctl_data 、struct i2c_msg。使用的命令是I2C_RDWR。
        struct i2c_rdwr_ioctl_data {
            struct i2c_msg __user *msgs; /* pointers to i2c_msgs */
            __u32 nmsgs; /* number of i2c_msgs */
        };
        struct i2c_msg {
            _ _u16 addr; /* slave address */
            _ _u16 flags; /* 标志(读、写) */
            _ _u16 len; /* msg length */
            _ _u8 *buf; /* pointer to msg data */
        };
针对smbus算法,需要熟悉struct i2c_smbus_ioctl_data。使用的命令是I2C_SMBUS。对于smbus算法,不需要考虑“多开始信号时序”问题。
        struct i2c_smbus_ioctl_data {
            __u8 read_write; //读、写
            __u8 command; //命令
            __u32 size; //数据长度标识
            union i2c_smbus_data __user *data; //数据
        };
下面以一个实例讲解操作的具体过程。通过S3C2410操作AT24C02 e2prom。实现在AT24C02中任意位置的读、写功能。
首先在内核中已经包含了对s3c2410 中的i2c控制器驱动的支持。提供了i2c算法(非smbus类型的,所以后面的ioctl的命令是I2C_RDWR)
        static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
            .master_xfer = s3c24xx_i2c_xfer,
            .functionality = s3c24xx_i2c_func,
        };
另外一方面需要确定为了实现对AT24C02 e2prom的操作,需要确定AT24C02的地址及读写访问时序。
●        AT24C02地址的确定
原理图上将A2、A1、A0都接地了,所以地址是0x50。
●        AT24C02任意地址字节写的时序
可见此时序符合前面提到的“单开始信号时序”
●        AT24C02任意地址字节读的时序
可见此时序符合前面提到的“多开始信号时序”
下面开始具体代码的分析(代码在2.6.22内核上测试通过):
        /*i2c_test.c
        * hongtao_liu <lht@farsight.com.cn>
        */
        #include <stdio.h>
        #include <linux/types.h>
        #include <stdlib.h>
        #include <fcntl.h>
        #include <unistd.h>
        #include <sys/types.h>
        #include <sys/ioctl.h>
        #include <errno.h>
        #define I2C_RETRIES 0x0701
        #define I2C_TIMEOUT 0x0702
        #define I2C_RDWR 0x0707
        /*********定义struct i2c_rdwr_ioctl_data和struct i2c_msg,要和内核一致*******/
struct i2c_msg
        {
                unsigned short addr;
                unsigned short flags;
        #define I2C_M_TEN 0x0010
        #define I2C_M_RD 0x0001
                unsigned short len;
                unsigned char *buf;
        };
struct i2c_rdwr_ioctl_data
        {
                struct i2c_msg *msgs;
                int nmsgs;
        /* nmsgs这个数量决定了有多少开始信号,对于“单开始时序”,取1*/
        };
/***********主程序***********/
        int main()
        {
                int fd,ret;
                struct i2c_rdwr_ioctl_data e2prom_data;
                fd=open("/dev/i2c-0",O_RDWR);
        /*
        */dev/i2c-0是在注册i2c-dev.c后产生的,代表一个可操作的适配器。如果不使用i2c-dev.c
        *的方式,就没有,也不需要这个节点。
        */
                if(fd<0)
                {
                        perror("open error");
                }
                e2prom_data.nmsgs=2;
        /*
        *因为操作时序中,最多是用到2个开始信号(字节读操作中),所以此将
        *e2prom_data.nmsgs配置为2
        */
                e2prom_data.msgs=(struct i2c_msg*)malloc(e2prom_data.nmsgs*sizeof(struct i2c_msg));
                if(!e2prom_data.msgs)
                {
                        perror("malloc error");
                        exit(1);
                }
                ioctl(fd,I2C_TIMEOUT,1);/*超时时间*/
                ioctl(fd,I2C_RETRIES,2);/*重复次数*/
                /***write data to e2prom**/

                e2prom_data.nmsgs=1;
                (e2prom_data.msgs[0]).len=2; //1个 e2prom 写入目标的地址和1个数据
                (e2prom_data.msgs[0]).addr=0x50;//e2prom 设备地址
                (e2prom_data.msgs[0]).flags=0; //write
                (e2prom_data.msgs[0]).buf=(unsigned char*)malloc(2);
                (e2prom_data.msgs[0]).buf[0]=0x10;// e2prom 写入目标的地址
                (e2prom_data.msgs[0]).buf[1]=0x58;//the data to write
        ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);
                if(ret<0)
                {
                        perror("ioctl error1");
                }
                sleep(1);
        /******read data from e2prom*******/
                e2prom_data.nmsgs=2;
                (e2prom_data.msgs[0]).len=1; //e2prom 目标数据的地址
                (e2prom_data.msgs[0]).addr=0x50; // e2prom 设备地址
                (e2prom_data.msgs[0]).flags=0;//write
                (e2prom_data.msgs[0]).buf[0]=0x10;//e2prom数据地址
                (e2prom_data.msgs[1]).len=1;//读出的数据
                (e2prom_data.msgs[1]).addr=0x50;// e2prom 设备地址
                (e2prom_data.msgs[1]).flags=I2C_M_RD;//read
                (e2prom_data.msgs[1]).buf=(unsigned char*)malloc(1);//存放返回值的地址。
                (e2prom_data.msgs[1]).buf[0]=0;//初始化读缓冲
        ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);
                if(ret<0)
                {
                        perror("ioctl error2");
                }
                printf("buff[0]=%x\n",(e2prom_data.msgs[1]).buf[0]);
        /***打印读出的值,没错的话,就应该是前面写的0x58了***/
                close(fd);
                return 0;
        }
以上讲述了一种比较常用的利用i2c-dev.c操作i2c设备的方法,这种方法可以说是在应用层完成了对具体i2c设备的驱动工作。
计划下一篇总结以下几点:
(1)在内核里写i2c设备驱动的两种方式:
●    Probe方式(new style),如:
                static struct i2c_driver pca953x_driver = {
                        .driver = {
                                .name = "pca953x",
                        },
                        .probe = pca953x_probe,
                        .remove = pca953x_remove,
                        .id_table = pca953x_id,
                };
●    Adapter方式(LEGACY),如:
                static struct i2c_driver pcf8575_driver = {
                        .driver = {
                                .owner = THIS_MODULE,
                                .name = "pcf8575",
                        },
                        .attach_adapter = pcf8575_attach_adapter,
                        .detach_client = pcf8575_detach_client,
                };
(2)适配器驱动编写方法
(3)分享一些项目中遇到的问题
        希望大家多提意见,多多交流。

2009-12-04

Linux 2.6 SPI Framework Analysis

1. Files

Linux only implements the SPI controller side. The SPI framework and related SPI master driver files normally go to the "drivers/spi" subfolder under Linux kernel source tree.

spi.c:
The main file implements the SPI framework, including the SPI master and SPI device related function calls.

include/linux/spi/spi.h:
Header file for spi.c and some inline functions.

spidev.c:
Implements a general char device driver for SPI device. This driver can do read/write/ioctl on the SPI device.

include/linux/spi/spidev.h:
Header file for spidev.c.

spi_bitbang.c/spi_gpio.c:
A library implementing a SPI master with GPIOs using bitbang.

spi_xxxx.c
Board related SPI master and SPI device implementation.

2. SPI Framework

Linux has two level SPI devices: SPI master and SPI device.

SPI master is SPI bus controller, and it really handles the hardware. struct spi_master specifies the details of SPI master. Its driver operates the hardware to transfer and/or receive data to/from the SPI device. Under each SPI master (or SPI bus), there could be more than 1 SPI devices.

SPI device is the presentation of the SPI slave in kernel. struct spi_device specifies the details of SPI device. In order to communicate with the SPI slave device, there must be one SPI device existing in the kernel.

Normally, platform data for SPI master and device are statically defined in board specific file. To make it work, SPI master driver and SPI device driver need to be implemented.

2.1. SPI Master Driver

SPI master driver should be registered with probe() function. When SPI master device is found, the probe() function will be called.

The probe() function will initialize the SPI hardware based on the platform data of SPI master. It will allocate struct spi_master and register it to the system. The it will scan the platform data to find the SPI devices connected to this SPI bus.

scan_boardinfo() scans the platform data, and call spi_new_device() to create SPI device data structure, and set up struct spi_device based on the platform information. Then it calls the master's setup() method to further initialize, link the struct spi_device with struct spi_master, and add the SPI device to the system.

To this point, the SPI master and SPI device are created and added to the system. But it still can't communicate with the SPI device as no specific driver is installed yet.

2.2. SPI Device Driver

SPI device needs a driver to work. A general char device driver is implemented in Linux to support basic read()/write()/ioctl() methods. To link up the SPI device with this driver, just need to define the modalias of struct spi_board_info to "spidev".

If a driver is available for SPI device, its probe() method will be called when the SPI device is created and added to the system.

2.3. SPI Data Transfer

The user application needs a SPI device to access the data transfer service from SPI. Read/write/ioctl can be used for data transfer. The SPI device driver utlizes the SPI framework structures to communicate with SPI master driver. The struct spi_message is used to schedule a message to the SPI master's queue. Each message might includes a list of struct spi_transfer.

The SPI master's transfer function will be called by the SPI device driver to start the transfer. The SPI device driver normally needs to wait the SPI master finishs the transfer before return to the user application.

The SPI master driver needs to implement a mechanism to send the data on the SPI bus using the SPI device specified settings. It's the SPI master driver's responsibilities to operate the hardware to send out the data. Normally, the SPI master needs to implement:

  - a message queue: to hold the messages from the SPI device driver

  - a workqueue and workqueue thread: to pump the messages from the message queue and start transfer

  - a tasklet and tasklet handler: to send the data on the hardware

  - a interrupt handler: to handle the interrupts during the transfer

While the SPI master transfering the data, the SPI device driver normally implement a completion to wait the SPI master finishes the transfer.

3. Data Structure Example

This data structure example is taken from ST Cartesio MSP SPI Driver. Click the picture to view a big one.



FPGA Drive (quote)


XScale PXA270在Linux下的FPGA设备驱动
北京航空航天大学 乾正光 王田苗 魏洪兴

引言
Intel 公司推出的XScale 采用ARM V5TE结构,是Strong ARM的升级换代产品。XScale PXA270处理器最高主频可达到624M赫兹,加入了Wireless MMX、Intel SpeedStep等新技术,以其高性能、低功耗、多功能等特点在信息家电、工业控制等领域得到了广泛的应用。在嵌入式控制中,“微处理器+FPGA”是 一种常用的解决方案,FPGA(现场可编程门阵列)有编程方便、集成度高、速度快等特点,电子设计人员可以通过硬件编程的方法来实现FPGA芯片各种功能 的开发,在我们的一个数控平台的研究项目中,采用XScale PXA270作为主CPU,并对其进行FPGA扩展,使其具有插补、电机驱动、信号处理、I/O口扩展的功能。Linux以其内核精炼、高效、源代码开放 且免费等优势,嵌入式领域得到了广泛的应用。下面以Intel XScale PXA270上的Altera FLEX/ACEX的应用为例,详细介绍Linux下的FPGA设备驱动的实现。
1 Altera FLEX/ACEX芯片结构
Altera FLEX/ACEX芯片是基于查找表LUT(Look-Up-Table)原理而实现的,LUT本质上就是一个RAM。目前FPGA中多使用4输入的 LUT,所以每个LUT可以看成一个有4位地址线的16×1的RAM。当用户通过原理图或HDL语言描述一个逻辑电路以后,FPGA开发软件会自动计算逻 辑电路的所有可能的结果,并把结果事先写入RAM,这样,每输入一个信号进行逻辑运算都等于输入一个地址进行查表,找出地址对应的内容,然后输出即可。
由 于LUT主要适合SRAM工艺生产,所以目前大部分FPGA都是基于SRAM工艺的,而SRAM工艺的芯片在 掉电后信息就会丢失,一定要外加1片专用配置芯片(本实验电路使用Altera EPC2LC20),在上电时,由这个专用配置芯片把数据加载到FPGA中然后FPGA即可正常工作。由于配置时间很短,因此不会影响系统正常工作,在使 用ACEX1K50之前,应对其进行设计编程,实现相应寄存器及I/O口的功能。有关FPGA的详细内容请参阅相关资料。
2 Intel XScale PXA270处理器的系统存储器接口
PXA270处理器的可编程静态存储体系结构如图1所示。

在系统上,ACEX1K50位于nCS<2>上,物理地址0x8000000-0x8001000共4K的静态地址空间,图2表示了Intel XScale PXA270与ACEX1K50的硬件连接关系。

3 Linux下ACEX1K50设备驱动的实现
3.1 Linux下设备驱动基本原理

设备驱动程序是应用程序与硬件之间的一个中间软件层,设备驱动程序为应用程序屏蔽了硬件的细节。这样在应用程序看来,硬件设备只是一个设备文件,应用程序 可以像操作普通文件一样对应用设备进行操作,设备驱动程序是内核的一部分,它主要实现的功能有:对设备进行初始化和释放;把数据从内核传送到硬件和从硬件 读取数据;读取应用程序传送给设备文件的数据,回送应用程序请求的数据以及检测和处理设备出现的错误。
Linux 将设备分为最基本的两大类:一类是字符设备;另一类是块设备,字符设备和块设备的主要区别在于是否使用了缓冲技术,字符设备以单个字节为单位进行顺序读/ 写操作,通常不使用缓冲技术,块设备为了提高效率,利用一块系统内存作为读/写操作的缓冲区,由于涉及缓冲区管理、调度和同步等问题,实现起来比字符设备 复杂得多。
Linux通过设备文件系统对设备进行管理,各种设备都以文件的形式存放在/dev目录下,称为 “设备文件”。应用程序可以像普通文件一样打开、关闭和读/写这些设备文件,为了管理这些设备,系统为设备编了号,每个设备号又分为主设备号和次设备号, 主设备号用来区分不同种类的设备,而次设备号用来区分同一类型的多个设备,Linux为所有的设备文件都提供了统一的操作函数接口,方法是使用数据结构 struct FILE_operations,这个数据结构中包括许多操作函数的指针,如open()、close()、read()和write()等,但由于外设 的种类较多,操作方式各不相同,struct file_operations结构体中的成员为一系列的接口函数,如用于读/写的read/write函数和用于控制的ioct1等。打开一个文件就是 调用这个文件file_operations中的open操作,不同类型的文件(如普通的磁盘数据文件)有不同的file_operations成员函 数,接口函数完成磁盘数据块读/写操作,而对于各种设备文件,则最终调用各自驱动程序中的I/O函数进行具体设备的操作,这样,应用程序根本不必考虑操作 的是设备还是普通文件,可一律当做文件处理,具有非常清晰、统一的I/O接口,所以file_operations是文件层次的I/O接口。
3.2 ACEX1K50在Linux下设备驱动的实现
驱动程序中使用内存映射可以提供给用户程序直接访问设备内存的能力。使用内存映射得好处是处理大文件时速度明显快于标准文件I/O,无论读/写,都少了一次用户空间与内核空间之间的复制,在用户空间对ACEX1K50 FPGA设备的访问是通过内存映射来实现的。
ACEX1K50可以看作是硬件连接在PXA270微处理器的nCS<2>上的一段物理地址来寻址。因为有虚拟内存管理单元,所以如果Linux下,必须先把物理地址映射到虚拟地址空间,然后才能对该段地址进行读/写。
在 内核驱动程序的初始化阶段,通过ioremap()将ACEX1K50的这段物理地址映射到内核虚拟空间;在驱动程序的mmap系统调用中,使用 remap_page_range()将该块COM映射到用户虚拟空间,这样内核空间和用户空间都能访问ACEX1K50的这段被映射后的虚拟地址。
由于ACEX1K50位于nCS<2>上,参照PXA270静态存储体系结构映射表,其物理起始地址为0x08000000。另外,其设备名称及主次设备号定义如下:

其中FPGA主设备号定义为零,使得操作系统可以随机为该设备分配主设备号。
ioremap()的作为是把一个物理内存地址点映射为一个内核指针,被映射数据的长度由size参数设定,该函数的实质上把一块物理区域二次映射到一个可以从驱动程序里访问的虚拟地址上去,以下是该函数的定义:
void*ioremap(unsigned long phys_addr,unsigned long size);
设备驱动通过fpga_init()函数初始化FPGA设备,最终通过init_module(fpga_init)在内核启动时初始化FPGA设备。
fpga_init()函数的流程如图3所示。

ioremap()调用的语句如下:
pxa270_fpga_base=(unsigned long)ioremap(FPGA_PHY_START,SZ_4K);
可以通过ioremap()调用的返回值pxa270_fpga_base来判断FPGA物理地址到内核虚拟空间是否映射成功。
if(!pxa270_fpga_base){

printk(“ioremap pxa270 fpga failed\n”);

return-EINVAL;

}
向设备文件系统注销FPGA设备通过调用cheanup_module()函数来实现。其代码如下:

在向内核设备文件系统注册该FPGA驱动后,还须实现设备驱动的file_operations结构,ACEX1K50的设备驱动定义了如下file_operations成员函数:

其 中fpga_open和fpga_release系统调用的功能只简单地实现了FPGA设备使用计数器的递增与递减,fpga_ioctl系统调用也只是 简单的打印一条没有ioctl控制的信息提示。这里不再分析实现的具体代码。下面具体分析fpga_mmap的实现过程:

fpga_mmap(struct file*filp,struct vm_area_struct*vma)系统调用允许直接将FPGA设备内存线性地映射到用户进程的地址空间中,fpga_mmap系统调用是通过调用 remap_page_range()函数来实现一段线性物理地址的映射,调用remap_page_range()函数需要填写 vm_area_struct结构的几个关键字段。

4 ACEX1K50设备驱动在用户程序中的使用
当设备驱动实现后就可以在用户空间使用该设备了。在用户空间主要是通过调用mmap()函数来实现对FPGA设备的访问。以下是用户空间应用程序的一个示例:

结语
本文通过介绍ACEX1K50在Linux操作系统下设备驱动的实现过程,为FPGA在嵌入式领域的应用提供了一种方法。在实际应用中,通过用户程序能够很好地实现对FPGA硬件编程后的各种功能的控制。

Linux 2.6 SPI Device Driver(quote)

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 结构表示总线,它的定义在<linux/device.h>中,如下
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 < 0)
              goto err1;

       status = class_register(&spi_master_class);
       if (status < 0)
              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 < 0)
              return status;

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

       status = spi_register_driver(&spidev_spi);
       if (status < 0) {
              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) < 0) {
              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位传输的时候要注意。