GPIO控制器驱动-gpio_chip
在中,我们处理了GPIO lines。这些lines通过⼀个叫做GPIO控制器的特殊设备向系统开放。本章将逐步解释如何为这些设备编写驱动程序,因此包括以下主题:
GPIO控制器驱动结构和数据结构
GPIO控制器的Sysfs接⼝
GPIO控制器在DT中的表⽰
驱动架构和数据结构
此类设备的驱动程序应提供以下内容:
建⽴GPIO⽅向(输⼊输出)的⽅法。
⽤于访问GPIO值的⽅法(get和set)。
将给定的GPIO映射到IRQ并返回相关的编号的⽅法。
⼀个表⽰对其⽅法的调⽤是否可以休眠的标志。这⼀点⾮常重要。
⼀个可选的debugfs转储⽅法(显⽰额外的状态,如pullup config)。
⼀个叫做base number的可选的编号,GPIO编号应该从它开始。如果省略,它将被⾃动分配。
在内核中,GPIO控制器被表⽰为在linux/ GPIO /driver.h中定义的结构体gpio_chip的实例:
struct gpio_chip {
const char *label;
struct device *dev;
struct module *owner;
int (*request)(struct gpio_chip *chip, unsigned offset);
void (*free)(struct gpio_chip *chip, unsigned offset);
int (*get_direction)(struct gpio_chip *chip, unsigned offset);
int (*direction_input)(struct gpio_chip *chip, unsigned offset);
int (*direction_output)(struct gpio_chip *chip, unsigned offset,
int value);
int (*get)(struct gpio_chip *chip,unsigned offset);
void (*set)(struct gpio_chip *chip, unsigned offset, int value);
void (*set_multiple)(struct gpio_chip *chip, unsigned long *mask,
unsigned long *bits);
int (*set_debounce)(struct gpio_chip *chip, unsigned offset,
unsigned debounce);
int (*to_irq)(struct gpio_chip *chip, unsigned offset);
int base;
u16 ngpio;
const char *const *names;
bool can_sleep;
bool irq_not_threaded;
bool exported;
#ifdef CONFIG_GPIOLIB_IRQCHIP
/*
* With CONFIG_GPIOLIB_IRQCHIP we get an irqchip
* inside the gpiolib to handle IRQs for most practical cases.
*/
struct irq_chip *irqchip;
struct irq_domain *irqdomain;
unsigned int irq_base;
irq_flow_handler_t irq_handler;
unsigned int irq_default_type;
#endif
#if defined(CONFIG_OF_GPIO)
/*
* If CONFIG_OF is enabled, then all GPIO controllers described in the
* device tree automatically may have an OF translation
*/
struct device_node *of_node;
int of_gpio_n_cells;
int (*of_xlate)(struct gpio_chip *gc,
const struct of_phandle_args *gpiospec, u32 *flags);
};
下⾯是结构中每个元素的含义:
request 是特定芯⽚激活的可选回调函数。如果提供了,在调⽤gpio_request()或gpiod_get()时,它会在分配GPIO之前执⾏。
free 是⼀个可选的回调函数,⽤于特定芯⽚的释放。如果提供了,那么在调⽤gpiod_put()或gpio_free()时,它会在GPIO被释放之前执⾏。
get_direction 在您需要知道⽅向的时候执⾏GPIO偏移量。返回值应为0表⽰out, 1表⽰in(与GPIOF_DIR_XXX相同),或负错误。
direction_input 将信号偏移量offset配置为输⼊,否则返回错误。
get 返回GPIO offset 的值;对于输出信号,这将返回实际感知到的值或0。
set 指定⼀个输出值给GPIO offset。
当需要为 mask 定义的多个信号分配输出值时,调⽤ set_multiple。如果没有提供,内核将安装⼀个通⽤回调函数,它将遍历掩码位并在每个位执⾏chip->set(i)。
请看下⾯的代码,它展⽰了如何实现这个函数:
static void gpio_chip_set_multiple(struct gpio_chip *chip, unsigned long *mask, unsigned long *bits)
{
if (chip->set_multiple) {
chip->set_multiple(chip, mask, bits);
} else {
unsigned int i;
/* set outputs if the corresponding mask bit is set */
for_each_set_bit(i, mask, chip->ngpio)
chip->set(chip, i, test_bit(i, bits));
}
}
如果控制器⽀持,这个钩⼦是⼀个可选的回调函数,⽤于为指定的GPIO设置防抖时间(GPIO设置为输⼊时可以设置防抖时间)。
to_irq 是⼀个可选钩⼦,⽤于提供GPIO到IRQ的映射。当您想要执⾏gpio_to_irq()或gpiod_to_irq()函数时,就会调⽤这个函数。这个实现可能不会休眠。
base 标识该芯⽚处理的第⼀个GPIO号;或者,如果注册时为负数,内核将⾃动(动态)分配⼀个。
ngpio 是这个控制器提供的gpio数;它从 base 开始到 (base + ngpio - 1)。
names,如果设置的话,对于这个芯⽚上的GPIOs,必须是⼀个字符串数组作为⼀个替代名称来使⽤。
can_sleep 是⼀个布尔标志,如果get()/set()⽅法可以休眠,则设置它。对于位于总线上的GPIO控制器(也称为expander),例如I2C或SPI,它的访问可能导致睡眠。这意味着,如果芯⽚⽀持IRQ,这些IRQ
需要被线程化,因为芯⽚访问可能会休眠,例如,读取IRQ状态寄存器。对于映射到内存(SoC的⼀部分)的GPIO控制器,这可以设置为false。
irq_not_threads 是⼀个布尔值标志,如果设置了can_sleep,则必须设置irq_not_threads,但是IRQs不需要被线程化。
每个芯⽚导出了⼀些信号,在⽅法调⽤中通过0 (ngpio - 1)范围内的偏移值来识别。当这些信号通过诸如gpio_get_value(gpio)之类的调⽤被引⽤时,偏移量通过gpio数减去基数(base)来计算。
在定义了每个回调函数并设置了其他字段之后,您应该在配置的结构gpio_chip结构上调⽤gpiochip_add(),以便将控制器注册到内核。当需要注销时,请使⽤gpiochip_remove()。你可以发现,编写⾃⼰的GPIO控制器驱动程序是多么容易。
要编写GPIO controller驱动程序,你需要包含有以下的头⽂件:
#include <linux/gpio.h>ios 字符串转数组
下⾯是控制器驱动程序的部分摘录:
#define GPIO_NUM 16
struct mcp23016 {
struct i2c_client *client;
struct gpio_chip chip;
};
static int mcp23016_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct mcp23016 *mcp;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
return -EIO;
mcp = devm_kzalloc(&client->dev, sizeof(*mcp), GFP_KERNEL);
if (!mcp)
return -ENOMEM;
mcp->chip.label = client->name;
mcp->chip.base = -1;
mcp->chip.dev = &client->dev;
mcp->chip.owner = THIS_MODULE;
mcp-&pio = GPIO_NUM; /* 16 */
mcp->chip.can_sleep = 1; /* may not be accessed from atomic context */
mcp-& = mcp23016_get_value;
mcp->chip.set = mcp23016_set_value;
mcp->chip.direction_output = mcp23016_direction_output;
mcp->chip.direction_input = mcp23016_direction_input;
mcp->client = client;
i2c_set_clientdata(client, mcp);
return gpiochip_add(&mcp->chip);
}
要从控制器驱动程序中请求⼀个⾃有的GPIO,你不应该使⽤gpio_request()。GPIO驱动程序可以使⽤以下函数来请求和释放描述符,⽽不必永远被固定在内核上:
struct gpio_desc *gpiochip_request_own_desc(struct gpio_desc *desc, const char *label)
void gpiochip_free_own_desc(struct gpio_desc *desc)
使⽤gpiochip_request_own_desc()请求的描述符必须使⽤gpiochip_free_own_desc()释放。
Pin controller指南
取决于你写驱动程序的控制器,你可能需要实现⼀个引脚控制操作来处理引脚复⽤,配置,等等:
对于只能做简单GPIO的引脚控制器,⼀个简单的结构gpio_chip就⾜够处理它了。没有必要建⽴⼀个struct pinctrl_desc结构,只需写个GPIO控制器驱动程序。
如果控制器可以在GPIO功能之上产⽣中断,必须建⽴⼀个irq_chip结构并注册到IRQ⼦系统。
对于⼀个具有引脚复⽤、⾼级引脚驱动强度和复杂偏置的控制器,您应该设置以下三个接⼝:
struct gpio_chip
struct irq_chip
struct pinctrl_desc,内核⽂档中有很好的解释
GPIO控制器的Sysfs接⼝
gpiochip_add()成功后,将创建⼀个路径为/sys/class/gpio/gpiochipX/的⽬录条⽬,其中X是gpio控制器base(提供以#X开始的gpio的控制器),具有以下属性:
base,其值与X相同,对应于gpio_chip.base(如果静态分配)并且是这个芯⽚管理的第⼀个GPIO。
label,它是为诊断提供的(并不总是唯⼀的)。
ngpio,它告诉了这个控制器提供了多少gpio (N 到 N + ngpio - 1).这与 pios 中定义的相同。
以上所有属性都是只读的。
GPIO控制器和DT
在DT中声明的每个GPIO控制器都必须具有 gpio-controller 的布尔属性集。⼀些控制器提供映射到GPIO的IRQs。在这种情况下,也应该设置interrupt-cells属性;通常使⽤2,但这取决于需要。第⼀个 cell 是引脚号码,第⼆个 cell 代表中断标志。
应该设置gpio-cells,以确定使⽤多少个cell来描述GPIO指⽰符。通常使⽤<2>,第⼀个cell ⽤来标识GPIO号,第⼆个cell ⽤来标识标志。实际上,⼤多数⾮内存映射的GPIO控制器不使⽤标记:
expander_1: mcp23016@27 {
compatible = "microchip,mcp23016";
interrupt-controller;
gpio-controller;
#gpio-cells = <2>;
interrupt-parent = <&gpio6>;
interrupts = <31 IRQ_TYPE_LEVEL_LOW>;
reg = <0x27>; /* i2c slave address */
#interrupt-cells=<2>;
};
上⾯的⽰例是我们的⼀个 gpio-controller设备(mcp23016)的设备树节点。
本⽂是编写GPIO控制器驱动程序的基础,它解释了这种设备主要⽤到的结构。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论