SPI接口的ADC驱动调试
SPI接口的ADC驱动调试
背景
最近在学习IIO子系统,顺带调试了个SPI接口的ADC驱动,所以在这简单记录下。
这里只简单介绍了适配一个简单SPI接口ADC驱动的流程,不过多深入框架子系统,更多关于IIO子系统的介绍,请见下一篇:
驱动开发
dts
dts主要修改或新增以下几点:
- spi master控制器相关配置
- spi设备节点配置
- 引脚复用配置
如spi节点配置例子:
&spi0 {
status = "okay";
num-cs = <1>; /* Total number of chip selects */
aaa: aaa@0 {
status = "okay";
reg = <0>; /* Chip select used by the device. */
compatible = "yyy,xxx";
wakeup-gpios = <&gpio3 RK_PC5 GPIO_ACTIVE_HIGH>; /* gpio, interrupt... */
spi-cpol; /* The device requires inverse clock polarity (CPOL) mode. */
spi-cpha; /* The device requires shifted clock phase (CPHA) mode. */
spi-tx-bus-width = <1>; /* Bus width to the SPI bus used for write transfers. */
spi-rx-bus-width = <1>;
spi-max-frequency = <5000000>;
};
};
更多spi配置信息可参见内核文档spi相关部分[^spi]
说明:
SPI的四种模式,即上面dts配置中spi-cpol
和spi-cpha
属性,这个需要根据spi从设备的时序特性来配置
SPI模式 | CPOL | CPHA | 空闲状态时钟 | 数据输入/出时钟相位 |
---|---|---|---|---|
SPI0 | 0 | 0 | 低电平 | 上升沿输入,下降沿输出 |
SPI1 | 0 | 1 | 低电平 | 下升沿输入,上降沿输出 |
SPI2 | 1 | 0 | 高电平 | 下升沿输入,上降沿输出 |
SPI3 | 1 | 1 | 高电平 | 上升沿输入,下降沿输出 |
这里有更具体详细介绍:https://blog.csdn.net/buhuidage/article/details/121180622
配置
主要有以下几方面的驱动配置:
- spi core的配置
- soc相关spi master控制器驱动配置
- 如果使用IIO子系统,还需打开IIO的配置
- 如果使用regmap子系统,还需打开regmap的配置
- 如果使用spidev,还需打开spidev相关配置,spidev相关介绍可见另一篇博文:https://notes.z-dd.online/2023/04/17/spidev%E7%9B%B8%E5%85%B3/
#spi core
CONFIG_SPI=y
CONFIG_SPI_MASTER=y
#spi master控制器
CONFIG_SPI_XXX=y
#iio
CONFIG_IIO=y
CONFIG_IIO_BUFFER=y
CONFIG_IIO_BUFFER_CB=y
CONFIG_IIO_KFIFO_BUF=y
CONFIG_IIO_TRIGGERED_BUFFER=y
CONFIG_IIO_TRIGGER=y
CONFIG_IIO_CONSUMERS_PER_TRIGGER=2
#regmap
CONFIG_REGMAP=y
CONFIG_REGMAP_SPI=y
CONFIG_REGMAP_MMIO=y
CONFIG_REGMAP_IRQ=y
#spidev
CONFIG_SPI_SPIDEV=y
驱动代码实现
其实有很多方式来实现该驱动:
- 直接使用字符设备驱动框架,用
struct file_operations
接口来操作,这里有个例子介绍:https://blog.csdn.net/shengnan89/article/details/124141886 - 使用IIO框架+
regmap
子系统 - 这里使用IIO框架+SPI驱动接口,未使用regmap
伪代码实现如下:
...
/* 通道属性,根据器件硬件属性配置 */
#define IIO_ADC_VOLTAGE_CHAN(num) \
{ \
.type = IIO_VOLTAGE, \
.indexed = 1, \
.channel = (num), \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \
}
static const struct iio_chan_spec iio_adc_channels[] = {
IIO_ADC_VOLTAGE_CHAN(0),
IIO_ADC_VOLTAGE_CHAN(1),
};
static int iio_adc_sensor_read(struct iio_adc *dev, int ch, int *val)
{
/* 通过spi_write()/spi_read()/spi_write_then_read()等相关spi接口操作寄存器 */
...
}
static int iio_adc_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2,
long mask)
{
int ret;
struct iio_adc *dev = iio_priv(indio_dev);
switch (mask) {
case IIO_CHAN_INFO_RAW: /* 读取传感器数据 */
if (chan->type == IIO_VOLTAGE) {
ret = iio_adc_sensor_read(dev, chan->channel, val);
if (ret < 0)
return ret;
}
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE: /* SCALE根据实际调整 */
*val = 3600 / 1000;
*val2 = 12;
return IIO_VAL_FRACTIONAL_LOG2;
default:
ret = -EINVAL;
break;
}
return ret;
}
/* 实际使用的接口需根据器件功能自行添加 */
static const struct iio_info iio_adc_info = {
.read_raw = &iio_adc_read_raw,
};
static int iio_adc_probe(struct spi_device *spi)
{
int ret = 0;
struct iio_adc *iio_adc_dev;
struct iio_dev *iiodev;
/* 向内核申请分配iio_dev内存, 包括同时分配的iio_adc_dev内存 */
iiodev = devm_iio_device_alloc(&spi->dev, sizeof(struct iio_adc));
if (!iiodev) {
return -ENOMEM;
}
spi_set_drvdata(spi, iiodev);
/* 把已分配的indio_dev内存结构的私有数据赋给iio_adc_dev */
iio_adc_dev = iio_priv(iiodev);
iio_adc_dev->spi = spi;
mutex_init(&iio_adc_dev->lock);
/* 设置iio_dev的主要成员变量 */
iiodev->name = "iio_adc";
//iiodev->dev.parent = &spi->dev;
iiodev->info = &iio_adc_info;
iiodev->modes = INDIO_DIRECT_MODE; /* 更多模式说明请见iio子系统的文章 */
iiodev->channels = iio_adc_channels;
iiodev->num_channels = ARRAY_SIZE(iio_adc_channels);
/* 注册iio_dev */
ret = iio_device_register(iiodev);
if (ret < 0) {
dev_err(&spi->dev, "iio_device_register failed\n");
goto err_iio_register;
}
/* vref regulator相关操作:devm_regulator_get() regulator_enable() */
/* GPIO 等硬件相关操作 */
/* 初始化从设备内部寄存器 */
iio_adc_reg_init(iio_adc_dev);
return 0;
}
static int iio_adc_remove(struct spi_device *spi)
{
struct iio_dev *iiodev = spi_get_drvdata(spi);
/* 注销IIO */
iio_device_unregister(iiodev);
return 0;
}
static const struct of_device_id iio_adc_of_match[] = {
{.compatible = "yyy,xxx"},
{ /* */}
};
MODULE_DEVICE_TABLE(of, iio_adc_of_match);
static struct spi_driver iio_adc_driver = {
.probe = iio_adc_probe,
.remove = iio_adc_remove,
.driver = {
.owner = THIS_MODULE,
.name = "iio_adc",
.of_match_table = iio_adc_of_match,
},
//.id_table = iio_adc_id,
};
static int __init iio_adc_init(void)
{
/* 注册spi 驱动 */
return spi_register_driver(&iio_adc_driver);
}
static void __exit iio_adc_exit(void)
{
spi_unregister_driver(&iio_adc_driver);
}
...
核外操作
这里以读取adc通道0的电压为例,其他类似。
命令行方式:
#读取 cat /sys/bus/iio/devices/iio\:device0/in_voltage0_raw
C程序方式:
可直接通过fopen
,read
等文件操作接口来操作设备节点
参考
drivers/iio/adc/ti-adc128s052.c
Documentation/devicetree/bindings/iio/adc/ti-adc128s052.txt
Documentation/devicetree/bindings/spi/spi-controller.yaml
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 DD'Notes!
评论