Linux设备驱动模型之SPI

Linux设备驱动模型之SPI

SPI:Serial Peripheral Interface,串行外设接口,主要用于控制器与外部传感器进行数据通信的接口,它是一种同步、全双工、主从式接口。

SPI接口介绍

接口定义

SPI接口有4根信号线,分别是片选信号、时钟信号、串行输出数据线、串行输入数据线。

  1. SS:从设备使能信号,由SPI主设备控制;
  2. SCLK:时钟信号,由主设备产生;
  3. MOSI:主设备数据输出,从设备数据输入;
  4. MISO:主设备数据输入,从设备数据输出;

产生时钟信号的设备为主设备,主设备和从设备之间传输的数据与主机产生的时钟同步。SPI接口只能有一个主设备,可有多个从设备。

SPI

时钟极性与相位

在SPI中,主机可以选择时钟极性和时钟相位。在空闲状态期间,CPOL位设置时钟信号的极性。空闲状态是指传输开始时CS为高电平且在向低电平转变的期间,以及传输结束时CS为低电平且在向高电平转变的期间。CPHA位选择时钟相位。
根据CPHA位的状态,使用时钟上升沿或下降沿来采样和/或移位数据。主机必须根据从机的要求选择时钟极性和时钟相位。根据CPOL和CPHA位的选择,有四种SPI模式可用。

SPI模式 CPOL CPHA 空闲状态下的时钟极性 用于采样和移位数据的时钟相位
0 0 0 逻辑低电平 数据在上升沿采样,在下降沿移出
1 0 1 逻辑低电平 数据在下降沿采样,在上升沿移出
2 1 1 高电平 数据在下降沿采样,在上升沿移出
3 1 0 高电平 数据在上升沿采样,在下降沿移出

上述简单介绍SPI的接口,下面看看Linux中,是如何注册SPI控制器,如何使用SPI与SPI从设备进行通信。

Linux SPI控制器注册

下面以全志平台以Linux4.9内核为基础,介绍SPI控制器是怎么注册到内核。

dts等配置

Linux内核通过设备树描述设备信息,spi设备的描述信息如下:

spi0: spi@04025000 {
    
    
	#address-cells = <1>;
    #size-cells = <0>;
    compatible = "allwinner,sun8i-spi";
    device_type = "spi0";
    reg = <0x0 0x04025000 0x0 0x1000>;				/*spi0控制器配置寄存器信息 */
    interrupts = <GIC_SPI 15 IRQ_TYPE_LEVEL_HIGH>;	/* 描述spi0中断信息 */
    clocks = <&clk_pll_periph0300m>, <&clk_spi0>;	/* 描述spi0时钟配置 */
    clock-frequency = <100000000>;					/* spi0支持的最大时钟 */
    pinctrl-0 = <&spi0_pins_a &spi0_pins_b>;		/* spi0引脚配置信息 */
    pinctrl-1 = <&spi0_pins_c>;
    pinctrl-names = "default", "sleep";
    spi_slave_mode = <0>;							/* spi从模式标志 */
    spi0_cs_number = <1>;							/* spi片选信息 */
    status = "okay";

    spi_board0 {
    
    									/* 挂载到spi0上的从设备 */
        device_type = "spi_board0";
        compatible = "spi-sensor";
        spi-max-frequency = <1000000>;				/* 从设备支持的最大通信频率 */
        reg = <0x0>;
        spi-rx-bus-width=<0x04>;					/* 对从设备读取数据时使用的data线数量 */
        spi-tx-bus-width=<0x04>;					/* 对从设备写入数据时使用的data线数量 */
        status="okay";
    };
};

设备树配置信息之后,开机时系统将会解析设备树,并根据设备树信息注册platform设备,而驱动端将解析dts信息,向内核注册spi控制器,全志平台的spi控制器驱动是drivers/spi/spi-sunxi.c,在该驱动中,将会看到以下代码:

static const struct of_device_id sunxi_spi_match[] = {
    
    
        {
    
     .compatible = "allwinner,sun8i-spi", },
        {
    
     .compatible = "allwinner,sun50i-spi", },
        {
    
    },
};
MODULE_DEVICE_TABLE(of, sunxi_spi_match);

static struct platform_driver sunxi_spi_driver = {
    
    
        .probe   = sunxi_spi_probe,
        .remove  = sunxi_spi_remove,
        .driver = {
    
    
                .name   = SUNXI_SPI_DEV_NAME,
                .owner  = THIS_MODULE,
                .pm             = SUNXI_SPI_DEV_PM_OPS,
                .of_match_table = sunxi_spi_match,
        },
};

static int __init sunxi_spi_init(void)
{
    
    
        return platform_driver_register(&sunxi_spi_driver);
}

static void __exit sunxi_spi_exit(void)
{
    
    
        platform_driver_unregister(&sunxi_spi_driver);
}

fs_initcall_sync(sunxi_spi_init);
module_exit(sunxi_spi_exit);

通过Linux设备驱动模型,我们知道在加载spi-sunxi驱动的时候,将会匹配调用到sunxi_spi_probe()函数,在该函数中进行dts的解析以及spi控制器的登记注册。

static int sunxi_spi_probe(struct platform_device *pdev)
{
    
    
        struct device_node *np = pdev->dev.of_node;
        struct resource *mem_res;
        struct sunxi_spi *sspi;
        struct sunxi_spi_platform_data *pdata;
        struct spi_master *master;
        struct sunxi_slave *slave;

    	...
		/* 从dts中获取知道是哪个spi控制器 */
        pdev->id = of_alias_get_id(np, "spi");
        if (pdev->id < 0) {
    
    
                SPI_ERR("SPI failed to get alias id\n");
                return -EINVAL;
        }

    	...
    	/* 从dts获取spi控制器寄存器信息,实际上就是dts中的reg */
        mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        if (mem_res == NULL) {
    
    
                SPI_ERR("Unable to get spi MEM resource\n");
                ret = -ENXIO;
                goto err0;
        }

    	/* 获取中断号,dts中的interrupts */
        irq = platform_get_irq(pdev, 0);
        if (irq < 0) {
    
    
                SPI_ERR("No spi IRQ specified\n");
                ret = -ENXIO;
                goto err0;
        }

        /* create spi master */
    	/* 创建spi控制器句柄 */
        master = spi_alloc_master(&pdev->dev, sizeof(struct sunxi_spi));
        if (master == NULL) {
    
    
                SPI_ERR("Unable to allocate SPI Master\n");
                ret = -ENOMEM;
                goto err0;
        }

        platform_set_drvdata(pdev, master);
        sspi = spi_master_get_devdata(master);
        memset(sspi, 0, sizeof(struct sunxi_spi));

        sspi->master            = master;
        sspi->mode_type         = MODE_TYPE_NULL;

    	/* 初始化spi控制器所需信息 */
        master->dev.of_node     = pdev->dev.of_node;
        master->bus_num         = pdev->id;				/* 标注spi控制器索引 */
        master->max_speed_hz    = SPI_MAX_FREQUENCY;	/* 支持的最大传输速率 */
        master->setup           = sunxi_spi_setup;		/* spi设备开始传输数据之前可能调用该函数设置模式或时钟 */
        master->can_dma         = sunxi_spi_can_dma;	/* 用于配置使用DMA传输 */
        master->transfer_one    = sunxi_spi_transfer_one;	/* 传输数据函数 */
        master->set_cs          = sunxi_spi_cs_control;	/* 设备片选信号 */
        master->num_chipselect  = pdata->cs_num;		/* 告知有多少个片选信号 */
        master->bits_per_word_mask = SPI_BPW_MASK(8);
        /* the spi->mode bits understood by this driver: */
    	/* 配置spi控制器支持的模式 */
        master->mode_bits       = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LSB_FIRST |
                                SPI_TX_DUAL | SPI_TX_QUAD | SPI_RX_DUAL | SPI_RX_QUAD;

    	...
        snprintf(sspi->dev_name, sizeof(sspi->dev_name), SUNXI_SPI_DEV_NAME"%d", pdev->id);
    	/* 向系统注册spi中断处理函数 */
        err = devm_request_irq(&pdev->dev, irq, sunxi_spi_handler, 0, sspi->dev_name, sspi);
        if (err) {
    
    
                SPI_ERR("[spi%d] Cannot request IRQ\n", sspi->master->bus_num);
                ret = -EINVAL;

            
        ...

        /* 映射spi控制器寄存器 */
        sspi->base_addr = ioremap(mem_res->start, resource_size(mem_res));
        if (sspi->base_addr == NULL) {
    
    
                SPI_ERR("[spi%d] Unable to remap IO\n", sspi->master->bus_num);
                ret = -ENXIO;
                goto err3;
        }
        sspi->base_addr_phy = mem_res->start;

        ...
        /* spi控制器初始化 */
        ret = sunxi_spi_hw_init(sspi, pdata, &pdev->dev);
        if (ret != 0) {
    
    
                SPI_ERR("[spi%d] spi hw init failed!\n", sspi->master->bus_num);
                ret = -EINVAL;
                goto err4;
        }

        /* 注册spi控制器 */
		if (spi_register_master(master)) {
    
    
           SPI_ERR("[spi%d] cannot register SPI master\n", sspi->master->bus_num);
            ret = -EBUSY;
			goto err6;
		}
        ...
}

在经过上述的操作之后,系统已经有一个spi控制器,在/dev目录下将会可以看到spiX节点,但是spi_register_master()函数具体是如何注册的,还没有介绍,下面继续。

spi_register_master

int spi_register_master(struct spi_master *master)
{
    
    
    	...
        /* spi是一个多从设备的接口,所以需要确认片选信息是否都已经有了 */
        status = of_spi_register_master(master);
        if (status)
                return status;

        /* even if it's just one always-selected device, there must
         * be at least one chipselect
         */
        if (master->num_chipselect == 0)
                return -EINVAL;

        if ((master->bus_num < 0) && master->dev.of_node)
                master->bus_num = of_alias_get_id(master->dev.of_node, "spi");

        /* convention:  dynamically assigned bus IDs count down from the max */
        if (master->bus_num < 0) {
    
    
                /* FIXME switch to an IDR based scheme, something like
                 * I2C now uses, so we can't run out of "dynamic" IDs
                 */
                master->bus_num = atomic_dec_return(&dyn_bus_id);
                dynamic = 1;
        }

    	/* 初始化master的各类锁 */
        INIT_LIST_HEAD(&master->queue);
        spin_lock_init(&master->queue_lock);
        spin_lock_init(&master->bus_lock_spinlock);
        mutex_init(&master->bus_lock_mutex);
        mutex_init(&master->io_mutex);
        master->bus_lock_flag = 0;
        init_completion(&master->xfer_completion);
        if (!master->max_dma_len)
                master->max_dma_len = INT_MAX;

    	/* 向系统注册spi master设备:/dev/spiX */
        dev_set_name(&master->dev, "spi%u", master->bus_num);
        status = device_add(&master->dev);
        if (status < 0)
                goto done;
        dev_dbg(dev, "registered master %s%s\n", dev_name(&master->dev),
                        dynamic ? " (dynamic)" : "");

        if (master->transfer)
                dev_info(dev, "master is unqueued, this is deprecated\n");
        else {
    
    
            	/* 初始化控制器的数据传输队列 */
                status = spi_master_initialize_queue(master);
                if (status) {
    
    
                        device_del(&master->dev);
                        goto done;
                }
        }

        mutex_lock(&board_lock);
    	/* 将spi控制器添加到spi_master_list链表 */
        list_add_tail(&master->list, &spi_master_list);
        list_for_each_entry(bi, &board_list, list)
            	/* 从board_list链表中查看是否有匹配该控制器的从设备,实际上就是看spi总线索引是否一致 */
                spi_match_master_to_boardinfo(master, &bi->board_info);
        mutex_unlock(&board_lock);

    	/* 从设备树中解析注册该控制的从设备 */
        of_register_spi_devices(master);

}

在这里,介绍了spi控制器的注册流程,但是spi的从设备又是如何注册的呢?

spi_add_device

在前面,我们介绍了注册spi控制器的时候,将会用过of_register_spi_devices()函数注册挂载到该总线上的spi从设备。of_register_spi_devices()解析dts信息之后,初始化spi_device,然后调用spi_add_device(),而spi_add_device的实现也非常简单。

int spi_add_device(struct spi_device *spi)
{
    
    
        static DEFINE_MUTEX(spi_add_lock);
        struct spi_master *master = spi->master;
        struct device *dev = master->dev.parent;
        int status;
    
    	/* 设置spi从设备名称,一般命令为spiX.Y,
    	 * X是spi控制器索引,而Y则是从设备片选索引
    	 */
        /* Set the bus ID string */
        spi_dev_set_name(spi);

		/* 接下里将会确认从设备的片选信号是否没有被占用,
         * 然后通过spi_setup()配置spi控制器
         */

        /* Device may be bound to an active driver when this returns */
        /* 最后通过device_add()函数添加设备,此时从设备的父设备是spi控制器 */
        status = device_add(&spi->dev);
    
    	return 0}

此时,spi控制器驱动已经注册,spi从设备也已经注册,但是spi从设备如何通过spi接口进行数据通信呢?继续。

spi从设备驱动

Linux系统中,有设备之后,也需要有相应的驱动才可以正常的使用,否则设备也只是一个没用的东西,不能发挥它真实的功能,上面已经注册了spi控制器,spi从设备,可以通过spi从设备操作spi控制器,但是,spi从设备也需要有相应的驱动,才知道应该如何使用,接下里就是给大家介绍这一点。

在上面dts章节,我们已经介绍了spi从设备的信息如下:

spi_board0 {
    
    									/* 挂载到spi0上的从设备 */
    device_type = "spi_board0";
    compatible = "spi_sensor";
    spi-max-frequency = <1000000>;				/* 从设备支持的最大通信频率 */
    reg = <0x0>;
    spi-rx-bus-width=<0x04>;					/* 对从设备读取数据时使用的data线数量 */
    spi-tx-bus-width=<0x04>;					/* 对从设备写入数据时使用的data线数量 */
    status="okay";
};

设备与驱动的compatible信息需要匹配上,才会调用驱动的probe,所以,我们的spi从设备,先这样写:

static int spi_senspr_probe(struct spi_device *spi)
{
    
    
    	/* 此时可以在这里解析上述spi从设备的dts信息,并进行有效的初始化,
         * 比如IMU将会调用 iio_device_register 注册iio设备,应用将通
         * 过 /sys/bus/iio/devices/iio:deviceX 节点最终操作到spi;
         * 比如spi flash,将会调用 mtd_device_register 注册mtd设备;
         * 不同的驱动可以有不一样的需求,实现对用户空间的交互。
         */
    	return 0;
}

static const struct spi_device_id spi_sensor_ids[] = {
    
    
	    {
    
    "spi_sensor", 0},
};

static const struct of_device_id of_match_spi_sensor[] = {
    
    
    	{
    
    .compatible = "spi_sensor"},
};

static struct spi_driver spi_sensor_driver = {
    
    
        .probe = spi_sensor_probe,
        .remove = spi_sensor_remove,
        .driver = {
    
    
				.name = "spi_sensor",
            	.owner = THIS_MODULE,
            	.of_match_table = of_match_spi_sensor,
        },
        .id_table = spi_sensor_ids,
};

static int __init spi_sensor_init(void)
{
    
    
        return spi_register_driver(&spi_sensor_driver);
}
module_init(spi_sensor_init);

上面介绍spi从设备的驱动匹配,匹配之后根据实际情况透传接口给用户空间进行操作,这个差异较大,不详细介绍,下面继续介绍spi从设备中使用到的spi接口。

在介绍驱动使用spi的接口之前,先介绍一些重要的数据结构。

struct spi_transfer,spi传输的读写数据的buffer单元。

struct spi_transfer {
    
    
        const void      *tx_buf;		/* 待发送的数据 */
        void            *rx_buf;		/* 待接收数据的缓冲区 */
        unsigned        len;			/* 数据长度 */

        dma_addr_t      tx_dma;			/* tx_buf的DMA地址 */
        dma_addr_t      rx_dma;			/* rx_buf的DMA地址 */
        struct sg_table tx_sg;
        struct sg_table rx_sg;

        unsigned        cs_change:1;
        unsigned        tx_nbits:3;		/* 多少bit一起发送 */
        unsigned        rx_nbits:3;		/* 一次接收多少bit */
#define SPI_NBITS_SINGLE        0x01 /* 1bit transfer */
#define SPI_NBITS_DUAL          0x02 /* 2bits transfer */
#define SPI_NBITS_QUAD          0x04 /* 4bits transfer */
        u8              bits_per_word;
        u16             delay_usecs;	/* 完成这次传输之后,延时多久才允许进行一下传输 */
        u32             speed_hz;		/* 通信速率 */

        struct list_head transfer_list;
};

struct spi_message,一次数据交互单元。

struct spi_message {
    
    
        struct list_head        transfers;		/* 链表,保存需要进行交互的struct spi_transfer */

        struct spi_device       *spi;

        unsigned                is_dma_mapped:1;	/* 标记tx_dma和rx_dma是否有效 */

        /* REVISIT:  we might want a flag affecting the behavior of the
         * last transfer ... allowing things like "read 16 bit length L"
         * immediately followed by "read L bytes".  Basically imposing
         * a specific message scheduling algorithm.
         *
         * Some controller drivers (message-at-a-time queue processing)
         * could provide that as their default scheduling algorithm.  But
         * others (with multi-message pipelines) could need a flag to
         * tell them about such special cases.
         */

        /* completion is reported through a callback */
        void                    (*complete)(void *context);		/* 异步传输时的传输完成回调函数指针 */
        void                    *context;
        unsigned                frame_length;					/* 帧数据长度 */
        unsigned                actual_length;
        int                     status;							/* 本次传输的完成状态 */
};

一些函数:

函数原型 作用
void spi_message_init(struct spi_message *m) 初始化struct spi_message
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m) 将spi_transfer添加到spi_message
int spi_sync(struct spi_device *spi, struct spi_message *message) 同步传输message
int spi_async(struct spi_device *spi, struct spi_message *message) 异步传输message,传输完成后回调
int spi_read(struct spi_device *spi, void *buf, size_t len) 读数据
int spi_write(struct spi_device *spi, const void *buf, size_t len) 写数据

Linux内核原生自带了一个spi从设备的范例,路径是 drivers/spi/spidev.c,另外用户空间的使用范例也可以参考内核 tools/spi/spidev_test.c。

spi设备进行数据传输时,函数调用过程如下:

SPI数据传输调用过程

整体框架图

SPI驱动框架

参考文章:

SPI通信原理及应用 — Laplaceの拌饭酱

猜你喜欢

转载自blog.csdn.net/weixin_41944449/article/details/133132434
今日推荐