Linux 驱动---------gpio配置和基本应用

本文详细介绍了GPIO的各种工作模式,包括高阻输入、推挽输出和开漏输出,并深入探讨了Linux内核2.6.32及以上版本中GPIO驱动的实现原理。文章通过具体的函数调用流程解释了GPIO配置过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

GPIO的几种模式:

modeAre
高阻输入保持高阻抗状态,彻底断开输出,避免干扰,对总线状态不起作用,此时总线可由其他器件占用。
推挽输出可以输出高,低电平,连接数字器件。
开漏输出输出端相当于三极管的集电极. 要得到高电平状态需要上拉电阻才行。
$软件上就是通过设置IO口的模式,然后控制IO的上拉下拉,写入对应寄存器,通过寄存器控制电路:
上拉寄存器是控制对应端口上拉使能(DE)的。当对应位为0时,设置对应引脚上拉使能,为1时,禁止对应引脚上拉使能。如果上拉寄存器使能,无论引脚功能寄存器如何设置(输入,输出,数据,中断等),对应引脚输出高电平。

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

Kernel 2.6.32版本以上提供了gpio口管理的库文件/kernel/drivers/gpio/gpiolib.c

一.相关的接口:

1.int gpio_request(unsigned gpio, const char *label)

申请一个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)
...
}

2.void gpio_free(unsigned gpio)

释放这个gpio口

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

3.int gpio_direction_input(unsigned gpio)

    设置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)为实现!

4.int gpio_direction_output(unsigned gpio, int value)

设置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)为实现!

5.void __gpio_set_value(unsigned gpio, int 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);
}

6.int __gpio_get_value(unsigned gpio)

 获取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

此外:
static inline int of_get_gpio_flags(struct device_node *np, int index,
enum of_gpio_flags *flags)

static inline int of_get_gpio_flags(struct device_node *np, int index,
              enum of_gpio_flags *flags)
{
    return of_get_named_gpio_flags(np, "gpios", index, flags);
}

而of_get_named_gpio_flags()函数定义如下:

static inline int of_get_named_gpio_flags(struct device_node *np,
        const char *list_name, int index, enum of_gpio_flags *flags)
{
    return -ENOSYS;
}
enum of_gpio_flags {
    OF_GPIO_ACTIVE_LOW = 0x1,
};

gpio_set_value(port_num,0/1)

这个函数一般只是在这个GPIO口的寄存器上写上某个值,至于这个端口是否设置为输出,它就管不了!

gpio_direction_output (port_num,0/1)
“`
在某个GPIO口写上某个值之后,还会把这个端口设置为输出模式。

### 回答1: CentOS 7上启动httpd服务失败可能有多种原因,以下是一些常见的解决方法: 1. 检查httpd配置文件是否正确:可以使用命令`httpd -t`检查httpd配置文件是否正确,如果有错误,需要修改配置文件。 2. 检查端口是否被占用:可以使用命令`netstat -tlnp`查看端口是否被占用,如果被占用需要释放端口或修改httpd配置文件中的端口号。 3. 检查httpd服务是否安装:可以使用命令`rpm -qa | grep httpd`查看httpd服务是否安装,如果没有安装需要先安装httpd服务。 4. 检查httpd服务是否启动:可以使用命令`systemctl status httpd`查看httpd服务是否启动,如果没有启动需要使用命令`systemctl start httpd`启动httpd服务。 5. 检查SELinux是否开启:如果SELinux开启,可能会导致httpd服务启动失败,需要使用命令`setenforce 0`关闭SELinux,或者修改SELinux策略。 以上是一些常见的解决方法,如果以上方法都无法解决问题,可以查看httpd服务日志文件,找到具体的错误信息,然后根据错误信息进行解决。 ### 回答2: CentOS 7上的httpd服务启动失败可能有多种原因。以下列出了一些常见问题解决方法: 1. 端口被占用 当httpd试图占用已被其他程序占用的端口时会启动失败。此时可以通过使用`netstat -tunlp`命令检查端口占用情况,然后杀死占用该端口的进程及时释放端口。或者修改httpd的配置文件,将端口修改为未被占用的端口。 2. 配置文件错误 有时httpd服务的配置文件中可能出现错误,例如语法错误或路径错误等等。在启动httpd服务之前,可以使用`apachectl configtest`命令进行检查,如果输出“Syntax OK”,则表示配置文件没有错误。如果出现错误,则需要根据错误提示进行相应修改。 3. 依赖关系问题 如果httpd依赖的其他程序或库缺失,也会导致启动失败。可以通过使用`systemctl status httpd.service`命令来查看httpd服务状,如果输出“Failed to start”或“Loaded: failed”,则需要检查依赖关系是否完整。 4. SELinux问题 当SELinux启用时,有时会导致httpd服务启动失败。在这种情况下,可以在SELinux上禁用httpd服务,或者修改httpd配置文件解决SELinux相关的问题。 5. 用户权限问题 httpd服务的启动可能需要特定的用户权限。如果使用的用户权限不够,则无法启动。可以尝试使用root用户启动httpd服务,或者根据需要修改相应的用户权限。 ### 回答3: CentOS 7中的Apache HTTP服务器(httpd)是一个常见的Web服务器,如果遇到httpd服务启动失败的情况,可能会影响服务器正常的工作对外服务的稳定性。本文将提供一些可能会导致httpd服务启动失败的原因,并给出相应的解决方法。 1. 端口被占用 如果端口被其他进程占用,httpd服务就无法启动。可以通过 netstat -tulpn 命令查看端口占用情况,并杀死占用该端口的进程。如果端口被 httpd 服务自身占用,可以通过 systemctl restart httpd 命令重启 httpd 服务;如果是其他进程占用了端口,可以通过 kill 命令杀死该进程或更改 httpd.conf 文件配置,将 httpd 服务的端口改为其他空闲端口,重新启动。 2. 配置文件错误 httpd 服务的配置文件通常是 /etc/httpd/conf/httpd.conf,如果其中存在语法错误、权限问题或者其它配置错误,可能会导致 httpd 服务启动出错。可以通过将 httpd.conf 文件备份后删掉,重新执行 yum install httpd 命令安装 httpd 服务,然后手动修改 httpd.conf 文件,逐个检查每个配置项是否正确,确认无误后重启 httpd 服务。 3. SELinux 问题 SELinux 是 CentOS 7中提供的一种安全模块,它可以对系统文件应用程序进行安全管控。如果 SELinux 配置不正确,可能会止 httpd 服务正常启动。可以通过修改 /etc/selinux/config 文件中 SELINUX=disabled 来暂时关闭 SELinux,然后重新启动 httpd 服务;或者一个更优的方式是,根据日志确定问题原因,使用命令 semanage 或者 setsebool 等工具将相关目录或者配置加入到 SELinux 许可列表中,重新启动 httpd 服务,以恢复服务正常工作。 4. 防火墙问题 如果你的 CentOs 7 服务器启用了防火墙,有可能会导致 httpd 服务启动失败。可以通过检查防火墙相关配置来确定问题原因,解决方案是修改防火墙规则,将端口 80 或者 443 等 httpd 服务需要的端口放行,重新启动 httpd 服务。 总之,当遇到 httpd 服务启动失败时,不要慌张,可以先通过日志或者执行命令查看错误信息,找到错误原因,然后根据错误原因一步一步解决问题。在解决问题过程中注意备份原始配置文件,以免造成不必要的损失。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值