全志SPI-NG框架使用说明
一、简介
SPI-NG框架,NG为next generation的缩写,是全志新的SPI框架,在新的BSP包中已全部启用。支持SPI 主从模式的配置和使用。
二、设备树配置
sunxi,spi-bus-mode = <SUNXI_SPI_BUS_MASTER>;有两个选项:
- SUNXI_SPI_BUS_MASTER
- SUNXI_SPI_BUS_SLAVE
驱动默认配置为:SUNXI_SPI_BUS_MASTER,当选为SUNXI_SPI_BUS_SLAVE模式时,还可进行SLAVE详细的配置。
dma0:dma-controller@03002000 {compatible = "allwinner,sun8i-dma";reg = <0x0 0x03002000 0x0 0x1000>;interrupts = <GIC_SPI 10 IRQ_TYPE_LEVEL_HIGH>;clocks = <&clk_dma>;#dma-cells = <1>;dma-channels = <24>; //打开SPI相应通道的DMAdma-requests = <64>;status = "okay";
};
spi1: spi@05011000 {#address-cells = <1>;#size-cells = <0>;compatible = "allwinner,sunxi-spi-v0.91";device_type = "spi1";reg = <0x0 0x05011000 0x0 0x1000>;interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>;clocks = <&clk_pll_periph0>, <&clk_spi1>;clock-names = "pll", "mod", "ahb";clock-frequency = <100000000>;pinctrl-names = "default", "sleep";pinctrl-0 = <&spi1_pins_a &spi1_pins_b>;pinctrl-1 = <&spi1_pins_c>;use_dma = <1>;dmas = <&dma0 23>, <&dma0 23>;dma-names = "tx", "rx";sunxi,spi-num-cs = <2>;sunxi,spi-bus-mode = <SUNXI_SPI_BUS_SLAVE>;sunxi,spi-slave-cs = <0>;sunxi,spi-slave-mode = <SUNXI_SPI_SLAVE_CYCLIC_MODE>;sunxi,spi-cs-mode = <0>; //0 auto, 1 softstatus = "disabled";
};
sunxi,spi-slave-mode = <SUNXI_SPI_SLAVE_CYCLIC_MODE>;SLAVE的方式有两个选项:
- SUNXI_SPI_SLAVE_GENERAL_MODE
- SUNXI_SPI_SLAVE_CYCLIC_MODE
驱动默认配置为SUNXI_SPI_SLAVE_GENERAL_MODE
SUNXI_SPI_SLAVE_GENERAL_MODE 为普通的模式,可以用来作为从设备的数据收发。SUNXI_SPI_SLAVE_CYCLIC_MODE 为DMA回环模式,只能用来作为数据的接收,不间断的接收。
三、驱动初始化调用流程
sunxi_spi_init()platform_driver_register()sunxi_spi_probe() //生成struct sunxi_spi *sspi结构体dma_set_mask_and_coherent() //设置 DMA 掩码以告知内核 设备 DMA 寻址功能dma_set_max_seg_size() //设备定义单次 DMA 传输的最大字节数,这里配置的是页大小,4KBsunxi_spi_resource_get() //获取设备树资源,字段一一获取spi_alloc_master()sunxi_spi_request_dma() //获取DMA的收发通道sunxi_spi_hw_init() //硬件配置,包括配置寄存器,时钟,配置为从模式等spi_register_master() //注册SPI控制器,后面的SPI操作就用这个控制器中的函数了sunxi_spi_create_cyclic_dev() //如果是SLAVE_CYCLIC_MODE设备,则创建相关设备sunxi_spi_probe函数执行过程:
sunxi_spi_probe()sunxi_spi_resource_get (获取设备树资源配置)这里有一个获取ready-gpio的配置//这里进行相关函数的配置sspi->ctlr->prepare_message = sunxi_spi_prepare_message;sspi->ctlr->unprepare_message = sunxi_spi_unprepare_message;sspi->ctlr->setup = sunxi_spi_setup;sspi->ctlr->set_cs = sunxi_spi_set_cs;if (sspi->use_dma)sspi->ctlr->can_dma = sunxi_spi_can_dma;sspi->ctlr->transfer_one = sunxi_spi_transfer_one;sspi->ctlr->handle_err = sunxi_spi_handle_err;ret = devm_request_irq(sspi->dev, sspi->irq, sunxi_spi_handler, 0, dev_name(sspi->dev), sspi); //spi本身自己的中断gpio_direction_output(sspi->ready_gpio, 0); //将这个引脚电平输出为低。ready-gpio这个脚就是用来做同步的,主要解决主从双方何时收发数据。 如果没有这个GPIO脚,就是双方自己约定一个休眠时间,发完数据后等待一段时间再处理。
四、主模式(master)数据收发流程
1、发送数据
sunxi_spi_transfer_one()sunxi_spi_xfer_master() (mode_type : SINGLE_HALF_DUPLEX_TX - 2)sunxi_spi_dma_tx() //如果符合使用DMA的条件则使用DMA传输sunxi_spi_start_xfer() //开启传输sunxi_spi_cpu_tx() //如果不符合使用DMA的条件则使用CPU传输wait_for_completion_timeout() //等待数据传输完成sunxi_spi_dma_cb_tx() //如果DMA传输则调这个函数,什么也没有做sunxi_spi_handler()sunxi_spi_bus_handler() //将done complete,唤醒wait操作2、接收数据
sunxi_spi_transfer_one()sunxi_spi_xfer_master() (mode_type : SINGLE_HALF_DUPLEX_RX - 1)sunxi_spi_dma_rx() //如果符合使用DMA的条件则使用DMA传输sunxi_spi_start_xfer()sunxi_spi_cpu_rx() //如果不符合使用DMA的条件则使用CPU传输wait_for_completion_timeout() //等待数据传输完成sunxi_spi_dma_cb_rx() //如果DMA传输则调这个函数,将done complete,唤醒wait操作sunxi_spi_handler()sunxi_spi_bus_handler() //如果CPU传输将done complete,唤醒wait操作五、从模式(slave)数据收发流程
1、接收数据
sunxi_spi_transfer_one()sunxi_spi_xfer_slave() (mode_type : SINGLE_HALF_DUPLEX_RX - 1)sunxi_spi_dma_rx() //如果符合使用DMA的条件则使用DMA传输sunxi_spi_enable_irq() //如果不符合则使能数据ready中断wait_for_completion_interruptible() //等待数据传输完成sunxi_spi_dma_cb_rx() //如果DMA传输完则调这个函数,将done complete,唤醒wait操作sunxi_spi_handler()sunxi_spi_bus_handler() //将done complete,唤醒wait操作sunxi_spi_cpu_rx() //如果不符合使用DMA的条件则使用CPU传输注意:如果是SUNXI_SPI_SLAVE_CYCLIC_MODE模式则不等待数据接收完毕,直接返回了,也就是不会调用wait_for_completion_interruptible函数
2、发送数据
sunxi_spi_transfer_one()sunxi_spi_xfer_slave() (mode_type : SINGLE_HALF_DUPLEX_TX - 2)sunxi_spi_dma_tx() //如果符合使用DMA的条件则使用DMA传输sunxi_spi_enable_irq() //使能数据发送完成中断wait_for_completion_interruptible() //等待数据传输完成sunxi_spi_dma_cb_tx() //如果DMA传输则调这个函数,什么也没有做sunxi_spi_handler()sunxi_spi_bus_handler() //将done complete,唤醒wait操作3、GENERAL_MODE模式
全志提供了spi-sunxi-slave-test.c 设计了一个简单的方案,针对主机先发送操作码,然后发送数据给从机,从机将这个数据存到一个cache中,并通过sys接口给上层去访问。
有个packet head结构,如下:
struct sunxi_spi_slave_head {u8 op_code; //代表操作码u8 bits; //SPI_NBITS_SINGLE 单bit位传输u16 addr; //要操作的地址u16 len; //数据的长度
};
{ 0x03 0x01 0x00 0x00 0x00 0x20 }op_code类型:
#define SUNXI_OP_WR 0x01 /* 这里其实是全双工的方式,MOSI和MISO均有数据 */
#define SUNXI_OP_RD 0x02
#define SUNXI_OP_WR_RD 0x03 /* 这里其实是全双工的方式,MOSI和MISO均有数据 */
#define SUNXI_OP_HEAD 0xff /* Only used in kernel space */数据收发流程:
sunxi_spi_slave_test_submit() //spi-sunxi-slave-test.csunxi_spi_transfer_one() //spi-ng/spi-sunxi.csunxi_spi_xfer_slave()wait_for_completion_interruptible() //一直等待数据传输完sunxi_spi_cpu_rx() //有数据后从寄存器中进行拷贝sunxi_spi_slave_test_complete() //spi-sunxi-slave-test.c (数据传输完,到达complete,此时可以将数据拷贝的驱动的其位置)sunxi_spi_slave_test_submit() //开始下一轮的数据获取测试命令:
(在主设备中运行)半双工收发测试
./spidev_test -D /dev/spidev1.0 -s 10000000 -S 32 -I 1 -v -H -O从设备中执行如下命令,查看收到的数据
hexdump -C /sys/devices/platform/soc/spi1/spi_master/spi1/spi1.0/sunxi-slave-test4、CYCLIC_MODE模式
DMA环形设备 sunxi_spi_create_cyclic_dev,在选择这个模式时会自动创建该设备,对应的操作函数如下:
static const struct file_operations spi_cyclic_dev_fops =
{.open = spi_cyclic_dev_open,.release = spi_cyclic_dev_release,.unlocked_ioctl = spi_cyclic_dev_ioctl,.mmap = spi_cyclic_dev_mmap,
};上层操作如下:
spi_cyclic_dev_ioctl()SPI_CYCLIC_DEV_START设置定时器的timeout创建spi device开始传输数据sunxi_spi_slave_cyclic_dev_hrtimer_handler() //定时执行函数
DMA设定传输的字节数为t->len,PAGE_SIZE,4096字节
dmaengine_tx_status //查看当前传输的状态,state.residue返回剩余传输字节数
current_pos //为当前传输了多少数据
rx_last_pos //上次传输了多少数据如果current_pos不等于rx_last_pos
【1】 如果current_pos大于rx_last_pos,说明有新的数据过来了
【2】 如果current_pos小于rx_last_pos,说明DMA传输完了,存在数据覆盖了sspi->cyclic_dev->t->len - sspi->cyclic_dev->rx_last_pos //上次剩余传输的字节数
sspi->cyclic_dev->buf_size - sspi->cyclic_dev->buf_pos //缓冲区空间剩余的空间sspi->cyclic_dev->buf_size //这是用户设定的大小,测试程序设置的是64KB
sspi->cyclic_dev->user_buf //这是分配的缓冲区
sspi->cyclic_dev->buf_pos //这是缓冲区的当前位置//更新位置
sspi->cyclic_dev->buf_pos += op_len;
sspi->cyclic_dev->rx_last_pos += op_len;总的来说就是不停的接收数据,并把数据放到用户缓冲区中去。
hrtimer_forward_now() //启动下一轮定时器针对这个设备,全志提供了一个测试程序spislave-cyclic-test,对应源代码为:spislave-cyclic-test.c使用命令为(在从设备中运行10us的定时器):
./spislave-cyclic-test -D /dev/spi1_cyclic_dev -t 10000