linux SPI 子系统

1、概述

SPI是”Serial Peripheral Interface” 的缩写,是一种四线制的同步串行通信接口,用来连接微控制器、传感器、存储设备,SPI设备分为主设备从设备两种,用于通信和控制的四根线分别是:

  • CS 片选信号
  • SCK 时钟信号
  • MISO 主设备的数据输入、从设备的数据输出脚
  • MOSI 主设备的数据输出、从设备的数据输入脚
    因为在大多数情况下,CPU或SOC一侧通常都是工作在主设备模式,所以,目前的Linux内核版本中,只实现了主模式的驱动框架。

本文是基于linux-3.0.35内核与imx6q硬件

2、硬件连接

imx6q 支持外挂四个SPI从设备,ECSPI的SPI0支持salve mode,内核没有实现,所以要用这种模式,要自己写驱动才行。

在这里用的从设备是sc16is752, spi转uart的一颗芯片.

3、工作时序

按照时钟信号和数据信号之间的相位关系,SPI有4种工作时序模式:

我们用CPOL表示时钟信号的初始电平的状态,CPOL为0表示时钟信号初始状态为低电平,为1表示时钟信号的初始电平是高电平。另外,我们用CPHA来表示在那个时钟沿采样数据,CPHA为0表示在首个时钟变化沿采样数据,而CPHA为1则表示要在第二个时钟变化沿来采样数据。用CPOL和CPHA的组合来表示当前SPI需要的工作模式:

  • CPOL=0,CPHA=1 模式0
  • CPOL=0,CPHA=1 模式1
  • CPOL=1,CPHA=0 模式2
  • CPOL=1,CPHA=1 模式3

sc16is752 只支持 模式0

4、 sc16is752 操作时序

5、内核相关的文件

这里我们使用linux内核自带的spidev的一个驱动,涉及的所有文件如下:

驱动:

./drivers/spi/spidev.c
./drivers/spi/spidev.h

子系统:

./drivers/spi/spi.c
./include/linux/spi/spi.h

中间层:

./drivers/spi/spi_bitbang.c
./drivers/spi/spi_bitbang.h

底层:

./drivers/spi/spi_imx.c


平台设备注册:

./arch/arm/plat-mxc/devices/platform-spi_imx.c

板级文件:

./arch/arm/mach-mx6/board-mx6q_sabreauto.h
./arch/arm/mach-mx6/board-mx6q_sabreauto.c

5、内核修改

spi的子系统是基于平台设备框架的,所以设备与驱动的名字必须要一致才能匹配成功。

第一步:配置pad

imx6的配置是相当复杂的,一个引脚可以通过mux来配置成不同的功能

static iomux_v3_cfg_t mx6q_sabreauto_pads[] = {
              .
              .
              .
        //ECSPI2
        MX6Q_PAD_DISP0_DAT19__ECSPI2_SCLK,
        MX6Q_PAD_DISP0_DAT17__ECSPI2_MISO,
        MX6Q_PAD_DISP0_DAT16__ECSPI2_MOSI,
        MX6Q_PAD_DISP0_DAT18__ECSPI2_SS0,
        MX6Q_PAD_DISP0_DAT22__GPIO_5_16, //这个引脚可以做为SC16IS752的中断输入(本文不用)
                .
                .
                .
                .

 };

第二步:定义平台信息

static struct spi_board_info imx6_sabresd_spi_uart[] __initdata = {
    {
        .modalias = "sc16is752", //这个别名非常重要,必须要和spidev.c驱动里的名字保持一致
        .max_speed_hz = 1000000,  //最大的速度
        .bus_num = 1,            //这里我们用的ECSPI2,下标要注意
        .chip_select = 0,        //片选,是低电平有效
        .mode = SPI_MODE_0,      //使用的SPI模式0
//      .irq = gpio_to_irq(SPI_UART_IRQ),
    },
};

第三步:初始化平台设备

static void spi_device_init(void)
{
    spi_register_board_info(imx6_sabresd_spi_uart, ARRAY_SIZE(imx6_sabresd_spi_uart));
}

static void __init mx6_board_init(void)
{

   。。。

       /* SPI */
     imx6q_add_ecspi(0, &mx6q_sabreauto_spi1_data);
     imx6q_add_ecspi(1, &mx6q_sabreauto_spi2_data);
     imx6q_add_ecspi(2, &mx6q_sabreauto_spi3_data);
     spi_device_init();

。。。。
}

第四步:驱动更改

static struct spi_driver spidev_spi_driver = {
    .driver = {
        //.name =       "spidev",  
        .name =     "sc16is752", // 这里改为sc16is752,当然改spi_board_info 里的值也是一样的,只要一致
        .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.
     */
};

第五步:测试

内核里提供了spidev驱动的测试代码,位于

./documentation/spi目录下的spidev_test.c

可以先将MISO与MOSI两根张用镊子短接,如果看到收发的数据是一样的则驱动已经正常工作了。剩下的事就是SC16IS752的配置问题了。

6、 关键数据结构分析

若要把子系统分析明白,得从以下几个结构体入手:

6.1、 spi_transfer & spi_message

spi_transfer 结构是一次spi传送,多个spi_transfer 组成了一个spi_message

struct spi_transfer {
    const void  *tx_buf;
    void        *rx_buf;
    unsigned    len;

    dma_addr_t  tx_dma;
    dma_addr_t  rx_dma;

    unsigned    cs_change:1;
    u8      bits_per_word;
    u16     delay_usecs;
    u32     speed_hz;

    struct list_head transfer_list;-------------------------|
};                                                          |
                                                            |
struct spi_message {                                        |
    struct list_head    transfers;--------------------------|

    struct spi_device   *spi;

    unsigned        is_dma_mapped:1;

    /* completion is reported through a callback */
    void            (*complete)(void *context);
    void            *context;
    unsigned        actual_length;
    int         status;

    struct list_head    queue;
    void            *state;
};

spi_transfer与spi_message的关系
linux SPI 子系统_第1张图片

操作接口函数:

static inline void spi_message_init(struct spi_message *m)
static inline void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
static inline void spi_transfer_del(struct spi_transfer *t)

6.2、 spi_master

这个结构代表了一条spi总线

struct spi_master {
    struct device   dev; //继承自device
    struct list_head list;  //spi_register_master 的时候会把spi_master注册到 spi_master_list这个双向链表上
    s16         bus_num; //第几根bus
    u16         num_chipselect; //这个spi master有几个片选信号
    u16         dma_alignment;
    u16         mode_bits;  //spi 协议的mode位
    u16         flags;

    /* lock and mutex for SPI bus locking */
    spinlock_t      bus_lock_spinlock;
    struct mutex        bus_lock_mutex;

    bool            bus_lock_flag;
    // 以下的三个接口都定义在spi_imx.c文件里
    //static int spi_imx_setup(struct spi_device *spi)
    //static int spi_imx_transfer(struct spi_device *spi,struct spi_transfer *transfer)
    //static void spi_imx_cleanup(struct spi_device *spi)
    int         (*setup)(struct spi_device *spi);
    int         (*transfer)(struct spi_device *spi,
                        struct spi_message *mesg);
    void            (*cleanup)(struct spi_device *spi);
};

6.3 、spi_driver & spi_device

struct spi_device {
    struct device       dev;  //继承自device
    struct spi_master   *master;
    u32         max_speed_hz;
    u8          chip_select;
    u8          mode;
    u8          bits_per_word; //一个word多少个bit
    int         irq; 
    void            *controller_state;
    void            *controller_data;
    char            modalias[SPI_NAME_SIZE]; //这个会从spi_board_info的modalias域拷贝过来,驱动与设备匹配的时候会比较
}

//板级初始化的时候会根据spi_board_info ,new一个spi_device出来
struct spi_device *spi_new_device(struct spi_master *master,
                  struct spi_board_info *chip)
{
    struct spi_device   *proxy;
    int         status;

    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; //spi操作模式
    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;
}

spi_driver & spi_device的匹配过程:

它们如匹配的呢?—————spi_imx.c的平台设备驱动注册的时候

struct spi_driver {
    const struct spi_device_id *id_table;
    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;
};

//平台驱动初始化:

static struct platform_driver spi_imx_driver = {
    .driver = {
           .name = DRIVER_NAME,
           .owner = THIS_MODULE,
           },
    .id_table = spi_imx_devtype,
    .probe = spi_imx_probe,
    .remove = __devexit_p(spi_imx_remove),
};

static int __init spi_imx_init(void)
{
    return platform_driver_register(&spi_imx_driver);
}

//platform_driver_register 会调用 bus_add_driver
int bus_add_driver(struct device_driver *drv)
{
        .
        .
        .
    if (drv->bus->p->drivers_autoprobe) {
        error = driver_attach(drv); //here
        if (error)
            goto out_unregister;         
    }
        .
        .
        .
}

static int __driver_attach(struct device *dev, void *data)
{
            .
            .
            .

    if (!driver_match_device(drv, dev)) //在这里会进行匹配
        return 0;

    if (dev->parent)    /* Needed for USB */
        device_lock(dev->parent);
    device_lock(dev);
    if (!dev->driver)
        driver_probe_device(drv, dev); //匹配成功了之后再probe
    device_unlock(dev);
    if (dev->parent)
        device_unlock(dev->parent);

    return 0;
}

static int spi_match_device(struct device *dev, struct device_driver *drv)
{
    const struct spi_device *spi = to_spi_device(dev);
    const struct spi_driver *sdrv = to_spi_driver(drv);

    /* Attempt an OF style match */
    if (of_driver_match_device(dev, drv))
        return 1;

    if (sdrv->id_table)
        return !!spi_match_id(sdrv->id_table, spi);

    return strcmp(spi->modalias, drv->name) == 0; //这里最终是匹配的名字
}


int driver_probe_device(struct device_driver *drv, struct device *dev)
{
        .
        .
        .
    pm_runtime_get_noresume(dev);
    pm_runtime_barrier(dev);
    ret = really_probe(dev, drv); //here
    pm_runtime_put_sync(dev);

    return ret;
}


static int really_probe(struct device *dev, struct device_driver *drv)
{
        .
        .
        .

    if (dev->bus->probe) {
        ret = dev->bus->probe(dev);
        if (ret)
            goto probe_failed;
    } else if (drv->probe) {
        ret = drv->probe(dev); //here
        if (ret)
            goto probe_failed;
    }

        .
        .
        .
}

imx spi平台驱动probe函数:

static int __devinit spi_imx_probe(struct platform_device *pdev)
{
    struct spi_imx_master *mxc_platform_info;
    struct spi_master *master;
    struct spi_imx_data *spi_imx;
    struct resource *res;
    int i, ret;

    mxc_platform_info = dev_get_platdata(&pdev->dev);
    if (!mxc_platform_info) {
        dev_err(&pdev->dev, "can't get the platform data\n");
        return -EINVAL;
    }

    master = spi_alloc_master(&pdev->dev, sizeof(struct spi_imx_data));
    if (!master)
        return -ENOMEM;

    platform_set_drvdata(pdev, master);

    master->bus_num = pdev->id;
    master->num_chipselect = mxc_platform_info->num_chipselect;

    spi_imx = spi_master_get_devdata(master);
    spi_imx->bitbang.master = spi_master_get(master);
    spi_imx->chipselect = mxc_platform_info->chipselect;

    for (i = 0; i < master->num_chipselect; i++) {
        if (spi_imx->chipselect[i] < 0)
            continue;
        ret = gpio_request(spi_imx->chipselect[i], DRIVER_NAME);
        if (ret) {
            while (i > 0) {
                i--;
                if (spi_imx->chipselect[i] >= 0)
                    gpio_free(spi_imx->chipselect[i]);
            }
            dev_err(&pdev->dev, "can't get cs gpios\n");
            goto out_master_put;
        }
    }

    //这里的函数指针初始化比较重要
    spi_imx->bitbang.chipselect = spi_imx_chipselect;
    spi_imx->bitbang.setup_transfer = spi_imx_setupxfer;
    spi_imx->bitbang.txrx_bufs = spi_imx_transfer;
    spi_imx->bitbang.master->setup = spi_imx_setup;
    spi_imx->bitbang.master->cleanup = spi_imx_cleanup;
    spi_imx->bitbang.master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;

    init_completion(&spi_imx->xfer_done);

    spi_imx->devtype_data =
        spi_imx_devtype_data[pdev->id_entry->driver_data];

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res) {
        dev_err(&pdev->dev, "can't get platform resource\n");
        ret = -ENOMEM;
        goto out_gpio_free;
    }

    if (!request_mem_region(res->start, resource_size(res), pdev->name)) {
        dev_err(&pdev->dev, "request_mem_region failed\n");
        ret = -EBUSY;
        goto out_gpio_free;
    }

    spi_imx->base = ioremap(res->start, resource_size(res));
    if (!spi_imx->base) {
        ret = -EINVAL;
        goto out_release_mem;
    }

    spi_imx->irq = platform_get_irq(pdev, 0);
    if (spi_imx->irq < 0) {
        ret = -EINVAL;
        goto out_iounmap;
    }

    /*上面我说没有用到中断,那这个中断什么鬼呢?这个中断是收发数据的SPI中断,上面说的中断是外接的从设备当数据准备好或是其它的情况的时候向IMX6输入的一个电平。*/
    ret = request_irq(spi_imx->irq, spi_imx_isr, 0, DRIVER_NAME, spi_imx);
    if (ret) {
        dev_err(&pdev->dev, "can't get irq%d: %d\n", spi_imx->irq, ret);
        goto out_iounmap;
    }

    spi_imx->clk = clk_get(&pdev->dev, NULL);
    if (IS_ERR(spi_imx->clk)) {
        dev_err(&pdev->dev, "unable to get clock\n");
        ret = PTR_ERR(spi_imx->clk);
        goto out_free_irq;
    }

    clk_enable(spi_imx->clk);
    spi_imx->spi_clk = clk_get_rate(spi_imx->clk);

    spi_imx->devtype_data.reset(spi_imx);

    spi_imx->devtype_data.intctrl(spi_imx, 0);
    ret = spi_bitbang_start(&spi_imx->bitbang);
    if (ret) {
        dev_err(&pdev->dev, "bitbang start failed with %d\n", ret);
        goto out_clk_put;
    }
    clk_disable(spi_imx->clk);

    //最后打印到这里
    dev_info(&pdev->dev, "probed\n");

    return ret;

out_clk_put:
    clk_disable(spi_imx->clk);
    clk_put(spi_imx->clk);
out_free_irq:
    free_irq(spi_imx->irq, spi_imx);
out_iounmap:
    iounmap(spi_imx->base);
out_release_mem:
    release_mem_region(res->start, resource_size(res));
out_gpio_free:
    for (i = 0; i < master->num_chipselect; i++)
        if (spi_imx->chipselect[i] >= 0)
            gpio_free(spi_imx->chipselect[i]);
out_master_put:
    spi_master_put(master);
    kfree(master);
    platform_set_drvdata(pdev, NULL);
    return ret;
}

linux SPI 子系统_第2张图片

7、数据流分析

从上到下分析一个数据怎么从应用层传到寄存器的,这里还是借用内核自己带的spidev驱动

7.1、spidev_test.c

7.1、spidev.c

static int spidev_message(struct spidev_data *spidev,
        struct spi_ioc_transfer *u_xfers, unsigned n_xfers)
{
    struct spi_message  msg;
    struct spi_transfer *k_xfers;
    struct spi_transfer *k_tmp;
    struct spi_ioc_transfer *u_tmp;
    unsigned        n, total;
    u8          *buf;
    int         status = -EFAULT;

    spi_message_init(&msg); //message 初始化
    k_xfers = kcalloc(n_xfers, sizeof(*k_tmp), GFP_KERNEL);
    if (k_xfers == NULL)
        return -ENOMEM;

    buf = spidev->buffer;
    total = 0;
    for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers;
            n;
            n--, k_tmp++, u_tmp++) {
        k_tmp->len = u_tmp->len;

        total += k_tmp->len;
        if (total > bufsiz) {
            status = -EMSGSIZE;
            goto done;
        }

        if (u_tmp->rx_buf) {
            k_tmp->rx_buf = buf;
            if (!access_ok(VERIFY_WRITE, (u8 __user *)
                        (uintptr_t) u_tmp->rx_buf,
                        u_tmp->len))
                goto done;
        }
        if (u_tmp->tx_buf) {
            k_tmp->tx_buf = buf;
            if (copy_from_user(buf, (const u8 __user *)
                        (uintptr_t) u_tmp->tx_buf,
                    u_tmp->len))
                goto done;
        }
        buf += k_tmp->len;

        k_tmp->cs_change = !!u_tmp->cs_change;
        k_tmp->bits_per_word = u_tmp->bits_per_word;
        k_tmp->delay_usecs = u_tmp->delay_usecs;
        k_tmp->speed_hz = u_tmp->speed_hz;

        // 把transfer添加到message的链表上
        spi_message_add_tail(k_tmp, &msg);
    }
     //发送  
    status = spidev_sync(spidev, &msg);
    if (status < 0)
        goto done;

    /* copy any rx data out of bounce buffer */
    buf = spidev->buffer;
    for (n = n_xfers, u_tmp = u_xfers; n; n--, u_tmp++) {
        if (u_tmp->rx_buf) {
            if (__copy_to_user((u8 __user *)
                    (uintptr_t) u_tmp->rx_buf, buf,
                    u_tmp->len)) {
                status = -EFAULT;
                goto done;
            }
        }
        buf += u_tmp->len;
    }
    status = total;

done:
    kfree(k_xfers);
    return status;
}

7.1、spi.c&spi_bitbang.c

linux SPI 子系统_第3张图片

 static void bitbang_work(struct work_struct *work)
{
    struct spi_bitbang  *bitbang =
        container_of(work, struct spi_bitbang, work);
    unsigned long       flags;

    spin_lock_irqsave(&bitbang->lock, flags);
    bitbang->busy = 1;
    while (!list_empty(&bitbang->queue)) { //遍历bitbang的队列
        struct spi_message  *m;
        struct spi_device   *spi;
        unsigned        nsecs;
        struct spi_transfer *t = NULL;
        unsigned        tmp;
        unsigned        cs_change;
        int         status;
        int         do_setup = -1;

        m = container_of(bitbang->queue.next, struct spi_message,
                queue);
        list_del_init(&m->queue);
        spin_unlock_irqrestore(&bitbang->lock, flags);

        /* FIXME this is made-up ... the correct value is known to
         * word-at-a-time bitbang code, and presumably chipselect()
         * should enforce these requirements too?
         */
        nsecs = 100;

        spi = m->spi;
        tmp = 0;
        cs_change = 1;
        status = 0;

        list_for_each_entry (t, &m->transfers, transfer_list) { //遍历message 链表上的所有transfer

            /* override speed or wordsize? */
            if (t->speed_hz || t->bits_per_word)
                do_setup = 1;

            /* init (-1) or override (1) transfer params */
            if (do_setup != 0) {
                status = bitbang->setup_transfer(spi, t); //这里对spi的接口进行配置,因为每个transfer都可以设置 bits_per_word
                if (status < 0)
                    break;
                if (do_setup == -1)
                    do_setup = 0;
            }

            /* set up default clock polarity, and activate chip;
             * this implicitly updates clock and spi modes as
             * previously recorded for this device via setup().
             * (and also deselects any other chip that might be
             * selected ...)
             */
            if (cs_change) {
                bitbang->chipselect(spi, BITBANG_CS_ACTIVE); //这里在imx里没有什么毛线用,拉低电平是通过芯片内部的硬件实现的
                ndelay(nsecs); //这个也没有用
            }
            cs_change = t->cs_change;
            if (!t->tx_buf && !t->rx_buf && t->len) {
                status = -EINVAL;
                break;
            }

            /* transfer data.  the lower level code handles any
             * new dma mappings it needs. our caller always gave
             * us dma-safe buffers.
             */
            if (t->len) {
                /* REVISIT dma API still needs a designated
                 * DMA_ADDR_INVALID; ~0 might be better.
                 */
                if (!m->is_dma_mapped)
                    t->rx_dma = t->tx_dma = 0;
                status = bitbang->txrx_bufs(spi, t); //在这里把数据发出去了,最终调用了static int spi_imx_transfer(struct spi_device *spi,
                struct spi_transfer *transfer)
            }
            if (status > 0)
                m->actual_length += status;
            if (status != t->len) {
                /* always report some kind of error */
                if (status >= 0)
                    status = -EREMOTEIO;
                break;
            }
            status = 0;

            /* protocol tweaks before next transfer */
            if (t->delay_usecs)
                udelay(t->delay_usecs);

            if (!cs_change)
                continue;
            if (t->transfer_list.next == &m->transfers)
                break;

            /* sometimes a short mid-message deselect of the chip
             * may be needed to terminate a mode or command
             */
            ndelay(nsecs);
            bitbang->chipselect(spi, BITBANG_CS_INACTIVE);
            ndelay(nsecs);
        }

        m->status = status;
        m->complete(m->context);

        /* normally deactivate chipselect ... unless no error and
         * cs_change has hinted that the next message will probably
         * be for this chip too.
         */
        if (!(status == 0 && cs_change)) {
            ndelay(nsecs);
            bitbang->chipselect(spi, BITBANG_CS_INACTIVE);
            ndelay(nsecs);
        }

        spin_lock_irqsave(&bitbang->lock, flags);
    }
    bitbang->busy = 0;
    spin_unlock_irqrestore(&bitbang->lock, flags);
}

片选与数据发送之间的延迟:

linux SPI 子系统_第4张图片

最终是要设置ECSPIx_PERIODREG寄存器的CSD CTL域来实现的,看SC16IS752的时序要求,这里可以不去设置,也有几十多us

linux SPI 子系统_第5张图片

最后一步的push是比较复杂的,会产生中断,中断产生后,再接着push

这与ECSPI的操作流程有关

linux SPI 子系统_第6张图片

参考

https://blog.csdn.net/droidphone/article/details/24663659

你可能感兴趣的:(Linux)