linux SPI subsystem

1 Overview

SPI is an abbreviation for "Serial Peripheral Interface", and is a synchronous serial communication interface is a four-wire, used to connect microcontrollers, sensors, memory devices, SPI device is divided into a master device and slave device are two, for communication The four lines of control and control are:

  • CS chip select signal
  • SCK clock signal
  • Data input of MISO master device, data output pin of slave device
  • MOSI data output of the main device, from the data input pin of the device
    because in most cases, CPU, or SOC side usually works in master mode, therefore, the current versions of the Linux kernel, the drive motor is only master mode frame.

This article is based on linux-3.0.35 kernel and imx6q hardware

2. Hardware connection

Write picture description here

imx6q supports four external SPI slave devices. The SPI0 of ECSPI supports the salve mode, which is not implemented by the kernel, so if you want to use this mode, you have to write the driver yourself.

The slave device used here is sc16is752, a chip from spi to uart.

3. Working sequence

According to the phase relationship between the clock signal and the data signal, SPI has 4 working timing modes:

Write picture description here

We use CPOL to represent the state of the initial level of the clock signal, a CPOL of 0 indicates that the initial state of the clock signal is low, and a value of 1 indicates that the initial level of the clock signal is high. In addition, we use CPHA to indicate that the data is sampled at that clock edge. A CPHA of 0 means that the data is sampled on the first clock edge, and a CPHA of 1 means that the data is sampled on the second clock edge. Use the combination of CPOL and CPHA to represent the current working mode required by SPI:

  • CPOL=0, CPHA=1 Mode 0
  • CPOL=0, CPHA=1 Mode 1
  • CPOL=1, CPHA=0 Mode 2
  • CPOL=1, CPHA=1 Mode 3

sc16is752 only supports mode 0

4. sc16is752 operation timing

Write picture description here

5. Kernel-related files

Here we use a spidev driver that comes with the linux kernel, and all the files involved are as follows:

drive:

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

Subsystem:

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

middle layer:

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

Bottom:

./drivers/spi/spi_imx.c


Platform device registration:

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

Board-level files:

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

5. Kernel modification

The SPI subsystem is based on the platform device framework , so the names of the device and the driver must be consistent to match successfully.

Step 1: Configure pad

The configuration of imx6 is quite complicated, a pin can be configured into different functions through 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的中断输入(本文不用)
                .
                .
                .
                .

 };

Step 2: Define platform information

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),
    },
};

Step 3: Initialize the platform equipment

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();

。。。。
}

Step 4: Drive changes

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.
     */
};

Step 5: Test

The test code of the spidev driver is provided in the kernel, located at

spidev_test.c in the ./documentation/spi directory

You can first short-circuit the MISO and MOSI with tweezers. If you see that the data sent and received are the same, the driver has been working normally. The rest is the configuration of SC16IS752.

6. Analysis of key data structure

If you want to understand the analysis of the subsystem, you have to start with the following structures:

6.1、 spi_transfer & spi_message

The spi_transfer structure is a spi transfer, multiple spi_transfers form a 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;
};

The relationship between spi_transfer and spi_message

Operation interface function:

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

This structure represents a spi bus

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;
}

The matching process of spi_driver & spi_device:

How do they match? ————— When the platform device driver of spi_imx.c is registered

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;
};

//Platform driver initialization:

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;
    }

        .
        .
        .
}

The probe function driven by imx spi platform:

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;
}

Write picture description here

7. Data flow analysis

Analyze from top to bottom how a piece of data is transferred from the application layer to the register, here is to borrow the spidev driver brought by the kernel itself

7.1、spidev_test.c

Write picture description here

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

Write picture description here

 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);
}

Delay between chip selection and data transmission:

Write picture description here

In the end, it is achieved by setting the CSD CTL field of the ECSPIx_PERIODREG register. See the timing requirements of SC16IS752. There is no need to set it here. There are also dozens of us.

Write picture description here

The push in the last step is more complicated and will generate an interrupt. After the interrupt is generated, then push

This is related to the operation process of ECSPI

Write picture description here

reference

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

Guess you like

Origin blog.csdn.net/amwha/article/details/80126842