模块使用范例
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)) {
unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
return PTR_ERR(spidev_class);
}
status = spi_register_driver(&spidev_spi_driver);
if (status < 0) {
class_destroy(spidev_class);
unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
}
return status;
}
module_init(spidev_init);
static void __exit spidev_exit(void)
{
spi_unregister_driver(&spidev_spi_driver);
class_destroy(spidev_class);
unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
}
module_exit(spidev_exit);
同时需要在对应的 spi 控制器的 dts 下加上 spi 子设备的设备信息描述,具体的配置信息如下所示:
&spi1 {
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 {
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";
};
};
对于 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 目录下, 运行如下命令:
然后在 tina/lichee/linux-5.4/tools/spi/下会有 spidev_test 可执行文件,拷贝到小机根文件系统中,运行如下命令即可进行测试:
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)
{
unsigned int i;
unsigned char tmp[len*2], cnt = 0;
for (i = 0; i < len; i++) {
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)) ) {
printf("%s\n", tmp);
cnt = 0;
}
}
}
void batch_rand(char *buf, unsigned int length)
{
unsigned int i;
srand(time(0));
for(i = 0; i < length; i++) {
*(buf + i) = rand() % 256;
}
}
int main(int argc, const char *argv[])
{
unsigned int length = 0, test_len;
char wbuf_head[HEAD_LEN] = {SUNXI_OP_WRITE, 0x00, 0x00, 0x00, 0x00};
char rbuf_head[HEAD_LEN] = {SUNXI_OP_READ, 0x00, 0x00, 0x00, 0x00};
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) {
printf("invalid argument, numbers must less 64B\n");
return -1;
}
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) {
printf("Fail to to open %s\n", DEVICE_NAME);
ret = -1;
return ret;
}
{//write
if (write(fd, wbuf_head, HEAD_LEN) != HEAD_LEN) {
printf("W Fail to write head\n");
ret = -1;
goto err;
} else
printf("W write head successful\n");
usleep(WRITE_DELAY);
if (write(fd, wbuf, test_len) != test_len) {
printf("W Fail to write data\n");
ret = -1;
goto err;
} else
printf("W write data successful\n");
usleep(READ_DELAY);
}
err:
if (fd > 0)
close(fd);
return ret;
}
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)
{
unsigned int i;
unsigned char tmp[len*2], cnt = 0;
for (i = 0; i < len; i++) {
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)) ) {
printf("%s\n", tmp);
cnt = 0;
}
}
}
void batch_rand(char *buf, unsigned int length)
{
unsigned int i;
srand(time(0));
for(i = 0; i < length; i++) {
*(buf + i) = rand() % 256;
}
}
int main(int argc, const char *argv[])
{
unsigned int length = 0, test_len;
char wbuf_head[HEAD_LEN] = {SUNXI_OP_WRITE, 0x00, 0x00, 0x00, 0x00};
char rbuf_head[HEAD_LEN] = {SUNXI_OP_READ, 0x00, 0x00, 0x00, 0x00};
char wbuf[PKT_MAX_LEN], rbuf[PKT_MAX_LEN], i, time;
int fd, ret;
test_len = 10;
if (test_len > PKT_MAX_LEN) {
printf("inval argument, numbers must less 64B\n");
return -1;
}
wbuf_head[4] = test_len;
rbuf_head[4] = test_len;
fd = open(DEVICE_NAME, O_RDWR);
if (fd <= 0) {
printf("Fail to to open %s\n", DEVICE_NAME);
ret = -1;
return ret;
}
{//read
if (write(fd, rbuf_head, HEAD_LEN) != HEAD_LEN) {
printf("R Fail to write head\n");
ret = -1;
goto err;
} else
printf("R write head successful\n");
usleep(READ_DELAY);
if (read(fd, rbuf, test_len) != test_len) {
printf("R Fail to read data\n");
ret = -1;
goto err;
} else
printf("R read data successful\n");
usleep(READ_DELAY);
}
printf("rbuf:\n");
dump_data(rbuf, test_len);
err:
if (fd > 0)
close(fd);
return ret;
}
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 {
pinctrl-0 = <&spi1_pins_a &spi1_pins_b>;
pinctrl-1 = <&spi1_pins_c>;
spi_slave_mode = <0>;
status = "okay";
spi_board1 {
device_type = "spi_board1";
compatible = "rohm,dh2228fv";
spi-max-frequency = <30000000>;
reg = <0x0>;
spi-rx-bus-width = <0x1>;
spi-tx-bus-width = <0x1>;
};
};
注: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:操作码
第 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) {
sunxi_spi_slave_cpu_rx_config(sspi);
} else if (head->op_code == SUNXI_OP_READ) {
sunxi_spi_slave_cpu_tx_config(sspi);
} else {
dprintk(DEBUG_INFO, "[spi%d] pkt head opcode err\n", sspi->master->bus_num);
ret = -1;
goto err1;
}
4.2.3.4.2 地址及缓存
第 2~4 个 Byte 的地址是用于指定读写缓存数据,缓存大小宏在drivers/spi/spi-slave-protocol.h中定义,用户自行设置,单位 Byte
4.2.3.4.3 长度
每次读写数据长度要求小于 64Byte,由于 SPI RX/TX 的 FIFO 缓存大小为 64Byte,为了防止读写时有一端设备没有及时拿走数据导致 buf 溢出,一次传输要求长度小于 64Byte,如果要读写大于 64Byte 数据,可分多次进行传输,地址偏移好就没问题。