总结

整体

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 这个也要配置下。

发表评论