如果不想編寫spi設備驅動,那么linux內核提供了一個通用的spidev設備驅動,提供統一的字符設備操作,那么只需要在應用層讀寫和控制即可。以SPI OLED為例子,使用spidev驅動OLED,基于linux5.15.
參考源碼:
tools/spi/spidev_fdx.c
tools/spi/spidev_test.c
1.配置使能spidev用戶態驅動
- > Device Drivers │
- > SPI support
< * > User mode SPI device driver support
驅動源文件:driver/spi/spidev.c
2.編寫設備樹
&ecspi2{
fsl,spi-num-chipselects = < 1 >;
cs-gpios = < &gpio1 29 GPIO_ACTIVE_LOW >;//GPIO1_29
pinctrl-names = "default";
pinctrl-0 = < &pinctrl_ecspi2 >;
status = "okay";
oled: ssd13306@0{
compatible = "Justice,ssd13306";//匹配spidev驅動
spi-cpol;
spi-cpha;
spi-rx-bus-width = < 0 >;
spi-max-frequency = < 20000000 >;
reset-gpios = < &gpio1 27 GPIO_ACTIVE_LOW >;
dc-gpios = < &gpio1 31 GPIO_ACTIVE_HIGH >;
reg = < 0 >;
};
};
注意這里的compatible 屬性,在新版linux內核,可以寫任意的字符串,最好不再寫”spidev”,老版的是要寫成”spidev”。給出的理由是: spidev should never be referenced in DT without a specific compatible string, it is a Linux implementation thing rather than a description of the hardware
3.修改spidev驅動,增加compatible
//driver/spi/spidev.c
static const struct of_device_id spidev_dt_ids[] = {
{ .compatible = "rohm,dh2228fv" },
{ .compatible = "lineartechnology,ltc2488" },
{ .compatible = "semtech,sx1301" },
{ .compatible = "lwn,bk4" },
{ .compatible = "dh,dhcom-board" },
{ .compatible = "menlo,m53cpld" },
{ .compatible = "cisco,spi-petra" },
{ .compatible = "micron,spi-authenta" },
{ .compatible = "Justice,ssd13306" },
{},
};
在最后一行增加自己的匹配compatible。
4.用戶態讀寫、控制spi設備
當設備樹和spidev成功匹配后,就為我們的spi設備生成了一個設備節點/dev/spidevx.y。
x表示spi控制器的軟件枚舉的總線號,y表示這個spi控制器的片選號。
設備樹aliases會影響spi控制器的軟件枚舉的總線號,如我使用ecspi2,芯片上spi控制器的第2個spi控制器,但是我的設備樹上面寫了aliases,因此我呈現的就是/dev/spidev1.0
aliases {
...
spi0 = &ecspi1;
spi1 = &ecspi2;
spi2 = &ecspi3;
spi3 = &ecspi4;
}
/sys/class/spidev下可以確認spidev枚舉出了多少個spi設備
root@imx6ull /sys/class/spidev# ls
spidev1.0
設置傳輸模式
spi 核心會根據spi device的mode標志,來決定一些傳輸的模式,比如時鐘極性、LSB等等
這個標志是32位,低16位是用戶空間設置,高16位是內核控制,因此不能有沖突。如果在設置之前讀取,讀取到的模式和設備樹定義的一樣。
以下是用戶空間的宏定義。
include/uapi/linux/spi/spi.h
#define SPI_CPHA _BITUL(0) /* clock phase */
#define SPI_CPOL _BITUL(1) /* 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_MODE_X_MASK (SPI_CPOL|SPI_CPHA)
#define SPI_CS_HIGH _BITUL(2) /* chipselect active high? */
#define SPI_LSB_FIRST _BITUL(3) /* per-word bits-on-wire */
#define SPI_3WIRE _BITUL(4) /* SI/SO signals shared */
#define SPI_LOOP _BITUL(5) /* loopback mode */
#define SPI_NO_CS _BITUL(6) /* 1 dev/bus, no chipselect */
#define SPI_READY _BITUL(7) /* slave pulls low to pause */
#define SPI_TX_DUAL _BITUL(8) /* transmit with 2 wires */
#define SPI_TX_QUAD _BITUL(9) /* transmit with 4 wires */
#define SPI_RX_DUAL _BITUL(10) /* receive with 2 wires */
#define SPI_RX_QUAD _BITUL(11) /* receive with 4 wires */
#define SPI_CS_WORD _BITUL(12) /* toggle cs after each word */
#define SPI_TX_OCTAL _BITUL(13) /* transmit with 8 wires */
#define SPI_RX_OCTAL _BITUL(14) /* receive with 8 wires */
#define SPI_3WIRE_HIZ _BITUL(15) /* high impedance turnaround */
/*
* All the bits defined above should be covered by SPI_MODE_USER_MASK.
* The SPI_MODE_USER_MASK has the SPI_MODE_KERNEL_MASK counterpart in
* 'include/linux/spi/spi.h'. The bits defined here are from bit 0 upwards
* while in SPI_MODE_KERNEL_MASK they are from the other end downwards.
* These bits must not overlap. A static assert check should make sure of that.
* If adding extra bits, make sure to increase the bit index below as well.
*/
#define SPI_MODE_USER_MASK (_BITUL(16) - 1)