内核初始化与gpio子系统

本文深入解析Linux内核中GPIO子系统的初始化过程,重点介绍了`s3c_gpio_cfgpin`函数及其相关结构体,如`s3c_gpio_chip`和`s3c_gpio_cfg`。内容涵盖GPIO结构体指针获取、GPIO配置、寄存器操作以及中断处理。通过代码分析,展示了GPIO的请求、方向设置、值的读写等操作,并探讨了gpiolib框架在系统中的作用和注册流程。

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

定义在linux/arch/arm/plat-s3c/gpio-config.c

int s3c_gpio_cfgpin(unsigned int pin, unsigned int config)

{

struct s3c_gpio_chip *chip = s3c_gpiolib_getchip(pin);  //得到对应GPIO结构体首指针,里面包含了该GPIO的各种参数

unsigned long flags;

int offset;

int ret;

 offset = pin - chip->chip.base;    // 否则offset等于该GPIO引脚相对于GPX0)的偏移量,每个偏移1

ret = s3c_gpio_do_setcfg(chip, offset, config);   //设置该GPIO状态寄存器的数值为config

//s3c_gpio_do_setcfg操作

static inline int s3c_gpio_do_setcfg(struct s3c_gpio_chip *chip,unsigned int off, unsigned int config)

{

     return (chip->config->set_config)(chip, off, config);

}

//这里的set_config是一个函数指针,由后面的分析知道,如果针对GPA,该函数指针指向s3c_gpio_setcfg_s3c24xx_a ,如果针对GPX应该是指向s3c_gpio_setcfg_s3c24xx——但发现,如果是其他GPX,根本没有定义set_config!!!(这个问题已经解决,见后文s3c24xx_gpiolib_init函数,事实上,其余的config的确指向s3c_gpio_do_setcfg函数)

struct s3c_gpio_cfg s3c24xx_gpiocfg_default = {

           .set_config = s3c_gpio_setcfg_s3c24xx,

           .get_config = s3c_gpio_getcfg_s3c24xx,

};

 

int s3c_gpio_setcfg_s3c24xx_a(struct s3c_gpio_chip *chip, unsigned int off, unsigned int cfg)

{

void __iomem *reg = chip->base;   // GPXCON的物理基地址

unsigned int shift = off;    // 每个GPA对应一位

u32 con;

 if (s3c_gpio_is_cfg_special(cfg)) {     //OUTPUT状态是否为(0xfffffffX),是,返回1

         cfg &= 0xf;  // cfg = 0xX

  /* Map output to 0, and SFN2 to 1 */ 本实验不会运行到这

cfg -= 1;

if (cfg > 1)

        return -EINVAL;

cfg <<= shift;

}

con = __raw_readl(reg);     // 先读出该GPXCON的值,32

con &= ~(0x1 << shift);     // 

con |= cfg;                         //

 __raw_writel(con, reg);    // 将新值写入GPXCON

 

PS:   

#define __raw_writeb(v,a) (__chk_io_ptr(a), *(volatile unsigned char __force *)(a) = (v))

#define __raw_writew(v,a) (__chk_io_ptr(a), *(volatile unsigned short __force *)(a) = (v))

#define __raw_writel(v,a) (__chk_io_ptr(a), *(volatile unsigned int __force *)(a) = (v))

#define __raw_readb(a) (__chk_io_ptr(a), *(volatile unsigned char __force *)(a))

#define __raw_readw(a) (__chk_io_ptr(a), *(volatile unsigned short __force *)(a))

#define __raw_readl(a) (__chk_io_ptr(a), *(volatile unsigned int __force *)(a))

return 0;

}

如果针对GPX情况

int s3c_gpio_setcfg_s3c24xx(struct s3c_gpio_chip *chip,

unsigned int off, unsigned int cfg)

{

void __iomem *reg = chip->base;

 unsigned int shift = off * 2;    // 每个GPX对应2

u32 con;

if (s3c_gpio_is_cfg_special(cfg)) {

          cfg &= 0xf;

if (cfg > 3)

          return -EINVAL;

  cfg <<= shift;        // cfg0,1两位左移offset

}

 con = __raw_readl(reg);         // 读对应的GPXCON

 con &= ~(0x3 << shift);          // GPXCONpin)的两bits0

 con |= cfg;                               // 设置config

 __raw_writel(con, reg);           // 写入新的GPXCON

return 0;

}

 

return ret;

我们先来看s3c_gpiolib_getchip,它实现了返回对应pin值的GPIO结构体首指针的功能

#include<mach/gpio-core.h> 

 static inline struct s3c_gpio_chip *s3c_gpiolib_getchip(unsigned int pin)

{

struct s3c_gpio_chip *chip;

 if (pin > S3C_GPIO_END)    //如果超过GPJ(32)return NULL

     return NULL;

chip = &s3c24xx_gpios[pin/32];     //根据偏移,计算出对应pinGPIO结构体指针

     return ((pin - chip->chip.base) < chip->chip.ngpio) ? chip : NULL;

     //  这里验证,如果pin偏移超过了GPIO的个数,说明出错了,否则就返回该GPIO的结构体指针

}

回想以下之前s3c2410_gpio_cfgpin中,我们传入的参数是led_table[i]和 led_cfg_table[i]

struct s3c_gpio_chip s3c24xx_gpios[] = {

[0] = {

.base = S3C2410_GPACON,  // datasheet上地址为0x56000000

#define S3C2410_GPIOREG(x) ((x) + S3C24XX_VA_GPIO) 

#define S3C24XX_VA_GPIO     ((S3C24XX_PA_GPIO - S3C24XX_PA_UART) + S3C24XX_VA_UART)

S3C24XX_PA_GPIO相当于(0x15600000) 

S3C24XX_PA_UART相当于(0x15000000) 

#define S3C_VA_UART    S3C_ADDR(0x01000000)    /* UART */ 

#define S3C_ADDR_BASE 0xF6000000

#ifndef __ASSEMBLY__

#define S3C_ADDR(x) ((void __iomem __force *)S3C_ADDR_BASE + (x))

#else

#define S3C_ADDR(x) (S3C_ADDR_BASE + (x))

#endif

0x15600000-15000000+F7000000这里的S3C2410_GPACON应该怎么算?

 

.pm = __gpio_pm(&s3c_gpio_pm_1bit),

.config = &s3c24xx_gpiocfg_banka,   // 设置GPIO的函数指针

                     static struct s3c_gpio_cfg s3c24xx_gpiocfg_banka = {

                         .set_config = s3c_gpio_setcfg_s3c24xx_a,

                         .get_config = s3c_gpio_getcfg_s3c24xx_a,

                      };

.chip = {

.base = S3C2410_GPA(0),   //基地址,也是偏移量

.owner = THIS_MODULE,

.label = "GPIOA",

.ngpio = 24,

.direction_input = s3c24xx_gpiolib_banka_input,

.direction_output = s3c24xx_gpiolib_banka_output,

},

},

[1] = {

.base = S3C2410_GPBCON,

.pm = __gpio_pm(&s3c_gpio_pm_2bit),

.chip = {

.base = S3C2410_GPB(0),

.owner = THIS_MODULE,

.label = "GPIOB",

.ngpio = 16,

},

},

[2] = {

.base = S3C2410_GPCCON,

.pm = __gpio_pm(&s3c_gpio_pm_2bit),

.chip = {

.base = S3C2410_GPC(0),

.owner = THIS_MODULE,

.label = "GPIOC",

.ngpio = 16,

},

},

[3] = {

.base = S3C2410_GPDCON,

.pm = __gpio_pm(&s3c_gpio_pm_2bit),

.chip = {

.base = S3C2410_GPD(0),

.owner = THIS_MODULE,

.label = "GPIOD",

.ngpio = 16,

},

},

[4] = {

.base = S3C2410_GPECON,

.pm = __gpio_pm(&s3c_gpio_pm_2bit),

.chip = {

.base = S3C2410_GPE(0),

.label = "GPIOE",

.owner = THIS_MODULE,

.ngpio = 16,

},

},

[5] = {

.base = S3C2410_GPFCON,

.pm = __gpio_pm(&s3c_gpio_pm_2bit),

.chip = {

.base = S3C2410_GPF(0),

.owner = THIS_MODULE,

.label = "GPIOF",

.ngpio = 8,

.to_irq = s3c24xx_gpiolib_bankf_toirq,

},

},

[6] = {

.base = S3C2410_GPGCON,

.pm = __gpio_pm(&s3c_gpio_pm_2bit),

.irq_base = IRQ_EINT8,

.chip = {

.base = S3C2410_GPG(0),

.owner = THIS_MODULE,

.label = "GPIOG",

.ngpio = 16,

.to_irq = samsung_gpiolib_to_irq,

},

}, {

.base = S3C2410_GPHCON,

.pm = __gpio_pm(&s3c_gpio_pm_2bit),

.chip = {

.base = S3C2410_GPH(0),

.owner = THIS_MODULE,

.label = "GPIOH",

.ngpio = 11,

},

},

/* GPIOS for the S3C2443 and later devices. */2440用不到

{

.base = S3C2440_GPJCON,

.pm = __gpio_pm(&s3c_gpio_pm_2bit),

.chip = {

.base = S3C2410_GPJ(0),

.owner = THIS_MODULE,

.label = "GPIOJ",

.ngpio = 16,

},

}, {

.base = S3C2443_GPKCON,

.pm = __gpio_pm(&s3c_gpio_pm_2bit),

.chip = {

.base = S3C2410_GPK(0),

.owner = THIS_MODULE,

.label = "GPIOK",

.ngpio = 16,

},

}, {

.base = S3C2443_GPLCON,

.pm = __gpio_pm(&s3c_gpio_pm_2bit),

.chip = {

.base = S3C2410_GPL(0),

.owner = THIS_MODULE,

.label = "GPIOL",

.ngpio = 15,

},

}, {

.base = S3C2443_GPMCON,

.pm = __gpio_pm(&s3c_gpio_pm_2bit),

.chip = {

.base = S3C2410_GPM(0),

.owner = THIS_MODULE,

.label = "GPIOM",

.ngpio = 2,

},

},

};

*************************************************************************** 

下面分析第二个函数,先看一下相关结构体

void s3c2410_gpio_setpin(unsigned int pin, unsigned int to)

{

/* do this via gpiolib until all users removed */

      gpio_request(pin, "temporary");

      gpio_set_value(pin, to);

      gpio_free(pin);

}

又出现了三个函数,我们一一说明:

1169/* These "optional" allocation calls help prevent drivers from stomping

1170 * on each other, and help provide better diagnostics in debugfs.

1171 * They're called even less than the "set direction" calls.

1172 */

PS:static struct gpio_desc gpio_desc[ARCH_NR_GPIOS];

其中ARCH_NR_GPIOSarch/arm/mach-s3c2410/include/mach/gpio.h中定义

#define ARCH_NR_GPIOS (32 * 9 + CONFIG_S3C24XX_GPIO_EXTRA)

因此,每个引脚都分配了一个gpio_desc数据结构

1173int gpio_request(unsigned gpio, const char *label)        // 这个函数还不是很明白

1174{

1175 struct gpio_desc *desc;

1176 struct gpio_chip *chip;

1177 int status = -EINVAL;

1178 unsigned long flags;

1179

1180 spin_lock_irqsave(&gpio_lock, flags);    // gpio_lock是自旋锁,上锁,保存FLAGflags变量中

1181

1182 if (!gpio_is_valid(gpio))     // 不符合要求,跳转到done

1183     goto done;

1184 desc = &gpio_desc[gpio];    // desc = &gpio_desc[pin]

1185 chip = desc->chip;

1186 if (chip == NULL)              // gpio_desc.chip指向NULL,跳转到done

1187     goto done;

1188

1189 if (!try_module_get(chip->owner))   // 该函数用于增加模块使用计数;若返回为0,表示调用失败,希望使用的模块没有被加载或正在被卸载中

1190     goto done;

1191

1192 /* NOTE: gpio_request() can be called in early boot,

1193 * before IRQs are enabled, for non-sleeping (SOC) GPIOs.

1194 */

1195

1196 if (test_and_set_bit(FLAG_REQUESTED, &desc->flags) == 0) {    // 原子操作,将flags的第FLAG_REQUESTED位置1,并返回其原值

1197     desc_set_label(desc, label ? : "?");    // 如果原来的值是0, 执行desc_set_label, desc->chip.label赋值,如果label有定义,直接用定义,比如上面的“temporary”,否则用“?”

static inline void desc_set_label(struct gpio_desc *d, const char *label)

{

#ifdef CONFIG_DEBUG_FS

 d->label = label;               // 为什么不是d->chip.label = label; ?

#endif

}

1198 status = 0;

1199 } else {                      // 如果flags的第FLAG_REQUESTED位原来的值是1

1200 status = -EBUSY;

1201 module_put(chip->owner);    // 该函数用于减少模块使用计数

1202     goto done;

1203 }

1204

1205 if (chip->request) {    // chip->requestlinux初始化时是没有指向的,可以见后面s3c_gpiolib_add

1206 /* chip->request may sleep */

1207                    spin_unlock_irqrestore(&gpio_lock, flags);            // 如果chip->request不为0, 解锁,因为后面调用的chip->request有可能睡眠

1208                    status = chip->request(chip, gpio - chip->base);

1209                    spin_lock_irqsave(&gpio_lock, flags);                    // 执行完后,继续上锁

1210

1211                    if (status < 0) {        // status返回负数,说明出错

1212                 &nbs

### GPIO 子系统的工作原理 GPIO(General Purpose Input/Output)子系统是一种通用的硬件接口机制,用于控制微处理器中的引脚状态。它允许开发者通过软件编程的方式配置和操作这些引脚的状态。根据提供的引用内容[^1],GPIO 子系统依赖于 pinctrl 子系统来完成某些功能性切换。 具体来说,`pin controller` 负责管理引脚的功能性切换,比如将引脚分配给 I2C、UART 或 SPI 等外设功能。而 `GPIO Controller` 则专注于提供基本的操作能力,例如设置引脚为输入或输出模式、读取或写入引脚值等简单功能。 当需要使用某个引脚控制外部设备(如 LED),通常会经历以下几个过程: - **使能时钟**:激活相关模块所需的电源时钟资源。 - **配置引脚功能**:利用 pinctrl 子系统将引脚复用为 GPIO 功能。 - **配置电气属性**:调整驱动强度、上拉/下拉电阻等参数。 - **初始化 GPIO 方向**:调用 `gpio_direction_output()` 或 `gpio_direction_input()` 设置引脚为输入或输出模式。 - **控制电平**:通过 `gpio_set_value()` 函数改变引脚的高低电平状态[^2]。 ### GPIO 子系统的实现细节 在 Linux 内核中,GPIO 子系统的核心结构围绕着 `struct gpio_chip` 展开。这个数据结构定义了一个 GPIO 控制器的行为模型,并提供了多个回调函数指针以便实现特定平台的需求。以下是几个重要的成员及其作用: #### 1. `request()` 此函数负责申请指定编号的 GPIO 引脚供应用程序使用。如果成功,则返回零;否则返回错误码表示失败原因。需要注意的是,在实际应用过程中可能还需要额外处理一些特殊情况,因为并非所有平台上都完全支持这一标准接口[^3]。 #### 2. `direction_output() / direction_input()` 这两个方法分别用来设定目标 GPIO 的工作方向——即作为输出端还是输入端。它们内部可能会进一步调用底层硬件寄存器访问逻辑或者借助 pin control framework 完成最终转换。 ```c void rockchip_gpio_direction_output(struct gpio_chip *gc, unsigned int offset, int value) { rockchip_gpio_set(gc, offset, value); pinctrl_gpio_direction_output(gc->base + offset); } ``` 上述代码片段展示了如何在一个典型的 Rockchip 平台上执行 output 模式的切换动作。这里不仅修改了具体的 bit 值还通知了 pinctrl subsystem 关于此变化的信息。 ### 使用方法总结 对于开发人员而言,最常用的 API 包括但不限于以下几种形式: - 请求并释放单条线路:`int gpio_request(unsigned gpio, const char *label)` 和 void gpio_free(unsigned gpio). - 配置出入类型:int gpio_direction_[output|input](unsigned gpio,int val). - 获取当前数值:int gpio_get_value(unsigned gpio),以及相应地更新新状态:void gpio_set_value(unsigned gpio,int value). 以上便是关于Linux环境下典型嵌入式架构下的GPIO子系统介绍.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值