Linux GPIO子系统分析

在设备驱动中对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);
内核设计真是很美!




猜你喜欢

转载自blog.csdn.net/u012830148/article/details/80513693