每个SOC芯片厂家BSP包,基于drivers/gpio/gpiolib.c下封装一层属于自己的GPIO驱动。
生成的驱动节点一般在/sys/devices/platform/gpio/gpioxxx
上层应用程序可以通过该节点下的value:/sys/devices/platform/gpio/gpioxxx/value,来对GPIO引脚进行输出电平控制。
但是xxx是底层驱动通过某个公司换算出来的一组数值,用户无法明确知道对应的是哪个GPIO口。
所以可以自己在SOC厂家的驱动基础上再封装一层,让用户可以明确对应的GPIO。
比如led1的GPIO控制
1. 首先注册实际的GPIO控制节点:/sys/devices/platform/gpio/gpioxxx
2. 在/sys/devices/platform/下注册user_gpio节点
3. 在/sys/devices/platform/user_gpio下创建led1链接,指向/sys/devices/platform/gpio/gpioxxx
/sys/devices/platform/user_gpio/led1 --> /sys/devices/platform/gpio/gpioxxx
用户可以通过控制led1节点下的value来对led灯进行控制
以下是具体的代码实例:
设备树实例:
/* ****************************************************************************
* USER-GPIO
*/
/ {
local_gpio: daughter-gpio {
compatible = "Arm,user-gpio";
status = "okay";
led1 {
label = "led1-ctl";
gpios = <&gpio5 24 0>;
output-low;
};
};
remote_gpio: mother-gpio {
compatible = "Arm,user-gpio";
status = "disabled";
};
};
驱动代码实例:
drivers/gpio/gpio-arm-user-gpio.c
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/driver.h>
#include <linux/gpio.h>
#include "gpiolib.h"
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/err.h>
#include <linux/kref.h>
#include <linux/gpio/consumer.h>
#include <linux/device.h>
#include <dt-bindings/gpio/gpio.h>
struct Arm_user_gpio {
struct device dev;
struct kref ref;
};
static struct Arm_user_gpio *user_gpio;
static struct kobj_type Armgpio_ktype;
/**
* Arm_user_gpio_release() - free all resources when all instances are removed
*
* @ref : kref used
*/
static void Arm_user_gpio_release(struct kref *ref)
{
kobject_del(&user_gpio->dev.kobj);
kfree(user_gpio);
user_gpio = NULL;
}
/**
*
*/
static void Arm_user_gpiod_release(struct device *dev, void *res)
{
struct gpio_desc **desc = res;
dev_dbg(dev, "clean-up : %s\n", (*desc)->label);
/* Remove specific symlinks */
sysfs_remove_link(&dev->kobj, (*desc)->label);
sysfs_remove_link(&user_gpio->dev.kobj, (*desc)->label);
/* free requested gpio */
gpiod_put(*desc);
}
static int Arm_user_gpio_request(struct device *dev, struct device_node *np)
{
int ret;
struct gpio_desc **dr;
unsigned long lflags = 0;
enum of_gpio_flags flags;
enum gpiod_flags dflags = 0;
struct gpio_desc *desc = NULL;
const char *label = NULL;
of_property_read_string(np, "label", &label);
if (!label) {
dev_err(dev, "node require a 'label' property");
return -EINVAL;
}
dr = devres_alloc(Arm_user_gpiod_release, sizeof(*dr), GFP_KERNEL);
if (!dr)
return -ENOMEM;
if (of_property_read_bool(np, "input"))
dflags |= GPIOD_IN;
else if (of_property_read_bool(np, "output-low"))
dflags |= GPIOD_OUT_LOW;
else if (of_property_read_bool(np, "output-high"))
dflags |= GPIOD_OUT_HIGH;
else {
dev_err(dev, "need to setup direction property for %s", label);
ret = -EINVAL;
goto err_devres;
}
desc = of_get_named_gpiod_flags(np, "gpios", 0, &flags);
if (!desc) {
dev_err(dev, "no entity for %s", label);
ret = -ENOENT;
goto err_devres;
} else if (IS_ERR(desc)) {
dev_err(dev, "error %ld for %s", PTR_ERR(desc), label);
ret = PTR_ERR(desc);
goto err_devres;
}
ret = gpiod_request(desc, label);
if (ret) {
dev_err(dev, "fail request for %s", label);
goto err_devres;
}
if (flags & OF_GPIO_ACTIVE_LOW)
lflags |= GPIO_ACTIVE_LOW;
if (flags & OF_GPIO_SINGLE_ENDED) {
if (flags & OF_GPIO_OPEN_DRAIN)
lflags |= GPIO_OPEN_DRAIN;
else
lflags |= GPIO_OPEN_SOURCE;
}
ret = gpiod_configure_flags(desc, label, lflags, dflags);
if (ret < 0) {
dev_err(dev, "failed to configure flags for %s", label);
goto err_request;
}
ret = gpiod_export(desc, false);
if (ret) {
dev_err(dev, "fail to export gpio %s", label);
goto err_request;
}
/* Set up symlinks:
* - from /sys/.../dev/name to /sys/class/gpio/gpioN
* - from /sys/.../dev/name to /sys/devices/platform/user_gpio/<label>
* <label> is the one defined in associated device-tree property.
*/
gpiod_export_link(&user_gpio->dev, label, desc);
gpiod_export_link(dev, label, desc);
*dr = desc;
devres_add(dev, dr);
return 0;
err_request:
gpiod_put(desc);
err_devres:
devres_free(*dr);
return ret;
}
static int Arm_user_gpio_of(struct device *dev)
{
struct device_node *np = dev->of_node;
struct device_node *child = NULL;
/* return -EPROBE_DEFER, if the referenced GPIO controller does
* not have gpiochip registered at the moment
*/
for_each_available_child_of_node(np, child)
if (of_get_gpio(child, 0) == -EPROBE_DEFER)
return -EPROBE_DEFER;
/* for each child */
for_each_child_of_node(np, child)
Arm_user_gpio_request(dev, child);
return 0;
}
/* ----------------------------------------------------------------------------
*/
static int Arm_user_gpio_probe(struct platform_device *pdev)
{
struct device *dev;
int res;
dev = &pdev->dev;
if (!user_gpio) {
user_gpio = kzalloc(sizeof(*user_gpio), GFP_KERNEL);
if (!user_gpio) {
res = -ENOMEM;
goto exit;
}
kref_init(&user_gpio->ref);
/* To allow gpios under /sys/devices/platform/user_gpio */
res = kobject_init_and_add(&user_gpio->dev.kobj,
&Armgpio_ktype,
dev->kobj.parent, "user_gpio");
if (res) {
dev_err(dev, "Can't initialize fake device\n");
kfree(user_gpio);
goto exit;
}
} else {
kref_get(&user_gpio->ref);
}
res = Arm_user_gpio_of(dev);
if (res)
goto exit_all;
platform_set_drvdata(pdev,dev);
goto exit;
exit_all:
kref_put(&user_gpio->ref, Arm_user_gpio_release);
exit:
return res;
}
static int Arm_user_gpio_remove(struct platform_device *pdev)
{
kref_put(&user_gpio->ref, Arm_user_gpio_release);
return 0;
}
static const struct of_device_id Arm_user_gpio_of_match[] = {
{ .compatible = "Arm,user-gpio", },
{ },
};
MODULE_DEVICE_TABLE(of, Arm_user_gpio_of_match);
static struct platform_driver Arm_user_gpio_driver = {
.probe = Arm_user_gpio_probe,
.remove = Arm_user_gpio_remove,
.driver = {
.name = "user_gpio",
.owner = THIS_MODULE,
.of_match_table = Arm_user_gpio_of_match,
},
};
module_platform_driver(Arm_user_gpio_driver);
MODULE_AUTHOR("Arm SA");
MODULE_LICENSE("GPL v2");