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)——控制器驱动层