linux SPI驱动

用A20的芯片做一个项目,rfid和单片机都用spi通讯,挂在同样个spi控制器上,A20的每个spi控制器刚好支持最多两个从设备,但是好像平台的代码有问题还是别的原因,只有rfid可以通讯,单片机的spi始终没有反应,不得已改用gpio模拟,幸好内核有现成的模拟gpio代码,把它配置起来用即可。

不过我以前没有用过,所以花了半天时间分析里面的代码结构才写好驱动代码。

重要的结构体分析:

struct spi_master {                                                                                                                                                struct device   dev;    struct list_head list;    /* other than negative (== assign one dynamically), bus_num is fully     * board-specific.  usually that simplifies to being SOC-specific.     * example:  one SOC has three SPI controllers, numbered 0..2,     * and one board's schematics might show it using SPI-2.  software     * would normally use bus_num=2 for that controller.     */    s16         bus_num;//给自己的编号,设备驱动中的编号和这里一样,表示用的是该控制器    /* chipselects will be integral to many controllers; some others     * might use board-specific GPIOs.     */    u16         num_chipselect;//如果芯片带有spi 控制器,一般片选脚集成在控制器中了,如A10每个spi控制器支持两个片选;如果是模拟的控制器,不受限制,可以多个    /* some SPI controllers pose alignment requirements on DMAable     * buffers; let protocol drivers know about these requirements.     */    u16         dma_alignment;    /* spi_device.mode flags understood by this controller driver */    u16         mode_bits; //该控制器支持的传输模式,比如A10的支持SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LSB_FIRST,设备驱动中的传输模式必须在该范围    /* other constraints relevant to this driver */    u16         flags;#define SPI_MASTER_HALF_DUPLEX  BIT(0)      /* can't do full duplex */#define SPI_MASTER_NO_RX    BIT(1)      /* can't do buffer read */#define SPI_MASTER_NO_TX    BIT(2)      /* can't do buffer write */    /* lock and mutex for SPI bus locking */    spinlock_t      bus_lock_spinlock;    struct mutex        bus_lock_mutex;    /* flag indicating that the SPI bus is locked for exclusive use */    bool            bus_lock_flag;    /* Setup mode and clock, etc (spi driver may call many times).                                                                                                  *     * IMPORTANT:  this may be called when transfers to another     * device are active.  DO NOT UPDATE SHARED REGISTERS in ways     * which could break those transfers.     */    int         (*setup)(struct spi_device *spi);//传输的一些准备工作,控制器自己实现    /* bidirectional bulk transfers     *     * + The transfer() method may not sleep; its main role is     *   just to add the message to the queue.     * + For now there's no remove-from-queue operation, or     *   any other request management     * + To a given spi_device, message queueing is pure fifo     *     * + The master's main job is to process its message queue,     *   selecting a chip then transferring data     * + If there are multiple spi_device children, the i/o queue     *   arbitration algorithm is unspecified (round robin, fifo,     *   priority, reservations, preemption, etc)     *     * + Chipselect stays active during the entire message     *   (unless modified by spi_transfer.cs_change != 0).     * + The message transfers use clock and SPI mode parameters     *   previously established by setup() for this device     */    int         (*transfer)(struct spi_device *spi,                        struct spi_message *mesg);//最终的传输函数,控制器自己实现    /* called on release() to free memory provided by spi_master */    void            (*cleanup)(struct spi_device *spi);    /*     * These hooks are for drivers that want to use the generic     * master transfer queueing mechanism. If these are used, the     * transfer() function above must NOT be specified by the driver.     * Over time we expect SPI drivers to be phased over to this API.     */    bool                queued;    struct kthread_worker       kworker;    struct task_struct      *kworker_task;    struct kthread_work     pump_messages;    spinlock_t          queue_lock;    struct list_head        queue;    struct spi_message      *cur_msg;    bool                busy;    bool                running;    bool                rt;    int (*prepare_transfer_hardware)(struct spi_master *master);    int (*transfer_one_message)(struct spi_master *master,                    struct spi_message *mesg);    int (*unprepare_transfer_hardware)(struct spi_master *master);};

mode_bits值会在spi.c中做判断:

    bad_bits = spi->mode & ~spi->master->mode_bits;                                                                                                                if (bad_bits) {        dev_err(&spi->dev, "setup: unsupported mode bits %x\n",            bad_bits);        return -EINVAL;    }  

可见,具体的spi设备的mode必须是控制器所支持的,否则错误(模式为0都支持),比如一般片选是0有效,1无效,如果某个设备相反1有效0无效,则该设备驱动中mode位要设置为SPI_CS_HIGH。

struct spi_board_info {    /* the device name and module name are coupled, like platform_bus;     * "modalias" is normally the driver name.     *     * platform_data goes to spi_device.dev.platform_data,     * controller_data goes to spi_device.controller_data,     * irq is copied too     */    char        modalias[SPI_NAME_SIZE]; //设备名字,和驱动中要对应                                                                                                                          const void  *platform_data;    void        *controller_data;//在模拟的控制器中,如果有cs要控制,则为cs对应的gpio     int     irq;    /* slower signaling on noisy or low voltage boards */    u32     max_speed_hz;//最大传输速率    /* bus_num is board specific and matches the bus_num of some     * spi_master that will probably be registered later.     *     * chip_select reflects how this chip is wired to that master;     * it's less than num_chipselect.     */    u16     bus_num;  //挂载那个控制器上,和master中的对应    u16     chip_select; //选择那个引脚作为cs控制脚    /* mode becomes spi_device.mode, and is essential for chips     * where the default of SPI_CS_HIGH = 0 is wrong.     */    u8      mode; //传输模式,    /* ... may need additional spi_device chip config data here.     * avoid stuff protocol drivers can set; but include stuff     * needed to behave without being bound to a driver:     *  - quirks like clock rate mattering when not selected     */};

对于controller_data值的使用,在spi-gpio.c中如下:

static void spi_gpio_chipselect(struct spi_device *spi, int is_active)                                                                                         {    unsigned long cs = (unsigned long) spi->controller_data;    /* set initial clock polarity */    if (is_active)        setsck(spi, spi->mode & SPI_CPOL);    if (cs != SPI_GPIO_NO_CHIPSELECT) {        /* SPI is normally active-low */        gpio_set_value(cs, (spi->mode & SPI_CS_HIGH) ? is_active : !is_active);    }}
struct spi_device {    struct device       dev;//每个spi设备对应一个,和device_drvier匹配    struct spi_master   *master; //对应的控制器,根据bus_num匹配    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];}

spi_board_info信息注册时候创建一个spi_device,代码入下:

struct spi_device *spi_new_device(struct spi_master *master,                  struct spi_board_info *chip){    struct spi_device   *proxy;    int         status;    /* NOTE:  caller did any chip->bus_num checks necessary.     *     * Also, unless we change the return value convention to use     * error-or-pointer (not NULL-or-pointer), troubleshootability     * suggests syslogged diagnostics are best here (ugh).     */    proxy = spi_alloc_device(master);    if (!proxy)        return NULL;    WARN_ON(strlen(chip->modalias) >= sizeof(proxy->modalias));    proxy->chip_select = chip->chip_select;    proxy->max_speed_hz = chip->max_speed_hz;    proxy->mode = chip->mode;    proxy->irq = chip->irq;     strlcpy(proxy->modalias, chip->modalias, sizeof(proxy->modalias));    proxy->dev.platform_data = (void *) chip->platform_data;    proxy->controller_data = chip->controller_data;    proxy->controller_state = NULL;    status = spi_add_device(proxy);    if (status < 0) {        spi_dev_put(proxy);        return NULL;    }    return proxy;}                                                                                                                                                              EXPORT_SYMBOL_GPL(spi_new_device);

内核中提供的gpio模拟spi代码在drivers/spi/spi-gpio.c中,传输逻辑控制在spi-bitbang.c中。按照spi-gpio.c的probe函数实现platform_device代码即可使用模拟spi。

分析到这里已经心中有数了,先把平台的spi gpio配置成普通的输入/输出功能:

[spi2_para]spi_used            = 0spi_cs_bitmap       = 3;--- spi2 mapping0 ---spi_cs0             = port:PC19<1><default><default><1>spi_cs1             = port:PB13<1><default><default><1>spi_sclk            = port:PC20<1><default><default><default>spi_mosi            = port:PC21<1><default><default><default>spi_miso            = port:PC22<0><default><default><default>spi_enable          = port:PB05<1><default><default><1>
[spi_board1]manual_cs               = port:PI07<1><default><default><1>

因为rfid部分代码已经写好了,不想去动它,所以这里把平台注册的所有代码放到了spi keyboard中来,不过这不妨碍代码运行。代码如下:

/* * linux/drivers/misc/spi_keyboard_input.c * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */#include <linux/kernel.h>#include <linux/module.h>#include <linux/slab.h>#include <linux/input.h>#include <linux/init.h>#include <linux/errno.h>#include <linux/delay.h>#include <linux/miscdevice.h>#include <linux/platform_device.h>#include <asm/uaccess.h>#include <linux/device.h>  #include <linux/interrupt.h>  #include <linux/mutex.h>  #include <linux/fs.h>#include <asm/uaccess.h>#include <mach/gpio.h>#include <linux/gpio.h>#include <mach/a20_config.h>#include <linux/timer.h>#include <linux/sched.h>#include <linux/version.h>#include <linux/semaphore.h>#include <mach/sys_config.h>  #include <linux/platform_device.h>#include <linux/spi/spi.h>#include <linux/spi/spi_gpio.h>#include <linux/spi/spi_bitbang.h>#define DEVICE_NAME"spikeyboard-input"#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,36)static DECLARE_MUTEX(sem_rw);//init as 1#elseDEFINE_SEMAPHORE(sem_rw);#endifstruct spi_device *keyboard_spi;static struct input_dev * input;struct workqueue_struct *spikeyboard_wq;struct work_struct spikeyboard_work;static volatile int flags = 1;static char command[3] = {0};//Set the keycode from 1 - 16static int spikeyboard_keycode[] = {KEY_0, KEY_1, KEY_2, KEY_3, KEY_4,     KEY_5, KEY_6, KEY_7, KEY_8, KEY_9,    KEY_Q, KEY_ESC, KEY_MINUS, KEY_EQUAL, KEY_BACKSPACE, KEY_TAB};#define MAX_BUTTON_CNT(sizeof(spikeyboard_keycode)/sizeof(spikeyboard_keycode[0]))static void report_keyvalue(unsigned char buffer){    if (buffer == 0)        buffer = 16;    input_report_key(input, buffer, 1);    input_sync(input);    input_report_key(input, buffer, 0);    input_sync(input);}static void spikeyboard_loop_work(struct work_struct *work)                              {    unsigned char buf_rx[1], buf_tx[1];    int ret;    buf_tx[0] = 0xaa;    while (flags) {        down(&sem_rw);        buf_rx[0] = -1;        ret = spi_write_then_read(keyboard_spi, buf_tx, 1, buf_rx, 1);        up(&sem_rw);        msleep(20);        if ((ret == 0) && (buf_rx[0] != 0xff))            printk("ret:%x\n", buf_rx[0]);            report_keyvalue(buf_rx[0]);    }}static ssize_t spi_keyboard_input_write(struct file *filp, char *buffer,int len){    int i, ret = 0;    struct spi_transfer st[4];    struct spi_message  msg;    char buf_rx[1];    if (len != sizeof(command)) {        printk("command format err\n");        return -EFAULT;    }    if (copy_from_user(command, buffer, len)) {        printk("%s, copy form user err\n", __func__);        return -EFAULT;    }    down(&sem_rw);    spi_message_init(&msg);    memset(st, 0, sizeof(st));    for (i = 0; i < sizeof(command); i++) {        st[i].tx_buf = &command[i];        st[i].len = 1;        spi_message_add_tail(&st[i], &msg);    }    st[3].rx_buf = buf_rx;    st[3].len = 1;    spi_message_add_tail(&st[3], &msg);    spi_sync(keyboard_spi, &msg);    up(&sem_rw);    msleep(20);    switch (buf_rx[0]) {        case 0x88 :            printk("spi slave respone the command\n");            break;        case 0x99 :            printk("spi slave not respone the commnad\n");            ret = -EBUSY;            break;        default :            printk("spi send command err\n");            ret = -EBUSY;    }    return ret;}static int spi_keyboard_input_open(struct inode *inode, struct file *filp){    return 0;}static int spi_keyboard_input_release(struct inode *inode, struct file *filp){    return 0;}static int spi_keyboard_probe(struct spi_device *spi)   {     keyboard_spi = spi;    printk("sclu %s, %d\n", __func__, __LINE__);    spikeyboard_wq = create_singlethread_workqueue("spi_keyboard_work");    INIT_WORK(&spikeyboard_work, spikeyboard_loop_work);    queue_work(spikeyboard_wq, &spikeyboard_work);    return 0;  }static int spi_keyboard_remove(struct spi_device *spi)   {       return 0;   } static struct file_operations spi_keyboard_dev_fops = {    .owner=THIS_MODULE,    .open=spi_keyboard_input_open,    .write=spi_keyboard_input_write,    .release=spi_keyboard_input_release,};static struct miscdevice misc = {    .minor= MISC_DYNAMIC_MINOR,    .name= "spikeyboard-input",    .fops= &spi_keyboard_dev_fops,};static struct spi_driver spi_keyboard_driver = {       .probe = spi_keyboard_probe,       .remove = spi_keyboard_remove,       .driver = {           .name = "spi_keyboard",       },   };  static struct spi_board_info rfid_rc522 = {    .modalias = "rfid_rc522",    //.platform_data = &at25df641_info,    .mode = SPI_MODE_0,    .irq = 0,    .max_speed_hz = 12 * 1000 * 1000,    .bus_num = 10,    .chip_select = 0, //模拟gpio用不到该成员,但是挂在模拟设备上的device还是不能重复该编号,因为spi core会判断重复的话就注册失败};static struct spi_board_info spi_keyboard = {    .modalias = "spi_keyboard",    //.platform_data = &at25df641_info,    .mode = SPI_MODE_0,    .irq = 0,    .max_speed_hz = 12 * 1000 * 1000,    .bus_num = 10,    .chip_select = 1, //模拟gpio用不到该成员};static int spi_gpio_config(char *main_para, char *sub_para){    int ret;    script_item_u list[1];    /* 获取gpio list */    ret = script_get_item(main_para, sub_para, list);    if (SCIRPT_ITEM_VALUE_TYPE_PIO != ret) {        printk("%s: %s get gpio list failed\n",                __func__, sub_para);        return 0;    }    return list[0].gpio.gpio;}static struct spi_gpio_platform_data spi_gpio_data = {    .num_chipselect = 2, //模拟gpio可以为多个,如果是spi控制器,配合chip_select一起使用};static int init_spi_gpio(void) {    char *p_main = "spi2_para";    char *p_sclk = "spi_sclk";    char *p_miso = "spi_miso";    char *p_mosi = "spi_mosi";    struct spi_gpio_platform_data *p = &spi_gpio_data;        p->sck = spi_gpio_config(p_main, p_sclk);    p->miso = spi_gpio_config(p_main, p_miso);    p->mosi = spi_gpio_config(p_main, p_mosi);    rfid_rc522.controller_data = (void *)spi_gpio_config(p_main, "spi_cs0");    spi_keyboard.controller_data = (void *)spi_gpio_config("spi_board1", "manual_cs");    if (!p->sck || !p->miso || !p->mosi)        return -1;    else        return 0;}static struct platform_device spi_gpio_device = {      .name           = "spi_gpio",      .id             = 10,//spi device的编号,设备的bus_num要和这个对应。spi-core也会自动分配编号      .dev.platform_data = &spi_gpio_data,};  static int __init spi_keyboard_init(void){    int ret;    int i;    input = input_allocate_device();    if(!input)         return -ENOMEM;    set_bit(EV_KEY, input->evbit);    for(i = 0; i < MAX_BUTTON_CNT; i++)        set_bit(spikeyboard_keycode[i], input->keybit);    input->name = "spikeyboard-input";    input->phys = "spikeyboard-input/input0";    input->id.bustype = BUS_HOST;    input->id.vendor = 0x0001;    input->id.product = 0x0001;    input->id.version = 0x0100;    input->keycode = spikeyboard_keycode;    if(input_register_device(input) != 0)    {        printk("spi_keyboard-input input register device fail!!\n");        input_free_device(input);        return -ENODEV;    }    ret = misc_register(&misc);    printk(DEVICE_NAME "\tinitialized\n");    init_spi_gpio();    platform_device_register(&spi_gpio_device);    if (spi_register_board_info(&rfid_rc522, 1))        printk("register rfid rc522 board info err\n");    else        printk("register rfid rc522 board info success\n");    if (spi_register_board_info(&spi_keyboard, 1))        printk("register spi_keyboard board info err\n");    else        printk("register spi_keyboard board info success\n");#if 1    ret = spi_register_driver(&spi_keyboard_driver);       if(ret < 0){        printk("register %s fail\n", __FUNCTION__);        return ret;    }#endif    return ret;}static void __exit spi_keyboard_exit(void){    flags = 0;    flush_work_sync(&spikeyboard_work);    destroy_workqueue(spikeyboard_wq);    input_unregister_device(input);    spi_unregister_driver(&spi_keyboard_driver);    misc_deregister(&misc);    platform_device_unregister(&spi_gpio_device);}module_init(spi_keyboard_init);module_exit(spi_keyboard_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("spi_keyboard for A20");

主要是添加了下面几行代码:

    init_spi_gpio();    platform_device_register(&spi_gpio_device);    if (spi_register_board_info(&rfid_rc522, 1))        printk("register rfid rc522 board info err\n");    else        printk("register rfid rc522 board info success\n");    if (spi_register_board_info(&spi_keyboard, 1))        printk("register spi_keyboard board info err\n");    else        printk("register spi_keyboard board info success\n");    ret = spi_register_driver(&spi_keyboard_driver);       if(ret < 0){        printk("register %s fail\n", __FUNCTION__);        return ret;    }

还有要在menuconfig配置中打开spi-gpio.c的配置,这样就可以用模拟spi运行了。同时按键和读写rfid都测试过,速度还是很快的。

和i2c一样,spi子系统也提供了spidev.c驱动让用户空间可以直接操作spi设备;不过有点不同的是,spidev.c中spi_driver的名字固定为"spidev",所以spi_board_info信息中的名字也要更改成“spidev”才能注册并使用spidev.c提供的方法。下面列出重点部分代码:

static struct spi_driver spidev_spi_driver = {     .driver = {         .name =     "spidev",        .owner =    THIS_MODULE,    },      .probe =    spidev_probe,                                                                                                                                      .remove =   __devexit_p(spidev_remove),    /* NOTE:  suspend/resume methods are not necessary here.     * We don't do anything except pass the requests to/from     * the underlying controller.  The refrigerator handles     * most issues; the controller driver handles the rest.     */};

定义好驱动后,注册即可:

    status = spi_register_driver(&spidev_spi_driver);

这样在匹配成功后调用probe方法:

    mutex_lock(&device_list_lock);    minor = find_first_zero_bit(minors, N_SPI_MINORS);    if (minor < N_SPI_MINORS) {        struct device *dev;        spidev->devt = MKDEV(SPIDEV_MAJOR, minor);        dev = device_create(spidev_class, &spi->dev, spidev->devt,                    spidev, "spidev%d.%d",                    spi->master->bus_num, spi->chip_select);        status = IS_ERR(dev) ? PTR_ERR(dev) : 0;    } else {        dev_dbg(&spi->dev, "no minor number available!\n");        status = -ENODEV;    }    if (status == 0) {        set_bit(minor, minors);        list_add(&spidev->device_entry, &device_list);    }    mutex_unlock(&device_list_lock);

重点是device_create创建了/dev/spidevX.X设备节点,把设备spidev添加到&device_list链表中,以便用户空间用open打开设备节点时候从链表中取出对应的spidev。

一般如果用到spidev.c来提供用户空间的操作函数的话,我们在spi_keyboard.c驱动中就用spi_register_board_info注册好信息即可,无需spi_register_driver注册了,这样就把所有的驱动操作方法搬到用户空间来完成。读写可以用标准的read、write来完成,如果要实现像驱动中的spi_write_then_read,则需要用到ioctl,具体如下。

把驱动中的spi结构体和io 命令复制一份到用户空间,先定义好读写结构:

char tx_buffer[128] = {0x0}, rx_buffer[128] = {0x0};    struct spi_ioc_transfer spi_t[2] = {        {            .tx_buf = tx_buffer,            //.rx_buf = rx_buffer,            .len    = 2,                                                                                                                                                   //.speed_hz = 10 * 1000 * 1000,            //.delay_usecs = 10,            //.bits_per_word = 1,            //.cs_change = 0,            //.pad = 0,        },          {               //.tx_buf = tx_buffer,            .rx_buf = rx_buffer,            .len    = 15,            .delay_usecs = 10,        }    };

spi_ioc_transfer的其他成员不用定义,这样会按照驱动默认值配置。根据自己设备的协议情况,改变tx_buf的内容和长度len、接收的长度len即可。接着:

    ret = ioctl(fd, SPI_IOC_MESSAGE(2), spi_t);    if (ret <= 0) {        LOGE("ioctl ret = %d, err = %s", ret, strerror(errno));    }

即可实现等同驱动的spi_write_then_read一次性读写操作,ret返回读写总长度,如上述ret返回15 + 2 = 17。读到的的数据返回在rx_buffer中。

如果要一次多次读写,增加数组spi_t的长度和内容即可。

最美不过偷瞄你是你忽然转头,看见你的微笑

linux SPI驱动

相关文章:

你感兴趣的文章:

标签云: