跳到主要内容

Linux_SPI_DevelopmentGuide-04

4 模块使用范例

4.1 内核原生驱动范例

驱动文件在 drivers/spi/spidev.c,此驱动是 Linux 内核自带的一个 spidev 通用驱动。其中调用 spi_register_driver() 注册 SPI 驱动,方便使用者实现 SPI message 数据的读写。

static int __init spidev_init(void)
{
int status;

/* Claim our 256 reserved device numbers. Then register a class
* that will key udev/mdev to add/remove /dev nodes. Last, register
* the driver which manages those device numbers.
*/
BUILD_BUG_ON(N_SPI_MINORS > 256);
status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);
if (status < 0)
return status;

spidev_class = class_create(THIS_MODULE, "spidev");
if (IS_ERR(spidev_class)) &#123;
unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
return PTR_ERR(spidev_class);
&#125;

status = spi_register_driver(&spidev_spi_driver);
if (status < 0) &#123;
class_destroy(spidev_class);
unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
&#125;
return status;
&#125;
module_init(spidev_init);

static void __exit spidev_exit(void)
&#123;
spi_unregister_driver(&spidev_spi_driver);
class_destroy(spidev_class);
unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
&#125;
module_exit(spidev_exit);

同时需要在对应的 spi 控制器的 dts 下加上 spi 子设备的设备信息描述,具体的配置信息如下所示:

&spi1 &#123;
clock-frequency = <100000000>;
pinctrl-0 = <&spi1_pins_a &spi1_pins_b>;
pinctrl-1 = <&spi1_pins_c>;
pinctrl-names = "default", "sleep";
spi_slave_mode = <0>;
status = "disabled";

spi_board1@0 &#123;
device_type = "spi_board1";
compatible = "rohm,dh2228fv";
spi-max-frequency = <0x5f5e100>;
reg = <0x0>;
spi-rx-bus-width = <0x4>;
spi-tx-bus-width = <0x4>;
status = "disabled";
&#125;;
&#125;;

对于 spi 控制器的描述在这里不再重复的陈述,这里的 spi_board1@0 就是我们虚拟的一个 spi 从设备,

device_type :表示设备的类型;

compatible :驱动匹配信息;

spi-max-frequency :从设备的最大频率;

reg :从设备的寄存器地址;

spi-rx-bus-width:对从设备进行数据读取时使用的 data 数据线个数;

spi-tx-bus-width :对从设备进行数据写入时使用的 data 数据线个数;

status :从设备的状态;

在 menuconfig(Device Drivers->SPI support)里面配置上 User mode SPI device driver support 选项。

​ 图 4-1: spidev

编译烧录固件之后会在小机文件系统的/dev 目录下发现 spidevX.0(X=0~2) 设备,可以对 spidevX.0 进行读写操作。或者使用 Linux 自带的 spi 工具:在 tina/lichee/linux-5.4/tools 目录下, 运行如下命令:

make spi

然后在 tina/lichee/linux-5.4/tools/spi/下会有 spidev_test 可执行文件,拷贝到小机根文件系统中,运行如下命令即可进行测试:

/spidev_test -D /dev/spidevX.0

4.2 Slave 模式驱动范例

需要在 board.dts 中相应的 SPI 节点设备配置 spi_slave_mode = <1>。

4.2.1 Slave 写数据

以 spidev1.0 设备为例,发送 0~9 十个数据:

#define DEVICE_NAME "/dev/spidev1.0"
#define HEAD_LEN 5
#define PKT_MAX_LEN 0x40
#define STATUS_LEN 0x01

#define SUNXI_OP_WRITE 0x01
#define SUNXI_OP_READ 0x03

#define STATUS_WRITABLE 0x02
#define STATUS_READABLE 0x04

#define WRITE_DELAY 200
#define READ_DELAY 100000

void dump_data(unsigned char *buf, unsigned int len)
&#123;
unsigned int i;
unsigned char tmp[len*2], cnt = 0;

for (i = 0; i < len; i++) &#123;
if (i%0x10== 0)
cnt += sprintf(tmp + cnt, "0x%08x: ", i);

cnt += sprintf(tmp + cnt, "%02x ", buf[i]);

if ( (i%0x10== 0x0f) || (i == (len -1)) ) &#123;
printf("%s\n", tmp);
cnt = 0;
&#125;
&#125;
&#125;

void batch_rand(char *buf, unsigned int length)
&#123;
unsigned int i;
srand(time(0));

for(i = 0; i < length; i++) &#123;
*(buf + i) = rand() % 256;
&#125;
&#125;

int main(int argc, const char *argv[])
&#123;
unsigned int length = 0, test_len;
char wbuf_head[HEAD_LEN] = &#123;SUNXI_OP_WRITE, 0x00, 0x00, 0x00, 0x00&#125;;
char rbuf_head[HEAD_LEN] = &#123;SUNXI_OP_READ, 0x00, 0x00, 0x00, 0x00&#125;;
char wbuf[PKT_MAX_LEN], rbuf[PKT_MAX_LEN], i, time;
int fd, ret;

test_len = 10;//send 10 numbers
if (test_len > PKT_MAX_LEN) &#123;
printf("invalid argument, numbers must less 64B\n");
return -1;
&#125;

wbuf_head[4] = test_len;
rbuf_head[4] = test_len;

for (i = 0; i < test_len; i++)
wbuf[i] = i;
printf("wbuf:\n");
dump_data(wbuf, test_len);

fd = open(DEVICE_NAME, O_RDWR);
if (fd <= 0) &#123;
printf("Fail to to open %s\n", DEVICE_NAME);
ret = -1;
return ret;
&#125;

&#123;//write
if (write(fd, wbuf_head, HEAD_LEN) != HEAD_LEN) &#123;
printf("W Fail to write head\n");
ret = -1;
goto err;
&#125; else
printf("W write head successful\n");

usleep(WRITE_DELAY);

if (write(fd, wbuf, test_len) != test_len) &#123;
printf("W Fail to write data\n");
ret = -1;
goto err;
&#125; else
printf("W write data successful\n");

usleep(READ_DELAY);
&#125;

err:
if (fd > 0)
close(fd);

return ret;
&#125;

4.2.2 Slave 读数据

以 spidev1.0 设备为例,读十个数据:

#define DEVICE_NAME "/dev/spidev1.0"
#define HEAD_LEN 5
#define PKT_MAX_LEN 0x40
#define STATUS_LEN 0x01

#define SUNXI_OP_WRITE 0x01
#define SUNXI_OP_READ 0x03

#define STATUS_WRITABLE 0x02
#define STATUS_READABLE 0x04

#define WRITE_DELAY 200
#define READ_DELAY 100000

void dump_data(unsigned char *buf, unsigned int len)
&#123;
unsigned int i;
unsigned char tmp[len*2], cnt = 0;

for (i = 0; i < len; i++) &#123;
if (i%0x10== 0)
cnt += sprintf(tmp + cnt, "0x%08x: ", i);

cnt += sprintf(tmp + cnt, "%02x ", buf[i]);

if ( (i%0x10== 0x0f) || (i == (len -1)) ) &#123;
printf("%s\n", tmp);
cnt = 0;
&#125;
&#125;
&#125;

void batch_rand(char *buf, unsigned int length)
&#123;
unsigned int i;
srand(time(0));

for(i = 0; i < length; i++) &#123;
*(buf + i) = rand() % 256;
&#125;
&#125;

int main(int argc, const char *argv[])
&#123;
unsigned int length = 0, test_len;
char wbuf_head[HEAD_LEN] = &#123;SUNXI_OP_WRITE, 0x00, 0x00, 0x00, 0x00&#125;;
char rbuf_head[HEAD_LEN] = &#123;SUNXI_OP_READ, 0x00, 0x00, 0x00, 0x00&#125;;
char wbuf[PKT_MAX_LEN], rbuf[PKT_MAX_LEN], i, time;
int fd, ret;

test_len = 10;
if (test_len > PKT_MAX_LEN) &#123;
printf("inval argument, numbers must less 64B\n");
return -1;
&#125;

wbuf_head[4] = test_len;
rbuf_head[4] = test_len;

fd = open(DEVICE_NAME, O_RDWR);
if (fd <= 0) &#123;
printf("Fail to to open %s\n", DEVICE_NAME);
ret = -1;
return ret;
&#125;

&#123;//read
if (write(fd, rbuf_head, HEAD_LEN) != HEAD_LEN) &#123;
printf("R Fail to write head\n");
ret = -1;
goto err;
&#125; else
printf("R write head successful\n");

usleep(READ_DELAY);

if (read(fd, rbuf, test_len) != test_len) &#123;
printf("R Fail to read data\n");
ret = -1;
goto err;
&#125; else
printf("R read data successful\n");

usleep(READ_DELAY);

&#125;

printf("rbuf:\n");
dump_data(rbuf, test_len);

err:
if (fd > 0)
close(fd);

return ret;
&#125;

4.2.3 Slave 使用 & 测试

4.2.3.1 环境搭建
4.2.3.1.1 硬件环境

本此测试使用两块开发板搭建环境,一块做 master,一块做 slave。

将 MASTER 与 SLAVE 的 SPI1 的 CS、CLK 按名字对应连接起来,MASTER 的 MOSI 接SLAVE 的 MOSI,MASTER 的 MISO 接 SLAVE 的 MISO,将两块开发板共地。

4.2.3.1.2 Menuconfig

打 开 menuconfig 的 CONFIG_SPI_SUNXI 与 CONFIG_SPI_SPIDEV,如下图所示。

​ 图 4-2: menuconfig

4.2.3.1.3 DTS

设备树路径:device/config/chips/xxx(t507)/configs/xxx(demo2.0)/board.dts,添加以下节点:

spi1: spi@05011000 &#123;
pinctrl-0 = <&spi1_pins_a &spi1_pins_b>;
pinctrl-1 = <&spi1_pins_c>;
spi_slave_mode = <0>;
status = "okay";
spi_board1 &#123;
device_type = "spi_board1";
compatible = "rohm,dh2228fv";
spi-max-frequency = <30000000>;
reg = <0x0>;
spi-rx-bus-width = <0x1>;
spi-tx-bus-width = <0x1>;
&#125;;
&#125;;

注:spi_slave_mode = <0> 为 Master 配置;spi_slave_mode = <1>,为 Slave 配置

4.2.3.2 测试

分别设置 Master 和 Salve 的 DTS,并编译出对应固件,烧写固件。

4.2.3.2.1 Slave

Slave 端执行下列命令,打开 Slave 的调试打印,这样可以看到读写的数据。

4.2.3.3 测试结果

Maset source data 和 target data 打印数据一致,即表明测试通过。

--------------------------------------------
n test
--------------------------------------------
W write head successful
W write data successful
source data:
0x00000000: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a
0x00000010: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a
R write head successful
R read data successful
target data:
0x00000000: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a
0x00000010: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a
slave function [PASS]
4.2.3.4 自定制说明

用户可以自定制从设备功能,要操作从设备,需要发送 5 个 byte 的操作请求,说明如下:

第 1 个 Byte:操作码

SUNXI_OP_WRITE 0x01
SUNXI_OP_READ 0x03
//读写是相对于master

第 2~4 个 Byte:地址(2 是高位地址)

第 5 给 Byte:长度(长度要求小于 64Byte)

4.2.3.4.1 操作码添加

现在我们只支持读写操作,用户自行拓展,在drivers/spi/spi-sunxi.c 的sunxi_spi_slave_handle_head函数中添加命令对应的操作函数

if (head->op_code == SUNXI_OP_WRITE) &#123;
sunxi_spi_slave_cpu_rx_config(sspi);
&#125; else if (head->op_code == SUNXI_OP_READ) &#123;
sunxi_spi_slave_cpu_tx_config(sspi);
&#125; else &#123;
dprintk(DEBUG_INFO, "[spi%d] pkt head opcode err\n", sspi->master->bus_num);
ret = -1;
goto err1;
&#125;
4.2.3.4.2 地址及缓存

第 2~4 个 Byte 的地址是用于指定读写缓存数据,缓存大小宏在drivers/spi/spi-slave-protocol.h中定义,用户自行设置,单位 Byte

#define STORAGE_SIZE 128

4.2.3.4.3 长度

每次读写数据长度要求小于 64Byte,由于 SPI RX/TX 的 FIFO 缓存大小为 64Byte,为了防止读写时有一端设备没有及时拿走数据导致 buf 溢出,一次传输要求长度小于 64Byte,如果要读写大于 64Byte 数据,可分多次进行传输,地址偏移好就没问题。