Bootstrap

Linux SPI驱动框架(3)——设备驱动层

SPI设备驱动层

  Linux SPI驱动框架(1)和(2)中分别介绍了SPI框架中核心层,和控制器驱动层。其实实际开发过程中,不是IC原厂工程师比较少会接触控制器驱动层,设备驱动层才是接触比较多的。
  本文以内核中spidev设备驱动为例,对基于设备树的SPI设备驱动进行简单的讲解。
  spidev驱动代码位于/drivers/spi/spidev.c

spidev设备驱动

spidev设备树

  首先来看设备树部分实现:

dac0: dh2228@2 {
	compatible = "rohm,dh2228fv";
	reg = <2>;
	spi-max-frequency = <100000>;
};

  设备树部分很简单,只是写了compatible描述,用于和驱动匹配。这里reg的意思是spi cs引脚序号。并不像其他平台设备一样是IO地址,或者像I2C一样是从机地址。

spidev驱动代码

spidev_init
static int __init spidev_init(void)
{
	int status;

	/* Claim our 256 reserved device numbers.  Then register a class
	 * that will key udev/mdev to add/remove /dev nodes.  Last, register
	 * the driver which manages those device numbers.
	 */
	BUILD_BUG_ON(N_SPI_MINORS > 256);
	status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);
	if (status < 0)
		return status;

	spidev_class = class_create(THIS_MODULE, "spidev");
	if (IS_ERR(spidev_class)) {
		unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
		return PTR_ERR(spidev_class);
	}

	status = spi_register_driver(&spidev_spi_driver);
	if (status < 0) {
		class_destroy(spidev_class);
		unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
	}
	return status;
}
module_init(spidev_init);

  这里设置的spidev的file_operations(spidev_fops),设备操作函数后文详细介绍。然后创建了spidev的class,创建完成后在用户空间/sys/class/下可以看到spidev目录结构。然后调用spi_register_driver注册spi从设备。

spi_driver
static const struct of_device_id spidev_dt_ids[] = {
	{ .compatible = "rohm,dh2228fv" },						(1)
	{ .compatible = "lineartechnology,ltc2488" },
	{ .compatible = "ge,achc" },
	{ .compatible = "nanopi,spidev" },
	{ .compatible = "semtech,sx1301" },
	{},
};

static struct spi_driver spidev_spi_driver = {
	.driver = {
		.name =		"spidev",
		.of_match_table = of_match_ptr(spidev_dt_ids),
		.acpi_match_table = ACPI_PTR(spidev_acpi_ids),
	},
	.probe =	spidev_probe,								(2)
	.remove =	spidev_remove,
};

(1)描述,与设备树相对应,会调用probe函数
(2)probe函数,匹配时调用

probe
static int spidev_probe(struct spi_device *spi)
{
	struct spidev_data	*spidev;
	int			status;
	unsigned long		minor;

	/*
	 * 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.
	 */
	if (spi->dev.of_node && !of_match_device(spidev_dt_ids, &spi->dev)) {
		dev_err(&spi->dev, "buggy DT: spidev listed directly in DT\n");
		WARN_ON(spi->dev.of_node &&
			!of_match_device(spidev_dt_ids, &spi->dev));
	}

	spidev_probe_acpi(spi);

	/* Allocate driver data */
	spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);
	if (!spidev)
		return -ENOMEM;

	/* Initialize the driver data */
	spidev->spi = spi;
	spin_lock_init(&spidev->spi_lock);
	mutex_init(&spidev->buf_lock);

	INIT_LIST_HEAD(&spidev->device_entry);

	/* If we can allocate a minor number, hook up this device.
	 * Reusing minors is fine so long as udev or mdev is working.
	 */
	mutex_lock(&device_list_lock);
	minor = find_first_zero_bit(minors, N_SPI_MINORS);
	if (minor < N_SPI_MINORS) {
		struct device *dev;

		spidev->devt = MKDEV(SPIDEV_MAJOR, minor);
		dev = device_create(spidev_class, &spi->dev, spidev->devt,			(1)
				    spidev, "spidev%d.%d",
				    spi->master->bus_num, spi->chip_select);
		status = PTR_ERR_OR_ZERO(dev);
	} else {
		dev_dbg(&spi->dev, "no minor number available!\n");
		status = -ENODEV;
	}
	if (status == 0) {
		set_bit(minor, minors);
		list_add(&spidev->device_entry, &device_list);
	}
	mutex_unlock(&device_list_lock);

	spidev->speed_hz = spi->max_speed_hz;

	if (status == 0)
		spi_set_drvdata(spi, spidev);
	else
		kfree(spidev);

	return status;
}

  spidev的probe函数,上述代码中并没有什么特殊的处理,申请了私有结构体spidev的内存空间,初始化了spidev的一些成员,自旋锁、互斥锁等。并将spidev指针设置为struct spi_device的私有数据(private_data)。
(1)调用device_create创建了/dev/下的spidev节点,如spi总线0上cs1设备,则设备名为/dev/spidev0.1,其他以此类推。
  在编写spi、i2c等驱动时,所做的操作也和spidev相差无几。初始化一些自己需要的资源等。但是一般情况下,这类涉及外设的驱动,会读取一下SPI、I2C从设备的ID寄存器等,来判断硬件是否就位,spi总线能否读写数据。

spidev_fops

  probe函数完成后,然后来看一下spidev_fops,文件操作相关api,先上代码:

static const struct file_operations spidev_fops = {
	.owner =	THIS_MODULE,
	.write =	spidev_write,
	.read =		spidev_read,
	.unlocked_ioctl = spidev_ioctl,
	.compat_ioctl = spidev_compat_ioctl,
	.open =		spidev_open,
	.release =	spidev_release,
	.llseek =	no_llseek,
};

  open和release就不看了,判断是否申请txbuff和rxbuff内存,如果没申请就申请。并维护了一个user count,用户空间每打开一次就+1,关闭一次就-1。所有都关闭后release释放申请的内存。
  然后再来看一下write和read函数,两个函数很雷同,这里只介绍write函数。下面是代码:

static ssize_t
spidev_write(struct file *filp, const char __user *buf,
		size_t count, loff_t *f_pos)
{
	struct spidev_data	*spidev;
	ssize_t			status = 0;
	unsigned long		missing;

	if (count > bufsiz)
		return -EMSGSIZE;
	spidev = filp->private_data;
	
	mutex_lock(&spidev->buf_lock);
	missing = copy_from_user(spidev->tx_buffer, buf, count);		(1)
	if (missing == 0)
		status = spidev_sync_write(spidev, count);					(2)
	else
		status = -EFAULT;
	mutex_unlock(&spidev->buf_lock);

	return status;
}

static inline ssize_t
spidev_sync_write(struct spidev_data *spidev, size_t len)
{
	struct spi_transfer	t = {
			.tx_buf		= spidev->tx_buffer,
			.len		= len,
			.speed_hz	= spidev->speed_hz,
		};
	struct spi_message	m;

	spi_message_init(&m);
	spi_message_add_tail(&t, &m);
	return spidev_sync(spidev, &m);									(3)
}

static ssize_t
spidev_sync(struct spidev_data *spidev, struct spi_message *message)
{
	int status;
	struct spi_device *spi;
	spin_lock_irq(&spidev->spi_lock);
	spi = spidev->spi;
	spin_unlock_irq(&spidev->spi_lock);
	if (spi == NULL)
		status = -ESHUTDOWN;
	else
		status = spi_sync(spi, message);							(4)
	if (status == 0)
		status = message->actual_length;
	return status;
}

(1)copy_from_u加粗样式ser从用户空间拷贝要发送的数据到内核空间。
(2)调用spidev_sync_write函数发送数据,这个函数实现代码也在上面贴出来了。
(3)spidev_sync_write代码实现,定义spi_transfer和spi_message,然后通过spidev_sync发送。
(4)spidev_sync中仅仅是判断了spi_device是否为空,不为空则调用spi_sync函数将spi_message发送出去。
  spidev_read函数与write函数如出一辙,不同的是write先从用户空间拷贝数据,然后调用发送函数发出去。struct spi_transfer填充的是tx_buff。而read则是先调用接受函数,struct spi_transfer填充的是rx_buff,然后将接收到的rx_buff,通过copy_to_user拷贝给用户空间。

注:spi_sync、spi_message、spi_transfer等在Linux SPI驱动框架(1)——核心层中已经详细介绍了。

  最后来看下ioctl函数,代码就不贴了,贴一下ioctl cmd:

#define SPI_IOC_MAGIC			'k'

/* Read / Write of SPI mode (SPI_MODE_0..SPI_MODE_3) (limited to 8 bits) */
#define SPI_IOC_RD_MODE			_IOR(SPI_IOC_MAGIC, 1, __u8)
#define SPI_IOC_WR_MODE			_IOW(SPI_IOC_MAGIC, 1, __u8)

/* Read / Write SPI bit justification */
#define SPI_IOC_RD_LSB_FIRST		_IOR(SPI_IOC_MAGIC, 2, __u8)
#define SPI_IOC_WR_LSB_FIRST		_IOW(SPI_IOC_MAGIC, 2, __u8)

/* Read / Write SPI device word length (1..N) */
#define SPI_IOC_RD_BITS_PER_WORD	_IOR(SPI_IOC_MAGIC, 3, __u8)
#define SPI_IOC_WR_BITS_PER_WORD	_IOW(SPI_IOC_MAGIC, 3, __u8)

/* Read / Write SPI device default max speed hz */
#define SPI_IOC_RD_MAX_SPEED_HZ		_IOR(SPI_IOC_MAGIC, 4, __u32)
#define SPI_IOC_WR_MAX_SPEED_HZ		_IOW(SPI_IOC_MAGIC, 4, __u32)

/* Read / Write of the SPI mode field */
#define SPI_IOC_RD_MODE32		_IOR(SPI_IOC_MAGIC, 5, __u32)
#define SPI_IOC_WR_MODE32		_IOW(SPI_IOC_MAGIC, 5, __u32)

  根据字面意思就可以基本了解的差不多了。用于匹配不同模式、字节高低顺序,不同spi速度的各种从设备。所以需要这些设置接口。

spidev总结

  spidev是内核中用来测试spi的驱动,不是专门针对某一SPI硬件设备做的驱动,只是简单的注册字符设备,用户空间通过read、write、ioctl,直接对spi进行操作,相当于是用户空间和spi设备的桥梁。
  用户空间操作时,打开设备节点,然后通过IOCTL设置模式、速度等参数,然后就可以调用read write进行操作了。

spi从设备设备树编写

  很多同学不知道spi从设备设备树怎么编写,模式、参数等如何在设备树中设置,下面来介绍下spi从设备标准device_node。
  其实通过spi_register_master在注册过程中,扫描设备树子节点,注册spi子设备时的api来反向推导:

spi_register_master->
	of_register_spi_devices->
		of_register_spi_device->
			of_spi_parse_dt

static int of_spi_parse_dt(struct spi_controller *ctlr, struct spi_device *spi,
			   struct device_node *nc)
{
	u32 value;
	int rc;

	/* Mode (clock phase/polarity/etc.) */
	if (of_property_read_bool(nc, "spi-cpha"))
		spi->mode |= SPI_CPHA;
	if (of_property_read_bool(nc, "spi-cpol"))
		spi->mode |= SPI_CPOL;
	if (of_property_read_bool(nc, "spi-cs-high"))
		spi->mode |= SPI_CS_HIGH;
	if (of_property_read_bool(nc, "spi-3wire"))
		spi->mode |= SPI_3WIRE;
	if (of_property_read_bool(nc, "spi-lsb-first"))
		spi->mode |= SPI_LSB_FIRST;

	/* Device DUAL/QUAD mode */
	if (!of_property_read_u32(nc, "spi-tx-bus-width", &value)) {
		switch (value) {
		case 1:
			break;
		case 2:
			spi->mode |= SPI_TX_DUAL;
			break;
		case 4:
			spi->mode |= SPI_TX_QUAD;
			break;
		default:
			dev_warn(&ctlr->dev,
				"spi-tx-bus-width %d not supported\n",
				value);
			break;
		}
	}

	if (!of_property_read_u32(nc, "spi-rx-bus-width", &value)) {
		switch (value) {
		case 1:
			break;
		case 2:
			spi->mode |= SPI_RX_DUAL;
			break;
		case 4:
			spi->mode |= SPI_RX_QUAD;
			break;
		default:
			dev_warn(&ctlr->dev,
				"spi-rx-bus-width %d not supported\n",
				value);
			break;
		}
	}

	if (spi_controller_is_slave(ctlr)) {
		if (strcmp(nc->name, "slave")) {
			dev_err(&ctlr->dev, "%pOF is not called 'slave'\n",
				nc);
			return -EINVAL;
		}
		return 0;
	}

	/* Device address */
	rc = of_property_read_u32(nc, "reg", &value);
	if (rc) {
		dev_err(&ctlr->dev, "%pOF has no valid 'reg' property (%d)\n",
			nc, rc);
		return rc;
	}
	spi->chip_select = value;

	/* Device speed */
	rc = of_property_read_u32(nc, "spi-max-frequency", &value);
	if (rc) {
		dev_err(&ctlr->dev,
			"%pOF has no valid 'spi-max-frequency' property (%d)\n", nc, rc);
		return rc;
	}
	spi->max_speed_hz = value;

	return 0;
}

  代码贴在这里了,非常简单,代码中判断节点是否存在,则设备树中就可以配置该节点,来对spi参数进行设置。

SPI驱动框架链接:
Linux SPI驱动框架(1)——核心层
Linux SPI驱动框架(2)——控制器驱动层

;