跳到主要内容位置

Linux驱动-SPI子系统

参考《百问网-驱动大全》、《【北京迅为】itop-3568 开发板驱动开发指南》。

1.SPI 总线设备驱动模型#

1.1 数据结构#

内核文件参考:include\linux\spi\spi.h

(1)SPI 控制器#

Linux 中使用spi_master结构体描述 SPI 控制器,里面最重要的成员就是transfer函数指针:

(2)SPI 设备#

Linux 中使用spi_device结构体描述 SPI 设备,里面记录有设备的片选引脚、频率、挂在哪个 SPI 控制器下面:

(3)SPI 设备驱动#

Linux 中使用spi_driver结构体描述 SPI 设备驱动:

1.2 SPI 驱动框架#

我们可以先看下平台总线驱动模型,都是类似的思想,将外部设备资源与驱动分开:

SPI 驱动框架:

1.3 SPI 控制器驱动程序#

SPI 控制器的驱动程序可以基于”平台总线设备驱动”模型来实现:

  • 在设备树里描述 SPI 控制器的硬件信息,在设备树子节点里描述挂在下面的 SPI 设备的信息
  • 在 platform_driver 中提供一个 probe 函数
    • 它会注册一个spi_master
    • 还会解析设备树子节点,创建spi_device结构体

1.4 SPI 设备驱动程序#

跟”平台总线设备驱动模型”类似,Linux 中也有一个”SPI 总线设备驱动模型”:

  • 左边是 spi_driver,使用 C 文件实现,里面有 id_table 表示能支持哪些 SPI 设备,有 probe 函数
  • 右边是 spi_device,用来描述 SPI 设备,比如它的片选引脚、频率
    • 可以来自设备树:比如由 SPI 控制器驱动程序解析设备树后创建、注册 spi_device
    • 可以来自 C 文件:比如使用spi_register_board_info创建、注册 spi_device

2.SPI 设备树#

主要就是定义 SPI 控制器与 SPI 设备,在 SPI 控制器驱动程序中就会解析设备树,注册二者。

/**
* struct spi_device - Master side proxy for an SPI slave device
* @dev: Driver model representation of the device.
* @master: SPI controller used with the device.
* @max_speed_hz: Maximum clock rate to be used with this chip
* (on this board); may be changed by the device's driver.
* The spi_transfer.speed_hz can override this for each transfer.
* @chip_select: Chipselect, distinguishing chips handled by @master.
* @mode: The spi mode defines how data is clocked out and in.
* This may be changed by the device's driver.
* The "active low" default for chipselect mode can be overridden
* (by specifying SPI_CS_HIGH) as can the "MSB first" default for
* each word in a transfer (by specifying SPI_LSB_FIRST).
* @bits_per_word: Data transfers involve one or more words; word sizes
* like eight or 12 bits are common. In-memory wordsizes are
* powers of two bytes (e.g. 20 bit samples use 32 bits).
* This may be changed by the device's driver, or left at the
* default (0) indicating protocol words are eight bit bytes.
* The spi_transfer.bits_per_word can override this for each transfer.
* @irq: Negative, or the number passed to request_irq() to receive
* interrupts from this device.
* @controller_state: Controller's runtime state
* @controller_data: Board-specific definitions for controller, such as
* FIFO initialization parameters; from board_info.controller_data
* @modalias: Name of the driver to use with this device, or an alias
* for that name. This appears in the sysfs "modalias" attribute
* for driver coldplugging, and in uevents used for hotplugging
* @cs_gpio: gpio number of the chipselect line (optional, -ENOENT when
* when not using a GPIO line)
*
* @statistics: statistics for the spi_device
*
* A @spi_device is used to interchange data between an SPI slave
* (usually a discrete chip) and CPU memory.
*
* In @dev, the platform_data is used to hold information about this
* device that's meaningful to the device's protocol driver, but not
* to its controller. One example might be an identifier for a chip
* variant with slightly different functionality; another might be
* information about how this particular board wires the chip's pins.
*/
struct spi_device {
struct device dev;
struct spi_master *master;
u32 max_speed_hz;
u8 chip_select;
u8 bits_per_word;
u16 mode;
#define SPI_CPHA 0x01 /* clock phase */
#define SPI_CPOL 0x02 /* clock polarity */
#define SPI_MODE_0 (0|0) /* (original MicroWire) */
#define SPI_MODE_1 (0|SPI_CPHA)
#define SPI_MODE_2 (SPI_CPOL|0)
#define SPI_MODE_3 (SPI_CPOL|SPI_CPHA)
#define SPI_CS_HIGH 0x04 /* chipselect active high? */
#define SPI_LSB_FIRST 0x08 /* per-word bits-on-wire */
#define SPI_3WIRE 0x10 /* SI/SO signals shared */
#define SPI_LOOP 0x20 /* loopback mode */
#define SPI_NO_CS 0x40 /* 1 dev/bus, no chipselect */
#define SPI_READY 0x80 /* slave pulls low to pause */
#define SPI_TX_DUAL 0x100 /* transmit with 2 wires */
#define SPI_TX_QUAD 0x200 /* transmit with 4 wires */
#define SPI_RX_DUAL 0x400 /* receive with 2 wires */
#define SPI_RX_QUAD 0x800 /* receive with 4 wires */
int irq;
void *controller_state;
void *controller_data;
char modalias[SPI_NAME_SIZE];
int cs_gpio; /* chip select gpio */
/* the statistics */
struct spi_statistics statistics;
/*
* likely need more hooks for more protocol options affecting how
* the controller talks to each chip, like:
* - memory packing (12 bit samples into low bits, others zeroed)
* - priority
* - drop chipselect after each word
* - chipselect delays
* - ...
*/
};

各个成员含义如下:

  • max_speed_hz:该设备能支持的 SPI 时钟最大值
  • chip_select:是这个 spi_master 下的第几个设备
    • 在 spi_master 中有一个 cs_gpios 数组,里面存放有下面各个 spi 设备的片选引脚
    • spi_device 的片选引脚就是:cs_gpios[spi_device.chip_select]
  • cs_gpio:这是可选项,也可以把 spi_device 的片选引脚记录在这里
  • bits_per_word:每个基本的 SPI 传输涉及多少位
    • word:我们使用 SPI 控制器时,一般是往某个寄存器里写入数据,SPI 控制器就会把这些数据一位一位地发送出去
    • 一个寄存器是 32 位的,被称为一个 word(有时候也称为 double word)
    • 这个寄存器里多少位会被发送出去?使用 bits_per_word 来表示
    • 扩展:bits_per_word 是可以大于 32 的,也就是每次 SPI 传输可能会发送多于 32 位的数据,这适用于 DMA 突发传输
  • mode:含义广泛,看看结构体里那些宏
    • SPI_CPHA:在第 1 个周期采样,在第 2 个周期采样?
    • SPI_CPOL:平时时钟极性
      • SPI_CPHA 和 SPI_CPOL 组合起来就可以得到 4 种模式
      • SPI_MODE_0:平时 SCK 为低(SPI_CPOL 为 0),在第 1 个周期采样(SPI_CPHA 为 0)
      • SPI_MODE_1:平时 SCK 为低(SPI_CPOL 为 0),在第 2 个周期采样(SPI_CPHA 为 1)
      • SPI_MODE_2:平时 SCK 为高(SPI_CPOL 为 1),在第 1 个周期采样(SPI_CPHA 为 0)
      • SPI_MODE_3:平时 SCK 为高(SPI_CPOL 为 1),在第 2 个周期采样(SPI_CPHA 为 1)
    • SPI_CS_HIGH:一般来说片选引脚时低电平有效,SPI_CS_HIGH 表示高电平有效
    • SPI_LSB_FIRST
      • 一般来说先传输 MSB(最高位),SPI_LSB_FIRST 表示先传 LSB(最低位);
      • 很多 SPI 控制器并不支持 SPI_LSB_FIRST
    • SPI_3WIRE:SO、SI 共用一条线
    • SPI_LOOP:回环模式,就是 SO、SI 连接在一起
    • SPI_NO_CS:只有一个 SPI 设备,没有片选信号,也 6 不需要片选信号
    • SPI_READY:SPI 从设备可以拉低信号,表示暂停、表示未就绪
    • SPI_TX_DUAL:发送数据时有 2 条信号线
    • SPI_TX_QUAD:发送数据时有 4 条信号线
    • SPI_RX_DUAL:接收数据时有 2 条信号线
    • SPI_RX_QUAD:接收数据时有 4 条信号线

2.1 SPI Master#

在设备树中,对于 SPI Master,必须的属性如下:

  • #address-cells:这个 SPI Master 下的 SPI 设备,需要多少个 cell 来表述它的片选引脚
  • #size-cells:必须设置为 0
  • compatible:根据它找到 SPI Master 驱动

可选的属性如下:

  • cs-gpios:SPI Master 可以使用多个 GPIO 当做片选,可以在这个属性列出那些 GPIO
  • num-cs:片选引脚总数

其他属性都是驱动程序相关的,不同的 SPI Master 驱动程序要求的属性可能不一样。

2.2 SPI Device#

在 SPI Master 对应的设备树节点下,每一个子节点都对应一个 SPI 设备,这个 SPI 设备连接在该 SPI Master 下面。

这些子节点中,必选的属性如下:

  • compatible:根据它找到 SPI Device 驱动
  • reg:用来表示它使用哪个片选引脚
  • spi-max-frequency:必选,该 SPI 设备支持的最大 SPI 时钟

可选的属性如下:

  • spi-cpol:这是一个空属性(没有值),表示 CPOL 为 1,即平时 SPI 时钟为低电平
  • spi-cpha:这是一个空属性(没有值),表示 CPHA 为 1),即在时钟的第 2 个边沿采样数据
  • spi-cs-high:这是一个空属性(没有值),表示片选引脚高电平有效
  • spi-3wire:这是一个空属性(没有值),表示使用 SPI 三线模式
  • spi-lsb-first:这是一个空属性(没有值),表示使用 SPI 传输数据时先传输最低位(LSB)
  • spi-tx-bus-width:表示有几条 MOSI 引脚;没有这个属性时默认只有 1 条 MOSI 引脚
  • spi-rx-bus-width:表示有几条 MISO 引脚;没有这个属性时默认只有 1 条 MISO 引脚
  • spi-rx-delay-us:单位是毫秒,表示每次读传输后要延时多久
  • spi-tx-delay-us:单位是毫秒,表示每次写传输后要延时多久

2.3 示例#

spi@f00 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,mpc5200b-spi","fsl,mpc5200-spi";
reg = <0xf00 0x20>;
interrupts = <2 13 0 2 14 0>;
interrupt-parent = <&mpc5200_pic>;
ethernet-switch@0 {
compatible = "micrel,ks8995m";
spi-max-frequency = <1000000>;
reg = <0>;
};
codec@1 {
compatible = "ti,tlv320aic26";
spi-max-frequency = <100000>;
reg = <1>;
};
};

3.spidev 的使用(SPI 用户态 API)#

参考资料:

  • 内核驱动:drivers\spi\spidev.c
  • 内核提供的测试程序:tools\spi\spidev_fdx.c
  • 内核文档:Documentation\spi\spidev

3.1 使用方法介绍#

设备树示例:

spidev0: spidev@0 {
compatible = “spidev”;
reg = <0>;
spi-max-frequency = <50000000>;
};

设备树里某个 spi 设备节点的 compatible 属性等于下列值,就会跟spidev驱动匹配:

  • “rohm,dh2228fv”
  • “lineartechnology,ltc2488”
  • “spidev”

匹配之后,spidev.c 的spidev_probe会被调用,它会:

  • 分配一个 spidev_data 结构体,用来记录对于的 spi_device
  • spidev_data 会被记录在一个链表里
  • 分配一个次设备号,以后可以根据这个次设备号在链表里找到 spidev_data
  • device_create:这会生产一个设备节点/dev/spidevB.D,B 表示总线号,D 表示它是这个 SPI Master 下第几个设备

以后,我们就可以通过/dev/spidevB.D来访问 spidev 驱动程序。

3.2 驱动程序分析#

spidev.c 通过 file_operations 向 APP 提供接口:

(1)读函数#

(2)写函数#

(3)通过 ioctl 读写参数#

(4)通过 ioclt 读写数据#

3.3 应用程序分析#

参考tools\spi\spidev_fdx.c

(1)显示设备属性#

(2)读数据#

(3)先写再读#

(4)同时读写#

3.4 spidev 的缺点#

使用 read、write 函数时,只能读、写,这是半双工方式。

使用 ioctl 可以达到全双工的读写。

但是 spidev 有 2 个缺点:

  • 不支持中断
  • 只支持同步操作,不支持异步操作:就是 read/write/ioctl 这些函数只能执行完毕才可返回

3.5 使用 spidev 操作 DAC 模块#

(1)内部框图#

image-20220309155625021

操作过程为:

  • CS 为低
  • 在 SCLK 的上升沿,从 DIN 采集 16 位数据,存入上图中的16-Bit Shift Register
  • 在 CS 的上升沿,把16-Bit Shift Register中的 10 位数据传入10-Bit DAC Register,作为模拟量在 OUT 引脚输出

注意

  • 传输的 16 位数据中,高 4 位是无意义的
  • 中间 10 位才被转换为模拟量
  • 最低 2 位必须是 0

(2)时序图#

image-20220309160306094

使用 SPI 传输的细节:

  • SCLK 初始电平为低
  • 使用 16 个 SCLK 周期来传输 16 位数据
  • 在 SCLK 上升沿读取 DIN 电平
  • 在 SCLK 上升沿发出 DOUT 信号
  • DOUT 数据来自16-Bit Shift Register
    • 第 1 个数据是上次数据遗留下的 LSB 位
    • 其余 15 个数据来自16-Bit Shift Register的高 15 位
    • 16-Bit Shift Register的 LSB 在下一个周期的第 1 个时钟传输
    • LSB 必定是 0,所以当前的周期里读出16-Bit Shift Register的 15 位数据也足够了

(3)DAC 公式#

输出电压 = 2 * VREFIN * n / 1024 = 2 * 2.048 * n / 1024
其中: n为10位数值

4.SPI 设备驱动程序编写(DAC)#

4.1 编写设备树#

  • 查看原理图,确定这个设备链接在哪个 SPI 控制器下

  • 在设备树里,找到 SPI 控制器的节点

  • 在这个节点下,创建子节点,用来表示 SPI 设备

  • 示例如下:

    &ecspi1 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_ecspi1>;
    fsl,spi-num-chipselects = <2>;
    cs-gpios = <&gpio4 26 GPIO_ACTIVE_LOW>, <&gpio4 24 GPIO_ACTIVE_LOW>;
    status = "okay";
    dac: dac {
    compatible = "100ask,dac";
    reg = <0>;
    spi-max-frequency = <20000000>;
    };
    };

4.2 注册 spi_driver#

SPI 设备的设备树节点,会被转换为一个 spi_device 结构体。

我们需要编写一个 spi_driver 来支持它。(of_match_table 中的一项的 compatible 要和设备树中的 compatible 属性对应)

示例如下:

static const struct of_device_id dac_of_match[] = {
{.compatible = "100ask,spidac"},
{}
};
static struct spi_driver dac_driver = {
.driver = {
.name = "dac",
.of_match_table = dac_of_match,
},
.probe = dac_probe,
.remove = dac_remove,
//.id_table = dac_spi_ids,
};

4.3 怎么发起 SPI 传输#

4.3.1 接口函数#

接口函数都在这个内核文件里:include\linux\spi\spi.h

  • 简易函数

    /**
    * SPI同步写
    * @spi: 写哪个设备
    * @buf: 数据buffer
    * @len: 长度
    * 这个函数可以休眠
    *
    * 返回值: 0-成功, 负数-失败码
    */
    static inline int
    spi_write(struct spi_device *spi, const void *buf, size_t len);
    /**
    * SPI同步读
    * @spi: 读哪个设备
    * @buf: 数据buffer
    * @len: 长度
    * 这个函数可以休眠
    *
    * 返回值: 0-成功, 负数-失败码
    */
    static inline int
    spi_read(struct spi_device *spi, void *buf, size_t len);
/**
* spi_write_then_read : 先写再读, 这是一个同步函数
* @spi: 读写哪个设备
* @txbuf: 发送buffer
* @n_tx: 发送多少字节
* @rxbuf: 接收buffer
* @n_rx: 接收多少字节
* 这个函数可以休眠
*
* 这个函数执行的是半双工的操作: 先发送txbuf中的数据,在读数据,读到的数据存入rxbuf
*
* 这个函数用来传输少量数据(建议不要操作32字节), 它的效率不高
* 如果想进行高效的SPI传输,请使用spi_{async,sync}(这些函数使用DMA buffer)
*
* 返回值: 0-成功, 负数-失败码
*/
extern int spi_write_then_read(struct spi_device *spi,
const void *txbuf, unsigned n_tx,
void *rxbuf, unsigned n_rx);
/**
* spi_w8r8 - 同步函数,先写8位数据,再读8位数据
* @spi: 读写哪个设备
* @cmd: 要写的数据
* 这个函数可以休眠
*
*
* 返回值: 成功的话返回一个8位数据(unsigned), 负数表示失败码
*/
static inline ssize_t spi_w8r8(struct spi_device *spi, u8 cmd);
/**
* spi_w8r16 - 同步函数,先写8位数据,再读16位数据
* @spi: 读写哪个设备
* @cmd: 要写的数据
* 这个函数可以休眠
*
* 读到的16位数据:
* 低地址对应读到的第1个字节(MSB),高地址对应读到的第2个字节(LSB)
* 这是一个big-endian的数据
*
* 返回值: 成功的话返回一个16位数据(unsigned), 负数表示失败码
*/
static inline ssize_t spi_w8r16(struct spi_device *spi, u8 cmd);
/**
* spi_w8r16be - 同步函数,先写8位数据,再读16位数据,
* 读到的16位数据被当做big-endian,然后转换为CPU使用的字节序
* @spi: 读写哪个设备
* @cmd: 要写的数据
* 这个函数可以休眠
*
* 这个函数跟spi_w8r16类似,差别在于它读到16位数据后,会把它转换为"native endianness"
*
* 返回值: 成功的话返回一个16位数据(unsigned, 被转换为本地字节序), 负数表示失败码
*/
static inline ssize_t spi_w8r16be(struct spi_device *spi, u8 cmd);
```
  • 复杂的函数

    /**
    * spi_async - 异步SPI传输函数,简单地说就是这个函数即刻返回,它返回后SPI传输不一定已经完成
    * @spi: 读写哪个设备
    * @message: 用来描述数据传输,里面含有完成时的回调函数(completion callback)
    * 上下文: 任意上下文都可以使用,中断中也可以使用
    *
    * 这个函数不会休眠,它可以在中断上下文使用(无法休眠的上下文),也可以在任务上下文使用(可以休眠的上下文)
    *
    * 完成SPI传输后,回调函数被调用,它是在"无法休眠的上下文"中被调用的,所以回调函数里不能有休眠操作。
    * 在回调函数被调用前message->statuss是未定义的值,没有意义。
    * 当回调函数被调用时,就可以根据message->status判断结果: 0-成功,负数表示失败码
    * 当回调函数执行完后,驱动程序要认为message等结构体已经被释放,不能再使用它们。
    *
    * 在传输过程中一旦发生错误,整个message传输都会中止,对spi设备的片选被取消。
    *
    * 返回值: 0-成功(只是表示启动的异步传输,并不表示已经传输成功), 负数-失败码
    */
    extern int spi_async(struct spi_device *spi, struct spi_message *message);
    /**
    * spi_sync - 同步的、阻塞的SPI传输函数,简单地说就是这个函数返回时,SPI传输要么成功要么失败
    * @spi: 读写哪个设备
    * @message: 用来描述数据传输,里面含有完成时的回调函数(completion callback)
    * 上下文: 能休眠的上下文才可以使用这个函数
    *
    * 这个函数的message参数中,使用的buffer是DMA buffer
    *
    * 返回值: 0-成功, 负数-失败码
    */
    extern int spi_sync(struct spi_device *spi, struct spi_message *message);
    /**
    * spi_sync_transfer - 同步的SPI传输函数
    * @spi: 读写哪个设备
    * @xfers: spi_transfers数组,用来描述传输
    * @num_xfers: 数组项个数
    * 上下文: 能休眠的上下文才可以使用这个函数
    *
    * 返回值: 0-成功, 负数-失败码
    */
    static inline int
    spi_sync_transfer(struct spi_device *spi, struct spi_transfer *xfers,
    unsigned int num_xfers);

5.SPI 设备驱动程序编写(OLED)#

5.1 编写设备树#

相比于 DAC 模块的 spi 通信,oled 多了 D/C 引脚和 RES 复位引脚。

&ecspi1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi1>;
fsl,spi-num-chipselects = <2>;
cs-gpios = <&gpio4 26 GPIO_ACTIVE_LOW>, <&gpio4 24 GPIO_ACTIVE_LOW>;
status = "okay";
oled: oled {
compatible = "100ask,oled";
reg = <0>;
spi-max-frequency = <10000000>;
dc-gpios = <&gpio4 20 GPIO_ACTIVE_HIGH>; // gpiod_set_value(1);
res-gpios = <&gpio4 21 GPIO_ACTIVE_HIGH>;
};
};

5.2 编写驱动程序:注册 spi_driver#

在入口函数里面注册:

static const struct of_device_id spidev_dt_ids[] = {
{ .compatible = "100ask,oled" },
{},
};
static struct spi_driver spidev_spi_driver = {
.driver = {
.name = "100ask_spi_oled_drv",
.of_match_table = of_match_ptr(spidev_dt_ids),
},
.probe = spidev_probe,
.remove = spidev_remove,
};
static int __init spidev_init(void)
{
int status;
status = spi_register_driver(&spidev_spi_driver);
return status;
}
module_init(spidev_init);
static void __exit spidev_exit(void)
{
spi_unregister_driver(&spidev_spi_driver);
}
module_exit(spidev_exit);
MODULE_AUTHOR("cheng");
MODULE_LICENSE("GPL");

这样当设备树和 spi_driver 匹配上,就会调用 probe 函数。

在 probe 函数中,注册字符设备,获取 OLED 的复位引脚 RES,数据/命令选择引脚 DC。

static const struct file_operations spidev_fops = {
.owner = THIS_MODULE,
/* REVISIT switch to aio primitives, so that userspace
* gets more complete API coverage. It'll simplify things
* too, except for the locking.
*/
.write = spidev_write,
.unlocked_ioctl = spidev_ioctl,
};
static void spidev_setup_cdev(int index)
{
int err, devno = MKDEV(g_major, index);
cdev_init(&g_cdev, &spidev_fops);
g_cdev.owner = THIS_MODULE;
err = cdev_add(&g_cdev, devno, 1);
if (err)
printk(KERN_NOTICE "Error %d adding globalmem%d", err, index);
}
static int spidev_probe(struct spi_device *spi)
{
int status;
dev_t devno = MKDEV(g_major, DACDEV_MINOR);
// 记录spi_device
g_oled = spi;
// 注册字符设备
if (g_major) // 分配设备号
status = register_chrdev_region(devno, 1, "100ask_oled");
else
{
status = alloc_chrdev_region(&devno, 0, 1, "100ask_oled");
g_major = MAJOR(devno);
}
if (status < 0)
return status;
spidev_setup_cdev(DACDEV_MINOR);
// 创建字符设备关联的节点
spidev_class = class_create(THIS_MODULE, "spidev");
if (IS_ERR(spidev_class)) {
unregister_chrdev_region(devno, 1);
return PTR_ERR(spidev_class);
}
device_create(spidev_class, NULL, devno, NULL, "100ask_oled");
// 获得GPIO引脚dc res
dc_gpiod = gpiod_get_optional(&spi->dev, "dc", 0);
if (dc_gpiod == NULL) {
printk("gpiod_get_optional dc_gpiod error\n");
return -1;
}
res_gpiod = gpiod_get_optional(&spi->dev, "res", 0);
if (res_gpiod == NULL) {
printk("gpiod_get_optional res_gpiod error\n");
return -1;
}
return 0;
}

5.3 编写驱动程序:ioctl、write(使用 SPI 传输函数)#

在字符设备驱动中,实现 ioctl 和 write,分别负责 OLED 的命令与数据操作。

static ssize_t
spidev_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos)
{
char *ker_buf;
int err;
ker_buf = kmalloc(count, GFP_KERNEL);
if (ker_buf == NULL) return -ENOMEM;
err = copy_from_user(ker_buf, buf, count);
if (err)
{
return -EFAULT;
}
oled_set_dc_pin(1);// 拉高,表示写入数据
spi_write_datas(ker_buf, count);
kfree(ker_buf);
return count;
}
static long
spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int x, y;
/* cmd: */
switch (cmd)
{
case OLED_IOC_INIT: /* init */
dc_pin_init();
res_pin_init();
oled_init();
break;
case OLED_IOC_SET_POS: /* Set_Pos */
x = arg & 0xff;
y = (arg >> 8) & 0xff;
OLED_DIsp_Set_Pos(x, y);
break;
default:
break;
}
return 0;
}

底层调用核心层调用,spi.h的 spi_write()

static void spi_write_datas(const unsigned char *buf, int len)
{
spi_write(g_oled, buf, len);
}
// 实现
static inline int
spi_write(struct spi_device *spi, const void *buf, size_t len)
{
struct spi_transfer t = {
.tx_buf = buf,
.len = len,
};
return spi_sync_transfer(spi, &t, 1);
}
->
static inline int
spi_sync_transfer(struct spi_device *spi, struct spi_transfer *xfers,
unsigned int num_xfers)
{
struct spi_message msg;
spi_message_init_with_transfers(&msg, xfers, num_xfers);
return spi_sync(spi, &msg);
}
->
int spi_sync(struct spi_device *spi, struct spi_message *message)
{
int ret;
mutex_lock(&spi->controller->bus_lock_mutex);
ret = __spi_sync(spi, message);
mutex_unlock(&spi->controller->bus_lock_mutex);
return ret;
}

6.使用 framebuffer 改造 oled#

前面的接口并不是标准接口。

软件,如 QT 和 LVGL,它们只关注 fb,而不关心硬件过程,写入一行数据就对应一行像素。

  • 分配 Framebuffer;
  • 周期性地将 Framebuffer 的数据格式转换为 OLED 显存格式,发送给 OLED 控制器。

在 SPI 驱动程序的 probe 函数中添加,注册 fb 程序#

分配、设置、注册 fb_info 结构体。

  • 分配 fb_info

  • 设置 fb_info

    • fb_var
    • fb_fix
  • 注册 fb_info

  • 硬件操作

  • 创建内核线程:负责刷新 fb 数据到 OLED 上

    • kthread_create:创建内核线程,线程处于"停止状态",要运行它需要执行wake_up_process

    • kthread_run:创建内核线程,并马上让它处于"运行状态"

    • kernel_thread

参考 Ubuntu20.04:/home/cheng/work/100askDriver/SPI/07_oled_use_fb