Linux: SPI 驱动

文章目录

  • 1. 前言
  • 2. SPI 总线驱动
    • 2.1 SPI 总线拓扑
    • 2.2 SPI 总线工作模式
    • 2.3 SPI 总线驱动编写
  • 3. SPI 从设驱动
  • 4. SPI 用户空间接口
    • 4.1 创建 SPI 总线用户空间字符设备节点
    • 4.2 操作 SPI 总线用户字符设备节点

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. SPI 总线驱动

2.1 SPI 总线拓扑

SPI 总线是Master设备和Slave从设通信的接口,是一种高速、全双工的同步串行通信总线。简单看一下 SPI 总线的拓扑结构:
Linux: SPI 驱动_第1张图片
其中:

SCLK: SPI 总线时钟,4MHz-20MHz;
MOSI: 数据线,从Master传送数据到Slave;
MISO: 数据线,从Slave传送数据到Master;
SSx: Slave片选信号,可能标记成CS更常见。

2.2 SPI 总线工作模式

SPI 有4种工作模式,通过串行时钟极性(CPOL)相位(CPHA)的搭配来得到4种工作模式。先看下 CPOLCPHA 的作用:

1. CPOL=0,串行时钟空闲状态为低电平。
2. CPOL=1,串行时钟空闲状态为高电平,此时可以通过配置时钟相位(CPHA)来选择具体的传输协议。
3. CPHA=0,串行时钟的第1个跳变沿(上升沿或下降沿)采集数据。
4. CPHA=1,串行时钟的第2个跳变沿(上升沿或下降沿)采集数据。

再看由 CPOLCPHA 搭配的模式:

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 是全双工的,读写可以同时进行。

2.3 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,
		...
	};
};

3. SPI 从设驱动

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;
}

4. SPI 用户空间接口

我们可以通过 SPI 子系统,提供的用户空间接口来操控 SPI 从设。

4.1 创建 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);

4.2 操作 SPI 总线用户字符设备节点

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() 接口。

你可能感兴趣的:(#,杂项,linux,spi驱动)