一. 简介
SPI 驱动框架和 I2C 很类似,都分为主机控制器驱动和设备驱动,主机控制器也就是 SOC的 SPI 控制器接口。
前一篇文章简单学习了Linux下SPI主控的驱动。文章如下:
本文来简单分析一下IMX6ULL(这个SOC)的 SPI主机控制器的驱动。
二. Linux下SPI驱动:I.MX6U SPI 主机驱动分析
和
I2C
的适配器驱动一样,
SPI
主机驱动一般都由
SOC
厂商编写好了,打开
imx6ull.dtsi 文件,找到如下所示内容:
ecspi3: ecspi@02010000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";
reg = <0x02010000 0x4000>;
interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_ECSPI3>,
<&clks IMX6UL_CLK_ECSPI3>;
clock-names = "ipg", "per";
dmas = <&sdma 7 7 1>, <&sdma 8 7 2>;
dma-names = "rx", "tx";
status = "disabled";
};
重点来看一下第
4
行的
compatible
属性值,
compatible
属性有两个值“
fsl,imx6ul-ecspi
”和
“
fsl,imx51-ecspi
”。在
Linux
内核源码中搜素这两个属性值即可找到
I.MX6U
对应的
ECSPI(SPI)
主机驱动。
I.MX6U
的
ECSPI
主机驱动文件为
drivers/spi/spi-imx.c
,在此文件中找到如下内容:
static struct platform_device_id spi_imx_devtype[] = {
{
.name = "imx1-cspi",
.driver_data = (kernel_ulong_t) &imx1_cspi_devtype_data,
}, {
.name = "imx21-cspi",
.driver_data = (kernel_ulong_t) &imx21_cspi_devtype_data,
..................
}, {
.name = "imx6ul-ecspi",
.driver_data = (kernel_ulong_t) &imx6ul_ecspi_devtype_data,
}, {
/* sentinel */
}
};
static const struct of_device_id spi_imx_dt_ids[] = {
{ .compatible = "fsl,imx1-cspi", .data = &imx1_cspi_devtype_data, },
{ .compatible = "fsl,imx21-cspi", .data = &imx21_cspi_devtype_data, },
....................
{ .compatible = "fsl,imx6ul-ecspi", .data = &imx6ul_ecspi_devtype_data, },
{ /* sentinel */ }
};
.......................
static struct platform_driver spi_imx_driver = {
.driver = {
.name = DRIVER_NAME,
.of_match_table = spi_imx_dt_ids,
.pm = IMX_SPI_PM,
},
.id_table = spi_imx_devtype,
.probe = spi_imx_probe,
.remove = spi_imx_remove,
};
module_platform_driver(spi_imx_driver);
第 1
行,
spi_imx_devtype
为
SPI
无设备树匹配表。
第 17
行,
spi_imx_dt_ids
为
SPI
设备树匹配表。
第
21
行,“
fsl,imx6ul-ecspi
” 匹配项,因此,可知
I.MX6U
的
ECSPI
驱动就是
spi-imx.c
这个文件。
第
26~35
行,
platform_driver
驱动框架,和
I2C
的适配器驱动一样,
SPI
主机驱动器采用了
platfom
驱动框架。
当设备和驱动匹配成功以后,
spi_imx_probe
函数就会执行。
spi_imx_probe
函数会从设备树中读取相应的节点属性值,申请并初始化
spi_master
,最后
调用
spi_bitbang_start
函数
(spi_bitbang_start
会调用
spi_register_master
函数
)
向
Linux
内核注册
spi_master
。
对于
I.MX6U
来讲,
SPI
主机的最终数据收发函数为
spi_imx_transfer
,此函数通过如下层层调用最终实现
SPI
数据发送:
spi_imx_transfer
-> spi_imx_pio_transfer
-> spi_imx_push
-> spi_imx->tx
spi_imx 是个 spi_imx_data 类型的机构指针变量,其中 tx 和 rx 这两个成员变量分别为 SPI 数据发送和接收函数。
I.MX6U SPI 主机驱动会维护一个 spi_imx_data 类型的变量 spi_imx,并且使用 spi_imx_setupxfer 函数来设置 spi_imx 的 tx 和 rx 函数。根据要发送的数据数据位宽的不同,分别有 8 位、16 位和 32 位的发送函数,如下所示:
spi_imx_buf_tx_u8
spi_imx_buf_tx_u16
spi_imx_buf_tx_u32
同理,也有
8
位、
16
位和
32
位的数据接收函数,如下所示:
spi_imx_buf_rx_u8
spi_imx_buf_rx_u16
spi_imx_buf_rx_u32
我们就以
spi_imx_buf_tx_u8
这个函数为例,看看一个自己的数据发送是怎么完成的,在 spi-imx.c
文件中找到如下所示内容:
#define MXC_SPI_BUF_TX(type) \
static void spi_imx_buf_tx_##type(struct spi_imx_data *spi_imx) \
{ \
type val = 0; \
\
if (spi_imx->tx_buf) { \
val = *(type *)spi_imx->tx_buf; \
spi_imx->tx_buf += sizeof(type); \
} \
\
spi_imx->count -= sizeof(type); \
\
writel(val, spi_imx->base + MXC_CSPITXDATA); \
}
MXC_SPI_BUF_RX(u8)
MXC_SPI_BUF_TX(u8)
可以看出,
spi_imx_buf_tx_u8
函数是通过
MXC_SPI_BUF_TX
宏来实现的。
第
13
行就是将要发送的数据值写入到
ECSPI
的
TXDATA
寄存器里面去,这和我们
SPI
裸
机实验的方法一样。
将第
17
行的
MXC_SPI_BUF_TX(u8)
展开,就是
spi_imx_buf_tx_u8
函数。
其他的
tx
和
rx
函数都是这样实现的,这里就不做介绍了。关于
I.MX6U
的主机驱动程序基本和
I2C
的适配器驱动程序类似。