linux SPI驅動 - 模擬gpio

用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            = 0
spi_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
#else
DEFINE_SEMAPHORE(sem_rw);
#endif

struct 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 - 16
static 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的長度和內容即可。













發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章