限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
SPI 总线是Master设备和Slave从设通信的接口,是一种高速、全双工
的同步串行通信总线。简单看一下 SPI 总线的拓扑结构:
其中:
SCLK: SPI 总线时钟,4MHz-20MHz;
MOSI: 数据线,从Master传送数据到Slave;
MISO: 数据线,从Slave传送数据到Master;
SSx: Slave片选信号,可能标记成CS更常见。
SPI 有4种工作模式,通过串行时钟极性(CPOL)
和相位(CPHA)
的搭配来得到4种工作模式。先看下 CPOL
和 CPHA
的作用:
1. CPOL=0,串行时钟空闲状态为低电平。
2. CPOL=1,串行时钟空闲状态为高电平,此时可以通过配置时钟相位(CPHA)来选择具体的传输协议。
3. CPHA=0,串行时钟的第1个跳变沿(上升沿或下降沿)采集数据。
4. CPHA=1,串行时钟的第2个跳变沿(上升沿或下降沿)采集数据。
再看由 CPOL
和 CPHA
搭配的模式:
SCL空闲时电平 采样 CPHA Mode
低电平 上升沿 CPOL = 0, CPHA = 0 Mode0
低电平 下降沿 CPOL = 0, CPHA = 1 Mode1
高电平 下降沿 CPOL = 1, CPHA = 0 Mode2
高电平 上升沿 CPOL = 1, CPHA = 1 Mode3
通过配置 SPI Master控制器,可以配置 SPI 的工作模式,而从设的模式一般是固定的,可以参数从设的数据手册。
SPI 的读写不同于 I2C,不需要显示标记,因为 SPI 是全双工的,读写可以同时进行。
编写 SPI 总线驱动相关的内核接口:
extern struct spi_controller *__spi_alloc_controller(struct device *host,
unsigned int size, bool slave);
extern int spi_register_controller(struct spi_controller *ctlr);
看一个 SPI 总线驱动框架示例:
spi@01c68000 {
compatible = "allwinner,sun8i-h3-spi";
...
spi@0 {
compatible = "nanopi,spidev";
...
};
};
static int sun6i_spi_probe(struct platform_device *pdev)
{
struct spi_master *master;
/* 创建SPI总线控制器对象 */
master = spi_alloc_master(&pdev->dev, sizeof(struct sun6i_spi));
...
/* SPI控制器中断处理: 数据传输完成、传输FIFO等的处理 */
ret = devm_request_irq(&pdev->dev, irq, sun6i_spi_handler,
0, "sun6i-spi", sspi);
...
master->max_speed_hz = 100 * 1000 * 1000; /* 支持的最小时钟 */
master->min_speed_hz = 3 * 1000; /* 支持的最小时钟 */
master->set_cs = sun6i_spi_set_cs; /* 片选接口 */
master->transfer_one = sun6i_spi_transfer_one; /* 设置SPI总线数据传输接口 */
...
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LSB_FIRST; /* 设置工作模式 */
...
/* 注册SPI总线控制器对象到系统 */
ret = devm_spi_register_master(&pdev->dev, master);
return 0;
}
static const struct of_device_id sun6i_spi_match[] = {
...
{ .compatible = "allwinner,sun8i-h3-spi", .data = (void *)SUN8I_FIFO_DEPTH },
{}
};
static struct platform_driver sun6i_spi_driver = {
.probe = sun6i_spi_probe,
...
.driver = {
...
.of_match_table = sun6i_spi_match,
...
};
};
以 ads7846
触摸输入设备驱动为例来分析。
spi@01c68000 {
compatible = "allwinner,sun8i-h3-spi";
...
/* SPI 从设 ads7846,挂接在 SPI 总线 spi@01c68000 上 */
pitft-ts@1 {
compatible = "ti,ads7846";
...
spi-max-frequency = <2000000>;
interrupt-parent = <&pio>;
interrupts = <6 9 IRQ_TYPE_EDGE_FALLING>; /* PG9 / EINT9 */
...
};
};
static const struct of_device_id ads7846_dt_ids[] = {
...
{ .compatible = "ti,ads7846", .data = (void *) 7846 },
...
{ }
};
/* SPI 从设驱动入口 */
static int ads7846_probe(struct spi_device *spi)
{
struct input_dev *input_dev;
...
spi->bits_per_word = 8;
spi->mode = SPI_MODE_0;
err = spi_setup(spi); /* 设置 SPI 工作模式和时钟频率 */
/* 创建输入设备对象 */
input_dev = input_allocate_device();
...
/* 配置输入设备 */
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
input_set_abs_params(input_dev, ABS_X,
pdata->x_min ? : 0,
pdata->x_max ? : MAX_12BIT,
0, 0);
...
/* 上电 */
ts->reg = regulator_get(&spi->dev, "vcc");
err = regulator_enable(ts->reg);
/* 注册输入中断处理接口:上报按键事件 */
err = request_threaded_irq(spi->irq, ads7846_hard_irq, ads7846_irq,
irq_flags, spi->dev.driver->name, ts);
...
/* 注册输入设备 */
err = input_register_device(input_dev);
...
return 0;
}
static struct spi_driver ads7846_driver = {
.driver = {
.name = "ads7846",
...
.of_match_table = of_match_ptr(ads7846_dt_ids),
},
.probe = ads7846_probe,
...
};
一个 SPI 输入设备的驱动框架已经出来了,现在还剩一点需要说明:系统是何时创建 spi_device
来触发驱动的 ads7846_probe()
接口的?答案是 SPI 控制器驱动对象注册的时候:
#define devm_spi_register_master(_dev, _ctlr) \
devm_spi_register_controller(_dev, _ctlr)
int devm_spi_register_controller(struct device *dev,
struct spi_controller *ctlr)
{
int ret;
...
ret = spi_register_controller(ctlr);
...
return ret;
}
int spi_register_controller(struct spi_controller *ctlr)
{
...
dev_set_name(&ctlr->dev, "spi%u", ctlr->bus_num);
status = device_add(&ctlr->dev);
...
list_add_tail(&ctlr->list, &spi_controller_list);
list_for_each_entry(bi, &board_list, list)
spi_match_controller_to_boardinfo(ctlr, &bi->board_info); /* 旧的 spi_register_board_info() 方式创建 spi_device */
of_register_spi_devices(ctlr); /* DTS 方式创建 spi_device */
acpi_register_spi_devices(ctlr); /* ACPI 方式创建 spi_device */
done:
return status;
}
/* 旧的 spi_register_board_info() 方式创建 spi_device */
static void spi_match_controller_to_boardinfo(struct spi_controller *ctlr,
struct spi_board_info *bi)
{
struct spi_device *dev;
dev = spi_new_device(ctlr, bi);
...
}
struct spi_device *spi_new_device(struct spi_controller *ctlr,
struct spi_board_info *chip)
{
struct spi_device *proxy;
proxy = spi_alloc_device(ctlr); /* 创建 spi_device 对象 */
...
status = spi_add_device(proxy); * 注册 spi_device 到 driver core,触发驱动 probe 接口 */
...
return proxy;
}
/* DTS 方式创建 spi_device */
static void of_register_spi_devices(struct spi_controller *ctlr)
{
struct spi_device *spi;
/*
* 扫描 DTS 中 SPI 总线上挂接的从设节点,为它们创建 spi_device。
* 如前面 DTS 代码片段中的 "ti,ads7846" 。
*/
for_each_available_child_of_node(ctlr->dev.of_node, nc) {
...
spi = of_register_spi_device(ctlr, nc);
...
}
}
static struct spi_device *
of_register_spi_device(struct spi_controller *ctlr, struct device_node *nc)
{
struct spi_device *spi;
int rc;
spi = spi_alloc_device(ctlr); /* 创建 spi_device 对象 */
...
rc = of_spi_parse_dt(ctlr, spi, nc); /* 解析 SPI 从设 DTS 配置 */
rc = spi_add_device(spi); /* 注册 spi_device 到 driver core,触发驱动 probe 接口 */
return spi;
}
我们可以通过 SPI 子系统,提供的用户空间接口来操控 SPI 从设。
spi@01c68000 {
compatible = "allwinner,sun8i-h3-spi";
...
/* SPI 总线用户空间设备 DTS */
spi@0 {
compatible = "nanopi,spidev";
...
};
};
/* drivers/spi/spidev.c */
static const struct of_device_id spidev_dt_ids[] = {
...
{ .compatible = "nanopi,spidev" },
...
{},
};
MODULE_DEVICE_TABLE(of, spidev_dt_ids);
static int spidev_probe(struct spi_device *spi)
{
struct spidev_data *spidev;
...
minor = find_first_zero_bit(minors, N_SPI_MINORS);
if (minor < N_SPI_MINORS)
struct device *dev;
spidev->devt = MKDEV(SPIDEV_MAJOR, minor);
/* 创建 SPI 总线的字符设备节点:供用户空间访问 */
dev = device_create(spidev_class, &spi->dev, spidev->devt,
spidev, "spidev%d.%d",
spi->master->bus_num, spi->chip_select);
} else {
...
}
...
}
s
tatic struct spi_driver spidev_spi_driver = {
.driver = {
.name = "spidev",
.of_match_table = of_match_ptr(spidev_dt_ids),
...
},
.probe = spidev_probe,
...
};
static int __init spidev_init(void)
{
int status;
/* SPI 总线字符设备 */
status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);
...
spidev_class = class_create(THIS_MODULE, "spidev");
...
status = spi_register_driver(&spidev_spi_driver);
...
return status;
}
module_init(spidev_init);
int fd, mode = SPI_MODE_0;
fd = open("/dev/spidev1.0", O_RDWR);
ret = ioctl(fd, SPI_IOC_WR_MODE, &mode); /* 设置工作模式 */
ret = ioctl(fd_spi, SPI_IOC_WR_BITS_PER_WORD, &bits); /* 设置SPI的数据位 */
ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed); /* 设置速度 */
ret = ioctl(fd, SPI_IOC_MESSAGE(n), &tr); /* 数据发送 */
...
close(fd);
更多细节参考 drivers/spi/spidev.c
中的 spidev_ioctl()
接口。