yanghn2002

View My GitHub Profile

需求是这样的,在用 PCA955x 通过 IIC 扩展 GPIO 来实现一系列拨码开关的访问,需要通过拨码引脚输入的二进制值拼成 10/16 整数在 sysfs 通过一个只读文件暴露给用户空间访问。

DTS 配置

当然首先需要:

	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 创建了两个 ROattribute ,名字分别是 value_decvalue_hex ,这个宏会自动绑定到 *_show 函数,后者只是将 dip_code_read_value 的返回值写走 sprintf 写进 buf

然后就是 proberemove 函数:

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);