在设备驱动中对GPIO的操作是非常普遍的,linux内核为我们提供了GPIO子系统,方便用户使用,它为用户提供了GPIO的统一操作接口,用户不需要关心底层实现,因为这是芯片厂商需要关心的,芯片厂商会去做这一部分工作。
接着我们就从驱动入手来看一下gpio子系统的框架。
gpio_direction_output(gmac->resetgpio, 1);
mdelay(30);
gpio_direction_output(gmac->resetgpio, 0);
mdelay(30);
gpio_direction_output(gmac->resetgpio, 1);
mdelay(40);
上面几行代码就是从驱动中摘出来的,很简单,就是写指定gpio的值,并设定为输出模式。
gpio_direction_output是内核提供给我们的统一接口,方便我们去操作gpio,那么他是怎么操作到最终的芯片寄存器呢?
带着这个问题我们进入gpio_direction_output函数看一下。
//driver/gpio/gpiolib.c
int gpio_direction_output(unsigned gpio, int value)
{
return gpiod_direction_output(gpio_to_desc(gpio), value);
}
EXPORT_SYMBOL_GPL(gpio_direction_output);
他直接调用了gpiod_direction_output;
static int gpiod_direction_output(struct gpio_desc *desc, int value)
{
unsigned long flags;
struct gpio_chip *chip;
int status = -EINVAL;
int offset;
.... //前面做了一些各种检查相关的工作,这不是重点
chip = desc->chip;
if (!chip || !chip->set || !chip->direction_output)
goto fail;
status = gpio_ensure_requested(desc);
if (status < 0)
goto fail;
might_sleep_if(chip->can_sleep);
offset = gpio_chip_hwgpio(desc);
if (status) {
status = chip->request(chip, offset);
if (status < 0) {
pr_debug("GPIO-%d: chip request fail, %d\n",
desc_to_gpio(desc), status);
/* and it's not available to anyone else ...
* gpio_request() is the fully clean solution.
*/
goto lose;
}
}
status = chip->direction_output(chip, offset, value); //重点在这个回调函数,应该是调用的这个函数去设置的CPU的寄存器,最终设置GPIO的。
if (status == 0)
set_bit(FLAG_IS_OUT, &desc->flags);
trace_gpio_value(desc_to_gpio(desc), 0, value);
trace_gpio_direction(desc_to_gpio(desc), 0, status);
}
我们猜测应该是调用的chip->direction_output(chip, offset, value);这个回调函数最终设置的寄存器,那么这接下来应该就是跟芯片紧密联系的,这部分工作应该是芯片厂商去完成的。
gpio子系统在kernel/drivers/gpio/目录下面。这里有跟平台相关的文件,这就是厂商提供的。
songchong@srv-artek-pad:~/GS700E/android/kernel/drivers/gpio$ ls
devres.c gpio-bt8xx.c gpio-generic.c gpiolib-of.c gpio-mcp23s08.c gpio-mxc.c gpio-pxa.c gpio-sta2x11.c gpio-tps65910.c gpio-wm831x.c
gpio-74x164.c gpio-clps711x.c gpio-grgpio.c gpio-lpc32xx.c gpio-ml-ioh.c gpio-mxs.c gpio-rc5t583.c gpio-stmpe.c gpio-tps65912.c gpio-wm8350.c
gpio-adnp.c gpio-cs5535.c gpio-ich.c gpio-lynxpoint.c gpio-mm-lantiq.c gpio-omap.c gpio-rcar.c gpio-stp-xway.c gpio-ts5500.c gpio-wm8994.c
gpio-adp5520.c gpio-da9052.c gpio-it8761e.c gpio-max7300.c gpio-mpc5200.c gpio-owl.c gpio-rdc321x.c gpio-sx150x.c gpio-twl4030.c gpio-xilinx.c
gpio-adp5588.c gpio-da9055.c gpio-janz-ttl.c gpio-max7301.c gpio-mpc8xxx.c gpio-palmas.c gpio-sa1100.c gpio-tc3589x.c gpio-twl6040.c Kconfig
gpio-amd8111.c gpio-davinci.c gpio-ks8695.c gpio-max730x.c gpio-msic.c gpio-pca953x.c gpio-samsung.c gpio-tegra.c gpio-ucb1400.c Makefile
gpio-arizona.c gpio-em.c gpio-langwell.c gpio-max732x.c gpio-msm-v1.c gpio-pcf857x.c gpio-sch.c gpio-timberdale.c gpio-viperboard.c
gpio-atc2603c-sgpio.c gpio-ep93xx.c gpiolib-acpi.c gpio-mc33880.c gpio-msm-v2.c gpio-pch.c gpio-sodaville.c gpio-tnetv107x.c gpio-vr41xx.c
gpio-atc260x.c gpio-ge.c gpiolib.c gpio-mc9s08dz60.c gpio-mvebu.c gpio-pl061.c gpio-spear-spics.c gpio-tps6586x.c gpio-vx855.c
各种各样的厂商的都有的。我们看看炬芯的gpio-owl.c文件,直接去看probe函数。
//省略不必要的
static int owl_gpio_probe(struct platform_device *pdev)
{
const struct of_device_id *of_id =
of_match_device(owl_gpio_dt_ids, &pdev->dev);
struct device_node *np = pdev->dev.of_node;
struct device *dev = &pdev->dev;
const struct owl_gpio_bank_data *pdata;
struct owl_gpio_bank *bank;
int ret;
bank = devm_kzalloc(dev, sizeof(*bank), GFP_KERNEL); //分配一个bank结构体
if (bank == NULL)
return -ENOMEM;
bank->id = of_alias_get_id(np, "gpio");
if (bank->id < 0)
return bank->id;
bank->devid = (enum owl_gpio_id) of_id->data;
pr_info("GPIO%c initialization\n", 'A' + bank->id);
if (is_s900_gpio(bank)) {
BUG_ON(bank->id >= ARRAY_SIZE(s900_banks_data));
pdata = &s900_banks_data[bank->id];
} else if (is_s700_gpio(bank)) {
BUG_ON(bank->id >= ARRAY_SIZE(s700_banks_data));
pdata = &s700_banks_data[bank->id];
} else if (is_ats3605_gpio(bank)) {
BUG_ON(bank->id >= ARRAY_SIZE(ats3605_banks_data));
pdata = &ats3605_banks_data[bank->id];
}else {
return -ENOENT;
}
bank->base = of_iomap(dev->of_node, 0);
if (IS_ERR(bank->base))
return -ENOENT;
bank->irq = irq_of_parse_and_map(np, 0);
if (bank->irq < 0) {
dev_err(dev, "failed to get IRQ");
return -ENOENT;
}
bank->nr_gpios = pdata->nr_gpios;
bank->regs = &pdata->regs;
bank->pdev = pdev;
spin_lock_init(&bank->lock);
owl_gpio_bank_setup(bank); //设置各种bank回调函数
platform_set_drvdata(pdev, bank);
ret = owl_gpio_irq_setup(bank);
if (ret < 0) {
dev_err(dev, "failed to setup irq, ret %d\n", ret);
return ret;
}
ret = gpiochip_add(&bank->gpio_chip); //将设置好的bank结构注册进gpio子系统中去,注册进去之后驱动中就能使用封装好的接口了。
if (ret < 0) {
dev_err(dev, "failed to add GPIO chip, ret %d\n", ret);
return ret;
}
return 0;
}
上面最重要的两个步骤就是设置回调函数,和将bank结构注册进gpio子系统。
//各种回调函数都是由厂商提供的,这里我们就不细看了,这跟芯片平台紧密相关。
static int owl_gpio_bank_setup(struct owl_gpio_bank *bank)
{
struct gpio_chip *chip = &bank->gpio_chip;
chip->base = bank->id * GPIO_PER_BANK;
chip->ngpio = bank->nr_gpios;
chip->request = owl_gpio_request;
chip->free = owl_gpio_free;
chip->get = owl_gpio_get;
chip->set = owl_gpio_set;
chip->direction_input = owl_gpio_direction_input;
chip->direction_output = owl_gpio_direction_output;
chip->to_irq = owl_gpio_to_irq;
chip->owner = THIS_MODULE;
chip->dev = &bank->pdev->dev;
chip->label = dev_name(chip->dev);
chip->of_node = chip->dev->of_node;
chip->owner = THIS_MODULE;
return 0;
}
接着看一下是怎么注册到gpio子系统的。
int gpiochip_add(struct gpio_chip *chip)
{
unsigned long flags;
int status = 0;
unsigned id;
int base = chip->base;
if ((!gpio_is_valid(base) || !gpio_is_valid(base + chip->ngpio - 1))
&& base >= 0) {
status = -EINVAL;
goto fail;
}
spin_lock_irqsave(&gpio_lock, flags);
if (base < 0) {
base = gpiochip_find_base(chip->ngpio);
if (base < 0) {
status = base;
goto unlock;
}
chip->base = base;
}
status = gpiochip_add_to_list(chip);
if (status == 0) {
chip->desc = &gpio_desc[chip->base];
for (id = 0; id < chip->ngpio; id++) {
struct gpio_desc *desc = &chip->desc[id];
desc->chip = chip;
/* REVISIT: most hardware initializes GPIOs as
* inputs (often with pullups enabled) so power
* usage is minimized. Linux code should set the
* gpio direction first thing; but until it does,
* and in case chip->get_direction is not set,
* we may expose the wrong direction in sysfs.
*/
desc->flags = !chip->direction_input
? (1 << FLAG_IS_OUT)
: 0;
}
}
spin_unlock_irqrestore(&gpio_lock, flags);
#ifdef CONFIG_PINCTRL
INIT_LIST_HEAD(&chip->pin_ranges);
#endif
of_gpiochip_add(chip);
if (status)
goto fail;
status = gpiochip_export(chip);
if (status)
goto fail;
pr_debug("gpiochip_add: registered GPIOs %d to %d on device: %s\n",
chip->base, chip->base + chip->ngpio - 1,
chip->label ? : "generic");
return 0;
unlock:
spin_unlock_irqrestore(&gpio_lock, flags);
fail:
/* failures here can mean systems won't boot... */
pr_err("gpiochip_add: gpios %d..%d (%s) failed to register\n",
chip->base, chip->base + chip->ngpio - 1,
chip->label ? : "generic");
return status;
}
EXPORT_SYMBOL_GPL(gpiochip_add);
内核设计真是很美!