Kernel 中的 GPIO 定义和控制

     最近要深一步用到GPIO口控制,写个博客记录下Kernel层的GPIO学习过程!


一、概念

     General Purpose Input Output (通用输入/输出)简称为GPIO,或 总线扩展器。也就是芯片的引脚,当微控制器或芯片组没有足够的I/O端口,或当系统需要采用远端串行通信或控制时,GPIO产品能够提供额外的控制和监视功能。通常在ARM里,所有I/O都是通用的,

每个GPIO端口包含8个管脚,如PA端口是PA0~PA7,而且GPIO口至少有两个寄存器,即"通用IO控制寄存器"与"通用IO数据寄存器"。数据寄存器的各位都直接引到芯片外部,而对这种寄存器中每一位的作用,即每一位的信号流通方向,则可以通过控制寄存器中对应位独立地加以设置。比如,可以设置某个引脚的属性为输入、输出或其他特殊功能。

常用的应该是:高阻输入、推挽输出、开漏输出。

通俗理解为 :

高阻输入—— 保持高阻抗状态,彻底断开输出,避免干扰,对总线状态不起作用,此时总线可由其他器件占用。

推挽输出——可以输出高,低电平,连接数字器件。

开漏输出——输出端相当于三极管的集电极. 要得到高电平状态需要上拉电阻才行。

软件上就是通过设置IO口的模式,然后控制IO的上拉下拉,写入对应寄存器,通过寄存器控制电路:

上拉寄存器是控制对应端口上拉使能(DE)的。当对应位为0时,设置对应引脚上拉使能,为1时,禁止对应引脚上拉使能。如果上拉寄存器使能,无论引脚功能寄存器如何设置(输入,输出,数据,中断等),对应引脚输出高电平。

上拉是一个电阻接到一个电压,其实就是增强IO的驱动能力,IO口是高电平。
下拉是一个电阻接到地,保证IO口是低电平。



二、kernel层调用接口实现

GPIO操作,在Kernel 2.6.32版本以上提供了gpio口管理的库文件 /kernel/drivers/gpio/gpiolib.c,里面就是我们需要的接口函数实现!

几个主要的方法:


申请一个pin脚作为gpio口,命名为 * label,如果经过判断空闲的 申请成功了做一些初始的bit位设置。

int gpio_request(unsigned gpio, const char *label)
{
struct gpio_desc	*desc;
struct gpio_chip	*chip;
int	 status = -EINVAL;
unsigned long	 flags;

spin_lock_irqsave(&gpio_lock, flags);
...
if (test_and_set_bit(FLAG_REQUESTED, &desc->flags) == 0)
...
}


释放这个gpio口,还原bit。

void gpio_free(unsigned gpio)
{
	unsigned long		flags;
	struct gpio_desc	*desc;
	struct gpio_chip	*chip;


	might_sleep();
...
clear_bit(FLAG_ACTIVE_LOW, &desc->flags);
clear_bit(FLAG_REQUESTED, &desc->flags);
clear_bit(FLAG_OPEN_DRAIN, &desc->flags);
clear_bit(FLAG_OPEN_SOURCE, &desc->flags);
...
}



设置gpio口为输入模式:

int gpio_direction_input(unsigned gpio)
{
	unsigned long		flags;
	struct gpio_chip	*chip;
	struct gpio_desc	*desc = &gpio_desc[gpio];
	int			status = -EINVAL;


	spin_lock_irqsave(&gpio_lock, flags);
	if (!gpio_is_valid(gpio))
		goto fail;
	chip = desc->chip;
...
	status = chip->direction_input(chip, gpio);
...
}

其中的 chip->direction_input(chip, gpio)为实现!


设置gpio口为输出模式 value为初始值 0为高电平/1为低电平:

int gpio_direction_output(unsigned gpio, int value)
{
	unsigned long		flags;
	struct gpio_chip	*chip;
	struct gpio_desc	*desc = &gpio_desc[gpio];
	int			status = -EINVAL;


	/* Open drain pin should not be driven to 1 */
	if (value && test_bit(FLAG_OPEN_DRAIN,  &desc->flags))
		return gpio_direction_input(gpio);
...
	status = chip->direction_output(chip, gpio, value);
...
}

其中的chip->direction_output(chip, gpio, value)为实现!



设置gpio口的值:

void __gpio_set_value(unsigned gpio, int value)
{
	struct gpio_chip	*chip;

	chip = gpio_to_chip(gpio);
	WARN_ON(chip->can_sleep);
	trace_gpio_value(gpio, 0, value);
	if (test_bit(FLAG_OPEN_DRAIN,  &gpio_desc[gpio].flags))
		_gpio_set_open_drain_value(gpio, chip, value);
	else if (test_bit(FLAG_OPEN_SOURCE,  &gpio_desc[gpio].flags))
		_gpio_set_open_source_value(gpio, chip, value);
	else
		chip->set(chip, gpio - chip->base, value);
}

获取gpio口的值:

int __gpio_get_value(unsigned gpio)
{
	struct gpio_chip	*chip;
	int value;

	chip = gpio_to_chip(gpio);
	WARN_ON(chip->can_sleep);
	value = chip->get ? chip->get(chip, gpio - chip->base) : 0;
	trace_gpio_value(gpio, 1, value);
	return value;
}

 对于有些挂载在I2C,SPI总线上的扩展GPIO,读写操作可能会导致睡眠,因此不能在中断函数中
  使用。使用下面的函数以区别于正常的GPIO

  int gpio_get_value_cansleep(unsigned gpio);//读GPIO
  void gpio_set_value_cansleep(unsigned gpio, int value);//写GPIO



三、gpiolib.c关联芯片接口

以上为gpiolib.c的基本方法都是向下调用到对应芯片的gpio实现!

所以每个方法里面的实现都是通过 struct gpio_chip*chip 这个指针调用结构体中关联的相关接口!


下面是gplolib怎么关联到特定的芯片(mach-at91)的几个主要方法:

mach-at91的/kernel/arch/arm/mach-at91/gpio.c中的

static struct at91_gpio_chip gpio_chip[] = {
	AT91_GPIO_CHIP("A", 0x00 + PIN_BASE, 32),
	AT91_GPIO_CHIP("B", 0x20 + PIN_BASE, 32),
	AT91_GPIO_CHIP("C", 0x40 + PIN_BASE, 32),
	AT91_GPIO_CHIP("D", 0x60 + PIN_BASE, 32),
	AT91_GPIO_CHIP("E", 0x80 + PIN_BASE, 32),
};

gpiolib.c中的接口与mach-at91函数接口对应关系如下:

#define AT91_GPIO_CHIP(name, base_gpio, nr_gpio)			\
	{								\
		.chip = {						\
			.label		  = name,			\
			.direction_input  = at91_gpiolib_direction_input, \
			.direction_output = at91_gpiolib_direction_output, \
			.get		  = at91_gpiolib_get,		\
			.set		  = at91_gpiolib_set,		\
			.dbg_show	  = at91_gpiolib_dbg_show,	\
			.base		  = base_gpio,			\
			.ngpio		  = nr_gpio,			\
		},							\
	}

从上面可以看到 在gpiolib.c中的方法调用芯片(mach-at91)的映射关系:

比如:gpio_direction_output() 设置gpio口为输出模式中最终的实现调用chip->direction_output(chip, gpio, value)。

从上面可以看出来,也就是调用mach-at91的at91_gpiolib_direction_output()!

其它的类似。


gpiolib.c中的

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

添加到结构体:

struct gpio_desc {
	struct gpio_chip	*chip;
	unsigned long		flags;
/* flag symbols are bit numbers */
#define FLAG_REQUESTED	0
#define FLAG_IS_OUT	1
#define FLAG_RESERVED	2
...
}

这就保存到了 chip 指针。

这一部分在Kernel初始化的时候调用执行!




四、芯片(mach-at91)gpio口的接口

由gpiolib.c根据映射调用对应接口

static int at91_gpiolib_direction_output(struct gpio_chip *chip,
					 unsigned offset, int val)
{
	struct at91_gpio_chip *at91_gpio = to_at91_gpio_chip(chip);
	void __iomem *pio = at91_gpio->regbase;
	unsigned mask = 1 << offset;

	__raw_writel(mask, pio + (val ? PIO_SODR : PIO_CODR));
	__raw_writel(mask, pio + PIO_OER);
	return 0;
}

定义为输出模式,初始设置val 上拉还是下拉 , 写入数据寄存器。

#define PIO_SODR	0x30	/* Set Output Data Register */
#define PIO_CODR	0x34	/* Clear Output Data Register */
#define PIO_ODR	 0x14	/* Output Disable Register */
#define PIO_PDSR	 0x3c	/* Pin Data Status Register */



同样设置为输入:

static int at91_gpiolib_direction_input(struct gpio_chip *chip,
					unsigned offset)
{
	struct at91_gpio_chip *at91_gpio = to_at91_gpio_chip(chip);
	void __iomem *pio = at91_gpio->regbase;
	unsigned mask = 1 << offset;

	__raw_writel(mask, pio + PIO_ODR);
	return 0;
}


设置GPIO口的value 值 0或者1,已经设置为输出模式下:

static void at91_gpiolib_set(struct gpio_chip *chip, unsigned offset, int val)
{
	struct at91_gpio_chip *at91_gpio = to_at91_gpio_chip(chip);
	void __iomem *pio = at91_gpio->regbase;
	unsigned mask = 1 << offset;

	__raw_writel(mask, pio + (val ? PIO_SODR : PIO_CODR));
}


读寄存器获取该GPIO口的值:

static int at91_gpiolib_get(struct gpio_chip *chip, unsigned offset)
{
	struct at91_gpio_chip *at91_gpio = to_at91_gpio_chip(chip);
	void __iomem *pio = at91_gpio->regbase;
	unsigned mask = 1 << offset;
	u32 pdsr;

	pdsr = __raw_readl(pio + PIO_PDSR);
	return (pdsr & mask) != 0;
}

大体流程就这样了。

撰写不易,转载请注明出处http://blog.csdn.net/jscese/article/details/16823519







  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
实现步骤如下: 1. 创建一个新的regulator驱动程序,命名为gpio-regulator.c。 2. 在驱动程序,包含必要的头文件,如regulator.h、gpio.h等。 3. 定义一个结构体,用于存储GPIO控制电源输出的相关信息,如GPIO编号、电源输出编号、电源输出状态等。 4. 实现驱动程序的probe函数,在该函数,注册GPIO设备并初始化GPIO控制电源输出的相关信息。 5. 实现驱动程序的set_voltage函数,该函数根据传入的电压设置电源输出,同时更新电源输出状态。 6. 实现驱动程序的get_voltage函数,该函数返回当前的电源输出电压。 7. 实现驱动程序的remove函数,该函数在驱动程序被卸载时释放GPIO资源。 示例代码如下: ```c #include <linux/regulator/driver.h> #include <linux/gpio.h> struct gpio_regulator { int gpio; int supply; bool enabled; }; static int gpio_regulator_set_voltage(struct regulator_dev *dev, unsigned int min_uV, unsigned int max_uV, unsigned int *selector) { struct gpio_regulator *regulator = rdev_get_drvdata(dev); if (min_uV == max_uV) { gpio_set_value(regulator->gpio, 0); regulator->enabled = false; } else { gpio_set_value(regulator->gpio, 1); regulator->enabled = true; } return 0; } static int gpio_regulator_get_voltage(struct regulator_dev *dev) { struct gpio_regulator *regulator = rdev_get_drvdata(dev); return regulator->enabled ? 3300000 : 0; } static const struct regulator_ops gpio_regulator_ops = { .set_voltage = gpio_regulator_set_voltage, .get_voltage = gpio_regulator_get_voltage, }; static int gpio_regulator_probe(struct platform_device *pdev) { struct gpio_regulator *regulator; struct regulator_dev *rdev; int ret; regulator = devm_kzalloc(&pdev->dev, sizeof(*regulator), GFP_KERNEL); if (!regulator) return -ENOMEM; regulator->gpio = platform_get_irq(pdev, 0); regulator->supply = of_get_named_gpio(pdev->dev.of_node, "supply-gpios", 0); regulator->enabled = false; gpio_request(regulator->gpio, "gpio-regulator"); gpio_direction_output(regulator->gpio, 0); rdev = devm_regulator_register(&pdev->dev, &gpio_regulator_ops, &regulator, NULL, "gpio-regulator", regulator->supply); if (IS_ERR(rdev)) { ret = PTR_ERR(rdev); goto err_gpio; } platform_set_drvdata(pdev, rdev); return 0; err_gpio: gpio_free(regulator->gpio); return ret; } static int gpio_regulator_remove(struct platform_device *pdev) { struct regulator_dev *rdev = platform_get_drvdata(pdev); struct gpio_regulator *regulator = rdev_get_drvdata(rdev); gpio_free(regulator->gpio); return 0; } static const struct of_device_id gpio_regulator_match[] = { { .compatible = "gpio-regulator", }, {}, }; MODULE_DEVICE_TABLE(of, gpio_regulator_match); static struct platform_driver gpio_regulator_driver = { .probe = gpio_regulator_probe, .remove = gpio_regulator_remove, .driver = { .name = "gpio-regulator", .of_match_table = gpio_regulator_match, }, }; module_platform_driver(gpio_regulator_driver); MODULE_AUTHOR("Your Name <your.email@example.com>"); MODULE_DESCRIPTION("GPIO regulator driver"); MODULE_LICENSE("GPL"); ``` 在该驱动程序,我们使用了一个GPIO控制多个电源输出的方式,即当GPIO输出低电平时,所有电源输出均关闭,当GPIO输出高电平时,所有电源输出均开启。在set_voltage函数,我们通过设置GPIO的输出状态来实现电源输出的开关。在get_voltage函数,我们返回当前的电源输出状态,如果所有电源输出均关闭,则返回0,否则返回3300000(即3.3V)。在probe函数,我们通过platform_get_irq和of_get_named_gpio函数获取GPIO和电源输出的编号,并注册GPIO设备和regulator设备。在remove函数,我们释放GPIO资源。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值