总结
整体
spi 分为 soc 上面的 spi master 和 外接的 spi device.
在设备树中,spi 节点本身是 spi master, 子节点就是对应的 spi device.
加载驱动的时候,根据 compitble 识别到 spi master, 并作为 platform 总线设备,匹配到对应的总线驱动。 在其 probe 中,注册 spi 总线master 并识别设备树中的 spi 子节点,并生成 spi 总线设备,并匹配到 spi 总线驱动。
设备树
可以参考内核文档 Documentation\devicetree\bindings\spi\spi-bus.txt
, 有详细的说明。
- deivce 必须提供的三个内容是,compatible, frequecy, reg. reg 用于指明是 cs-gpios 中的第几个。
- master 必须提供的内容是:
-
address-cells:这个SPI Master下的SPI设备,需要多少个cell来表述它的片选引脚
-
size-cells:必须设置为0
- compatible:根据它找到SPI Master驱动
- cs-gpios:SPI Master可以使用多个GPIO当做片选,可以在这个属性列出那些GPIO
- num-cs:片选引脚总数
-
驱动
spidev.c
中的 init 可以看出,并没有直接创建 device, 而是会直接注册 spi device 驱动。 在这个驱动的 probe 中,会根据设备树的情况,来创建 device,并把私有数据结构,挂到spi 链表上,用于后续的 open 来找到私有数据结构。
spidev.c
提供的 read, write 只能用于写或读,ioctrl 只能用与写一个字节后读,如果想要实现复杂功能,需要自己编写驱动。
内核中提供了相关的测试程序,在 tools\spi\spidev_fdx.c
里面。
spi设备使用
设备树
设备树中,compatible = "spidev"
这个会有点警告,不过可以用,因为并不是匹配 of_match_table. 具体的匹配在 spi.c
中的 spi_match_device
函数中 strcmp(spi->modalias, drv->name)
。
内核
内核中的 spidev 需要启用,名称是spi 名目下面的 user xxx.
gpio 控制
gpio 控制一般可以三种方式,
sysfs
接口:简单易用,适合快速测试 GPIO 功能。 类似于命令行方式。libgpiod
库:高效灵活,推荐在生产环境中使用。 需要安装libgpiod
库mmap
直接访问硬件:适合高级用户,需要了解硬件寄存器布局。需要知道寄存器地址,比较麻烦。
sysfs
接口使用方式:
// echo 509 > /sys/class/gpio/export
char cmd[100];
dc_pin_num = number;
sprintf(cmd, "echo %d > /sys/class/gpio/export", number);
system(cmd);
spi 使用 sysfs
控制 dc 引脚,用于数据传输,效率很慢。
自己的 spi 设备驱动
发送数据,首先需要构造 spi_message 作为链表头,然后需要收发多少次数据就构造多少个 spi_transfer,并把 spi_transfer 放入到 spi_message 的链表中。然后使用 spidev_sync 来发送。
如果只发或者只收,可以针对应的在 spi_transfer 中把 rx_buf 或者 tx_buf 设置为 null
自己的设备驱动,可以在 probe 中,直接 register_chrdev
class_create
device_create
一起完成。
自己的 oled spi 设备驱动
dc 使用 gpiod 放在驱动中控制,app 通过 ioctl 进行初始化或设置位置等操作,写入通过 write.
framebuffer 改造 oled
正常的 qt 等程序,使用 framebuffer,像素是横向对应到 fb 里面的字节,app 并不关心具体的控制。
所以需要改造我们的驱动。把以前的 framebuffer 中的 fb_ops 相关的操作移植过来。 都处理好之后,register_framebuffer 注册 fb.
因为 app 只需要把显示的内容放到 fb 中即可。这就需要驱动定期的把 fb 内容更新到屏幕上面,可以使用 timer 或者内核线程来做这个事情。如果用内核线程,那就用 kthread_run
创建,用 schedule_timeout_interruptible
休眠。
dma 传输用到的 buffer,最好是 malloc 出来的,直接全局变量会有一定问题。
spi master
spi device 传输的时候,需要 message 和 transfer。 本质上是创建 message 之后,挂到了 master 的 message 列表中,由 master 来 启动 work 挨个处理传输。
在 work 中,会先从 master 的 message 列表中取出数据,然后启动传输,并休眠等待。 等到终端唤醒这个 work,才会继续向下,唤醒 spi device.
__spi_sync
传输的时候,深层有两种调用,master->transfer == spi_queued_transfer
设置了这个是新方法,就会帮你管理 queue,否则就是老方法,要自己管理 queue 并触发传输。
spi master 老方法
probe 中,先 spi_alloc_master 分配, 然后 配置 master->transfer
传输函数 和 master->dev.of_node
节点,再 INIT_WORK
创建传输用的工作队列,最后 spi_register_master
注册。 如果没有配置 master->dev.of_node
在 sysfs 中会缺少相应的节点信息。
transfer 传输函数,先配置 mesg, 再用 list_add_tail
放入消息队列,最后 schedule_work
启动工作队列。
工作队列函数,轮询检查 list_empty
, 用 list_entry
取出 mesg, list_del_init
清除list节点,最后再清零 mesg->status
, 并 mesg->complete
唤醒。
spi master 新方法
probe 中,先 spi_alloc_master 分配, 再 spi_master_get_devdata
获取 bitbang 然后配置 bitbang, 最后spi_bitbang_start
启动,还有别忘了 init_completion
初始化一下。
bitbang->txrx_bufs
这个对应的传输函数需要实现,先 reinit_completion
初始化,再启动传输,最后 wait_for_completion_timeout
等待传输完成。
在中断处理函数中,complete(&g_xfer_done)
完成并唤醒。
bitbang->chipselect
这个也要配置下。