需求是这样的,在用 PCA955x 通过 IIC 扩展 GPIO 来实现一系列拨码开关的访问,需要通过拨码引脚输入的二进制值拼成 10/16 整数在 sysfs 通过一个只读文件暴露给用户空间访问。
当然首先需要:
pca9557: pca9557@18 {
compatible = "nxp,pca9557";
reg = <0x18>;
status = "okay";
};
可以挂载,也可以在 sysfs 中访问。但我扩展的这几个引脚实际上是一个拨码开关,我需要写一个驱动去访问这些引脚,所以:
dip_code {
compatible = "my,dip-code";
gpios = <&pca9557 0 GPIO_ACTIVE_HIGH>,
<&pca9557 1 GPIO_ACTIVE_HIGH>,
<&pca9557 2 GPIO_ACTIVE_HIGH>,
<&pca9557 3 GPIO_ACTIVE_HIGH>,
<&pca9557 4 GPIO_ACTIVE_HIGH>,
<&pca9557 5 GPIO_ACTIVE_HIGH>,
<&pca9557 6 GPIO_ACTIVE_HIGH>,
<&pca9557 7 GPIO_ACTIVE_HIGH>;
};
这里, lable pca9557 指向了我的设备,但问题在于,我的 pca9557 不是一个GPIO控制器,所以需要:
pca9557: pca9557@18 {
// ...
gpio-controller;
#gpio-cells = <2>;
};
这里我用 gpio_desc 来描述引脚:
struct dip_code_data {
struct gpio_desc **gpios;
int gpio_count;
};
实现两个只读( RO )的 attribute :
static int dip_code_read_value(struct dip_code_data *data)
{
int i, val = 0;
for (i = 0; i < data->gpio_count; i++) {
int bit = gpiod_get_value_cansleep(data->gpios[i]);
val |= (bit & 1) << i;
}
return val;
}
static ssize_t value_dec_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct dip_code_data *data = dev_get_drvdata(dev);
int val = dip_code_read_value(data);
return sprintf(buf, "%d\n", val);
}
static ssize_t value_hex_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct dip_code_data *data = dev_get_drvdata(dev);
int val = dip_code_read_value(data);
return sprintf(buf, "0x%X\n", val);
}
static DEVICE_ATTR_RO(value_dec);
static DEVICE_ATTR_RO(value_hex);
static struct attribute *dip_code_attrs[] = {
&dev_attr_value_dec.attr,
&dev_attr_value_hex.attr,
NULL,
};
static const struct attribute_group dip_code_attr_group = {
.attrs = dip_code_attrs,
};
这里, DEVICE_ATTR_RO 创建了两个 RO 的 attribute ,名字分别是 value_dec 和 value_hex ,这个宏会自动绑定到 *_show 函数,后者只是将 dip_code_read_value 的返回值写走 sprintf 写进 buf 。
然后就是 probe 和 remove 函数:
static int dip_code_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct dip_code_data *data;
int count, ret, i;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
// ...
count = of_property_count_elems_of_size(dev->of_node,
"gpios",
sizeof(u32) * 3);
// ...
data->gpio_count = count;
data->gpios = devm_kcalloc(dev, count, sizeof(struct gpio_desc *),
GFP_KERNEL);
// ...
for (i = 0; i < count; i++) {
data->gpios[i] = devm_gpiod_get_index(dev, NULL, i, GPIOD_IN);
// ...
}
platform_set_drvdata(pdev, data);
ret = sysfs_create_group(&dev->kobj, &dip_code_attr_group);
if (ret) {
dev_err(dev, "Failed to create sysfs group: %d\n", ret);
return ret;
}
return 0;
}
static int dip_code_remove(struct platform_device *pdev)
{
sysfs_remove_group(&pdev->dev.kobj, &dip_code_attr_group);
return 0;
}
probe 分别先通过 of 系列的函数从 DTS 里面读参数写进 data ,然后通过 platform_set_drvdata 注册以供后续访问,最后创建前面声明的 sysfs group ,而 remove 只需要将其移除就可以。
最后,元数据:
static const struct of_device_id dip_code_of_match[] = {
{ .compatible = "my,dip-code" },
{ }
};
MODULE_DEVICE_TABLE(of, dip_code_of_match);
static struct platform_driver dip_code_driver = {
.probe = dip_code_probe,
.remove = dip_code_remove,
.driver = {
.name = "dip-code",
.of_match_table = dip_code_of_match,
},
};
module_platform_driver(dip_code_driver);