为什么分析这个器件呢,因为他涉及的知识点比较多,而且很多人都用过,既支持I2C接口,也支持SPI接口,在Linux中当然推荐用regmap统一管理,这里分析的是由Eva Rachel Retuya大佬写的驱动,实际上这个驱动也很简单但是很经典,所以值得分析.
首先在adxl345_spi.c和adxl345_i2c.c分别实现platform driver引入.
static struct i2c_driver adxl345_i2c_driver = {
.driver = {
.name = "adxl345_i2c",
.of_match_table = adxl345_of_match,
.acpi_match_table = adxl345_acpi_match,
},
.probe_new = adxl345_i2c_probe,
.id_table = adxl345_i2c_id,
};
module_i2c_driver(adxl345_i2c_driver);
static struct spi_driver adxl345_spi_driver = {
.driver = {
.name = "adxl345_spi",
.of_match_table = adxl345_of_match,
.acpi_match_table = adxl345_acpi_match,
},
.probe = adxl345_spi_probe,
.id_table = adxl345_spi_id,
};
module_spi_driver(adxl345_spi_driver);
他们分别也只是单独实现了自己的probe函数,先看看两个初始化的区别.
static const struct regmap_config adxl345_i2c_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
};
static int adxl345_i2c_probe(struct i2c_client *client)
{
struct regmap *regmap;
regmap = devm_regmap_init_i2c(client, &adxl345_i2c_regmap_config);
if (IS_ERR(regmap))
return dev_err_probe(&client->dev, PTR_ERR(regmap), "Error initializing regmap\n");
return adxl345_core_probe(&client->dev, regmap);
}
static const struct regmap_config adxl345_spi_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
/* Setting bits 7 and 6 enables multiple-byte read */
.read_flag_mask = BIT(7) | BIT(6),
};
static int adxl345_spi_probe(struct spi_device *spi)
{
struct regmap *regmap;
/* Bail out if max_speed_hz exceeds 5 MHz */
if (spi->max_speed_hz > ADXL345_MAX_SPI_FREQ_HZ)
return dev_err_probe(&spi->dev, -EINVAL, "SPI CLK, %d Hz exceeds 5 MHz\n",
spi->max_speed_hz);
regmap = devm_regmap_init_spi(spi, &adxl345_spi_regmap_config);
if (IS_ERR(regmap))
return dev_err_probe(&spi->dev, PTR_ERR(regmap), "Error initializing regmap\n");
return adxl345_core_probe(&spi->dev, regmap);
}
我们知道如果单独操作SPI驱动,可以用spi_message_xxx做结构然后用spi_sync发送,如果单独做I2C驱动用i2c_transfer,不管哪种,他对于这种多接口器件也不友好,而且更底层的接口不会优化太多的性能,反而增加后续很多麻烦,比如ADXL345的SPI读写寄存器需要用最高位来决定,还要用MB位决定是否连续读取.
如果使用传统的SPI方法我们就要先行操作reg,最后还要做复制,明明都是很常见的代码,要是每个设备都这么做,必然大量代码冗余.
// 结构
struct spi_message m;
struct spi_device *spi = (struct spi_device *)dev->private_data;
// 内存申请
t = kzalloc(sizeof(struct spi_transfer),GFP_KERNEL);
// 数据填充
txdata[0] = reg | 0x80;
t->tx_buf = txdata;
t->rx_buf = rxdata;
t->len = len + 1
// 数据发送
spi_message_init(&m);
spi_message_add_tail(t,&m);
ret = spi_sync(spi,&m);
if(ret){
return -ENOMEM;
}
memcpy(buf,rxdata+1,len);
在regmap会简单成怎样?
regmap_read(dev->regmap,reg,&data);
regmap目前支持很多接口,除了I2C,SPI之类,包括I3C之类也是支持的,可以说是只要操作的设备自身也有寄存器,通信方式比较正常,都可以支持,看一开始SPI初始化时候会套用一个参数read_flag_mask,这个参数就是说当我需要读寄存器时候,自动置位BIT7/BIT6,那么我们就不用自己txdata[0] = reg | 0x8这样置位了.
OK,初始化后,一切实际驱动都会在adxl345_core实现,进来后简单校验然后就开始创建IIO设备.
int adxl345_core_probe(struct device *dev, struct regmap *regmap)
{
enum adxl345_device_type type;
struct adxl345_data *data;
struct iio_dev *indio_dev;
const char *name;
u32 regval;
int ret;
type = (uintptr_t)device_get_match_data(dev);
switch (type) {
case ADXL345:
name = "adxl345";
break;
case ADXL375:
name = "adxl375";
break;
default:
return -EINVAL;
}
// 读取寄存器,如果读取不到,可能dts或者device驱动匹配不对的地址,如果在SPI怎么都能过这一步,毕竟没有ACK状态.
ret = regmap_read(regmap, ADXL345_REG_DEVID, ®val);
if (ret < 0)
return dev_err_probe(dev, ret, "Error reading device ID\n");
// 比较Device ID是否相等.
if (regval != ADXL345_DEVID)
return dev_err_probe(dev, -ENODEV, "Invalid device ID: %x, expected %x\n",
regval, ADXL345_DEVID);
// 创建一个IIO设备(工业IO)
indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
IIO设备可以理解成所有涉及ADC,DAC转换,最后以此上报数据,不好分类为其他设备的,都可以加入这里,比如驱动这里实现了3个函数.
static const struct iio_info adxl345_info = {
.attrs = &adxl345_attrs_group,
.read_raw = adxl345_read_raw,
.write_raw = adxl345_write_raw,
.write_raw_get_fmt = adxl345_write_raw_get_fmt,
};
这里有一个特别的知识点,用户无法传递浮点数到内核,所以read_raw和read_raw分别有val1,val2,write_raw_get_fmt决定了用户用那种格式写数据到内核驱动,比如当写入IIO_CHAN_INFO_CALIBBIAS的时候,支持IIO_VAL_INT,也就是val1是整数,val2无意义,当写入IIO_CHAN_INFO_SAMP_FREQ时候,小数部分扩大10^9倍,则用户写1.234,此时val1是1,val2是0.234*10^9.
static int adxl345_write_raw_get_fmt(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
long mask)
{
switch (mask) {
case IIO_CHAN_INFO_CALIBBIAS:
return IIO_VAL_INT;
case IIO_CHAN_INFO_SAMP_FREQ:
return IIO_VAL_INT_PLUS_NANO;
default:
return -EINVAL;
}
}
当然我们返回的数据也要报告一个格式,这样用户空间才知道怎么显示,数据读写我们就有了,难道还要用户自己用户空间挨个转换读取,查查寄存器手册,明显太低效率了,所以还要定义iio_chan_spec,下面定义进行了部分展开,具体源码可以看官方源码库.
static const struct iio_chan_spec adxl345_channels[] = {
{
.type = IIO_ACCEL, // 比如IIO_LIGHT(光传感) IIO_VOLTAGE(电压ADC) 等等
.modified = 1, // 当设置为1,channel2为通道描述符
.channel2 = IIO_MOD_X, // 这个是iio_modifier定义的,他定义决定了用户空间下的文件名称
.address = 0, // 地址(不一定是寄存器地址)
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |BIT(IIO_CHAN_INFO_CALIBBIAS), // 通道属性
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_SAMP_FREQ), // 通道属性
}
ADXL345_CHANNEL(1, Y),
ADXL345_CHANNEL(2, Z),
};
为什么这里address设置成0呢,查表看到地址应该是这样的.
所以实际读取时候也进行了转换.
#define ADXL345_REG_DATAX0 0x32
#define ADXL345_REG_DATA_AXIS(index) \
(ADXL345_REG_DATAX0 + (index) * sizeof(__le16))
static int adxl345_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct adxl345_data *data = iio_priv(indio_dev);
__le16 accel;
long long samp_freq_nhz;
unsigned int regval;
int ret;
switch (mask) {
case IIO_CHAN_INFO_RAW:
/*
* Data is stored in adjacent registers:
* ADXL345_REG_DATA(X0/Y0/Z0) contain the least significant byte
* and ADXL345_REG_DATA(X0/Y0/Z0) + 1 the most significant byte
*/
ret = regmap_bulk_read(data->regmap,
ADXL345_REG_DATA_AXIS(chan->address),
&accel, sizeof(accel));
if (ret < 0)
return ret;
*val = sign_extend32(le16_to_cpu(accel), 12);
如果不想这么做也可以这么设置.
static const struct iio_chan_spec adxl345_channels[] = {
ADXL345_CHANNEL(ADXL345_REG_DATAX0, X),
ADXL345_CHANNEL(ADXL345_REG_DATAY0, Y),
ADXL345_CHANNEL(ADXL345_REG_DATAZ0, Z),
};
还有一些杂项,比如采样率必然不是连续可调的,所以也可以预先定义.
static IIO_CONST_ATTR_SAMP_FREQ_AVAIL(
"0.09765625 0.1953125 0.390625 0.78125 1.5625 3.125 6.25 12.5 25 50 100 200 400 800 1600 3200"
);
static struct attribute *adxl345_attrs[] = {
&iio_const_attr_sampling_frequency_available.dev_attr.attr,
NULL
};
static const struct attribute_group adxl345_attrs_group = {
.attrs = adxl345_attrs,
};
现在已经差不多了,回到主线继续注册IIO设备.
// 创建一个IIO设备(工业IO)
indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
// 从dev中要一块私有数据的内存,这里的私有数据当然是adxl345_data了.
data = iio_priv(indio_dev);
data->regmap = regmap; // 来自regmap驱动,不存起来后续可是很麻烦.
data->type = type; // 这个驱动本体识别两个不同的驱动.
/* Enable full-resolution mode */
data->data_range = ADXL345_DATA_FORMAT_FULL_RES;
ret = regmap_write(data->regmap, ADXL345_REG_DATA_FORMAT,
data->data_range);
if (ret < 0)
return dev_err_probe(dev, ret, "Failed to set data range\n");
// 实际IIO套结构
indio_dev->name = name;
indio_dev->info = &adxl345_info; // 关联read_raw/write_raw/write_raw_get_fmt
indio_dev->modes = INDIO_DIRECT_MODE; // 提供sysfs接口模式,还可以选择INDIO_BUFFER_TRIGGERED(硬件触发),INDIO_BUFFER_SOFTWARE(软件触发),INDIO_BUFFER_HARDWARE(硬件缓冲区)
indio_dev->channels = adxl345_channels; // 各种通道
indio_dev->num_channels = ARRAY_SIZE(adxl345_channels);
/* Enable measurement mode */
ret = adxl345_powerup(data->regmap);
if (ret < 0)
return dev_err_probe(dev, ret, "Failed to enable measurement mode\n");
ret = devm_add_action_or_reset(dev, adxl345_powerdown, data->regmap);
if (ret < 0)
return ret;
// 注册设备
return devm_iio_device_register(dev, indio_dev);
}
为了用,当然要dts声明他.
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
i2c0 {
#address-cells = <1>;
#size-cells = <0>;
/* Example for a I2C device node */
accelerometer@2a {
compatible = "adi,adxl345";
reg = <0x53>;
interrupt-parent = <&gpio0>;
interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
};
};
spi0 {
#address-cells = <1>;
#size-cells = <0>;
/* Example for a SPI device node */
accelerometer@0 {
compatible = "adi,adxl345";
reg = <0>;
spi-max-frequency = <5000000>;
spi-cpol;
spi-cpha;
interrupt-parent = <&gpio0>;
interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
};
};
现在到用户空间看,命名规则主要看industrialio-core.c,主要结构如下:
static const char * const iio_direction[] = {
[0] = "in",
[1] = "out",
};
static const char * const iio_chan_type_name_spec[] = {
[IIO_VOLTAGE] = "voltage",
[IIO_CURRENT] = "current",
[IIO_POWER] = "power",
[IIO_ACCEL] = "accel",
...
}
static const char * const iio_modifier_names[] = {
[IIO_MOD_X] = "x",
[IIO_MOD_Y] = "y",
[IIO_MOD_Z] = "z",
...
}
static const char * const iio_chan_info_postfix[] = {
[IIO_CHAN_INFO_RAW] = "raw",
[IIO_CHAN_INFO_PROCESSED] = "input",
[IIO_CHAN_INFO_SCALE] = "scale",
[IIO_CHAN_INFO_OFFSET] = "offset",
[IIO_CHAN_INFO_CALIBSCALE] = "calibscale",
[IIO_CHAN_INFO_CALIBBIAS] = "calibbias",
...
}
我们设备刚好是输入且类型是IIO_ACCEL,且channel2定义是IIO_MOD_X,并且数据格式是IIO_CHAN_INFO_RAW,IIO_CHAN_INFO_CALIBBIAS,IIO_CHAN_INFO_SCALE,IIO_CHAN_INFO_SAMP_FREQ,所以他会生成3个(后面2个共享)名称,其中第一个不用质疑就是in_accel_x_raw.
另外驱动中中断似乎没用?需要的话要自行写2E寄存器,然后绑定中断函数.
Linux 驱动开发实际上就是和各种子系统打交道,真是一个最优美的架构.
有个项目想看下博主是否能做