linux GPIO驱动的一些理解

linux GPIO驱动的一些理解

问题的引出

在一些驱动代码中经常看到gpiod_get_value,但有时也会看到gpiod_get_value_cansleep,二者有什么区别,什么时候该使用gpiod_get_value_cansleep

int gpiod_get_value_cansleep(const struct gpio_desc * desc);
int gpiod_get_value(const struct gpio_desc *desc);

源码对比

gpiod_get_value 的源码如下(gpiolib.c)

/**
 * gpiod_get_value() - return a gpio's value
 * @desc: gpio whose value will be returned
 *
 * Return the GPIO's logical value, i.e. taking the ACTIVE_LOW status into
 * account, or negative errno on failure.
 *
 * This function should be called from contexts where we cannot sleep, and will
 * complain if the GPIO chip functions potentially sleep.
 */
int gpiod_get_value(const struct gpio_desc *desc)
{
	int value;

	VALIDATE_DESC(desc);
	/* Should be using gpiod_get_value_cansleep() */
	WARN_ON(desc->gdev->chip->can_sleep);

	value = gpiod_get_raw_value_commit(desc);
	if (value < 0)
		return value;

	if (test_bit(FLAG_ACTIVE_LOW, &desc->flags))
		value = !value;

	return value;
}
EXPORT_SYMBOL_GPL(gpiod_get_value);

gpiod_get_value_cansleep 的源码如下

/**
 * gpiod_get_value_cansleep() - return a gpio's value
 * @desc: gpio whose value will be returned
 *
 * Return the GPIO's logical value, i.e. taking the ACTIVE_LOW status into
 * account, or negative errno on failure.
 *
 * This function is to be called from contexts that can sleep.
 */
int gpiod_get_value_cansleep(const struct gpio_desc *desc)
{
	int value;

	might_sleep_if(extra_checks);
	VALIDATE_DESC(desc);
	value = gpiod_get_raw_value_commit(desc);
	if (value < 0)
		return value;

	if (test_bit(FLAG_ACTIVE_LOW, &desc->flags))
		value = !value;

	return value;
}
EXPORT_SYMBOL_GPL(gpiod_get_value_cansleep);

gpiod_get_raw_value_commit 实现代码如下,留意chip->get

static int gpiod_get_raw_value_commit(const struct gpio_desc *desc)
{
	struct gpio_chip	*chip;
	int offset;
	int value;

	chip = desc->gdev->chip;
	offset = gpio_chip_hwgpio(desc);
	value = chip->get ? chip->get(chip, offset) : -EIO;
	value = value < 0 ? value : !!value;
	trace_gpio_value(desc_to_gpio(desc), 1, value);
	return value;
}

从源码中看,gpiod_get_value 如果desc->gdev->chip->can_sleep 不等于0,则会打印警告信息。
gpiod_get_value_cansleep 中might_sleep_if 是否生效取决于内核的配置(CONFIG_PREEMPT_VOLUNTARY),当前内核中未配置CONFIG_PREEMPT_VOLUNTARY 。

区别-解释

gpio-legacy.txt 文档中的话

https://www.kernel.org/doc/Documentation/gpio/gpio-legacy.txt
GPIO access that may sleep
-------------------------- Some GPIO controllers must be accessed using message based busses like I2C or SPI. Commands to read or write
those GPIO values require waiting to get to the head of a queue to
transmit a command and get its response. This requires sleeping, which
can’t be done from inside IRQ handlers.

Platforms that support this type of GPIO distinguish them from other
GPIOs by returning nonzero from this call (which requires a valid GPIO
number, which should have been previously allocated with
gpio_request):

int gpio_cansleep(unsigned gpio);

To access such GPIOs, a different set of accessors is defined:

/* GPIO INPUT: return zero or nonzero, might sleep */ int
gpio_get_value_cansleep(unsigned gpio);

/* GPIO OUTPUT, might sleep */ void gpio_set_value_cansleep(unsigned
gpio, int value);

Accessing such GPIOs requires a context which may sleep, for example
a threaded IRQ handler, and those accessors must be used instead of
spinlock-safe accessors without the cansleep() name suffix.

Other than the fact that these accessors might sleep, and will work on
GPIOs that can’t be accessed from hardIRQ handlers, these calls act
the same as the spinlock-safe calls.

有些GPIO控制器必须使用基于消息的总线(如I2C或SPI)访问。读取或写入这些GPIO值的命令需要等待到达队列的头部以传输命令并获得其响应。这样就需要允许睡眠,导致这类GPIO的访问不能在内部IRQ处理程序内(原子上下文)完成。访问这样的GPIO需要一个可以休眠的上下文,例如一个threaded IRQ处理程序,并且必须使用上述访问函数访问函数(而不是没有带cansleep()后缀的)。

简单的说就是有些GPIO控制器是外接扩展的,不是内部芯片集成的,一般扩展的接口多为I2C的接口,I2C在读写外部控制器的寄存器时是有时序要求的,增加延时等,这时控制外部的GPIO就必做在进度上下文,或者threaded IRQ 这些允许睡眠(延时)的地方使用,不能在IRQ处理程序内使用。

如何理解

gpiod_get_value_cansleep 接口的由来,当功能复杂,芯片本身的GPIO不够用时,需要接外部的GPIO扩展芯片来扩展GPIO,如下面列举了一种GPIO控制器–MCP23016

GPIO控制器

MCP23016 的芯片资料见链接

在这里插入图片描述注意看SCL,SDA 接口为I2C接口的。通过I2C接口控制了16个IO口

驱动程序如下

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/gpio.h>

#define GPIO_NUM 16
#define INPUT 1
#define OUTPUT 0

/* Chip Control registers */

/*
 * GP0 ang GP1 provides access to GPIO ports
 * A read from these register provide status of pin of these ports
 * A write to these register will modify the output latch register (OLAT0, OLAT1)
 * and data register.
 */
#define GP0		0x0
#define GP1		0x1

/*
 * IOLAT0 and IOLAT1 control the output value of GP0 and GP1
 * Write into one of these register will result in affecting 
 */ 
#define OLAT0	0x2
#define OLAT1	0x3
#define IOPOL0	0x4
#define IOPOL1	0x5

/*
 * IODIR0 and IODIR1 registers control GP0 and GP1 IOs direction
 * 1 => input; 0 => output
 * default value are 0xFF in each reg.
 */
#define IODIR0	0x6
#define IODIR1	0x7

/*
 * INTCAP0 and INTCAP1 register contain value of the port that generated the interupt
 * INTCAP0 contains value of GPO at time of GPO change interrupt
 * INTCAP1 contains value of GP1 at time of GP1 change interrupt
 */
#define INTCAP0	0x8
#define INTCAP1	0x9

struct mcp23016 {
	struct i2c_client *client;
	struct gpio_chip chip;
};

static inline struct mcp23016 *to_mcp23016(struct gpio_chip *gc)
{
	return container_of(gc, struct mcp23016, chip);
}

static int mcp23016_get_value(struct gpio_chip *gc, unsigned offset)
{
	s32 value;
	struct mcp23016 *mcp = to_mcp23016(gc);
	unsigned bank = offset / 8 ;
	unsigned bit = offset % 8 ;
	
	u8 reg_intcap = (bank == 0) ? INTCAP0 : INTCAP1;
	value = i2c_smbus_read_byte_data(mcp->client, reg_intcap);
	return (value >= 0) ? (value >> bit) & 0x1 : 0;
}

static int mcp23016_set(struct mcp23016 *mcp, unsigned offset, int val)
{
	s32 value;

	unsigned bank = offset / 8 ;
	u8 reg_gpio = (bank == 0) ? GP0 : GP1;
	unsigned bit = offset % 8 ;

	value = i2c_smbus_read_byte_data(mcp->client, reg_gpio);
	if (value >= 0) {
		if (val)
			value |= 1 << bit;
		else
			value &= ~(1 << bit);

		return i2c_smbus_write_byte_data(mcp->client, reg_gpio, value);
	}

	return value;
}


static void mcp23016_set_value(struct gpio_chip *gc, unsigned offset, int val)
{
	struct mcp23016 *mcp = to_mcp23016(gc);
	mcp23016_set(mcp, offset, val);
}

/*
 * direction = 1 => input
 * direction = 0 => output
 */
static int mcp23016_direction(struct gpio_chip *gc, unsigned offset,
                                unsigned direction, int val)
{
	struct mcp23016 *mcp = to_mcp23016(gc);
	unsigned bank = offset / 8 ;
	unsigned bit = offset % 8 ;
	u8 reg_iodir = (bank == 0) ? IODIR0 : IODIR1;
	s32 iodirval = i2c_smbus_read_byte_data(mcp->client, reg_iodir);

	if (direction)
		iodirval |= 1 << bit;
	else
		iodirval &= ~(1 << bit);

	i2c_smbus_write_byte_data(mcp->client, reg_iodir, iodirval);
	if (direction)
		return iodirval ;
	else
		return mcp23016_set(mcp, offset, val);    
}

static int mcp23016_direction_output(struct gpio_chip *gc,
                                    unsigned offset, int val)
{
	return mcp23016_direction(gc, offset, OUTPUT, val);
}

static int mcp23016_direction_input(struct gpio_chip *gc,
                                    unsigned offset)
{
	return mcp23016_direction(gc, offset, INPUT, 0);
}

static const struct of_device_id mcp23016_ids[] = {
	{ .compatible = "microchip,mcp23016", },
	{ /* sentinel */ }
};

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->chip.ngpio = GPIO_NUM;
	mcp->chip.can_sleep = 1;
	mcp->chip.get = 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);
}

static int mcp23016_remove(struct i2c_client *client)
{
	struct mcp23016 *mcp;
	mcp = i2c_get_clientdata(client);
	gpiochip_remove(&mcp->chip);
	return 0;
}

static const struct i2c_device_id mcp23016_id[] = {
	{"mcp23016", 0},
	{},
};

MODULE_DEVICE_TABLE(i2c, mcp23016_id);

static struct i2c_driver mcp23016_i2c_driver = {
	.driver = {
		.owner = THIS_MODULE,
		.name = "mcp23016",
		.of_match_table = of_match_ptr(mcp23016_ids),
	},
	.probe = mcp23016_probe,
	.remove = mcp23016_remove,
	.id_table = mcp23016_id,
};

module_i2c_driver(mcp23016_i2c_driver);

重点关注一下probe

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->chip.ngpio = GPIO_NUM;
	mcp->chip.can_sleep = 1;   
	mcp->chip.get = 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);
}

注意这里 ** mcp->chip.can_sleep = 1; ** 这里决定了上述的gpio是否 can_sleep 。
gpiochip_add(&mcp->chip); mcp->chip.get 与上面提到的chip->get 关联起来。

总结

在内核未配置CONFIG_PREEMPT_VOLUNTARY 的情况下,二者从代码的角度几乎一样,gpiod_get_value_cansleep 给代码一个提示表示可以进行进程上下文,不能放IRQ中断服务程序中。但是外接I2C的GPIO应用场景不同。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值