程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

linux驱动移植-GPIO控制器驱动

----------------------------------------------------------------------------------------------------------------------------
内核版本:linux 5.2.8
根文件系统:busybox 1.25.0
u-boot:2016.05
----------------------------------------------------------------------------------------------------------------------------

这一节我们来分析Mini2440 GPIO控制器驱动的源码,由于GPIO控制器驱动比较简单,所以这一节内容相对来说也是比较少的。

GPIO控制器驱动编写主要包含两个步骤:

  • 为SoC的每个GPIO控制器分配一个gpio_chip,并进行初始化;
  • 调用gpiochip_add注册每一个gpio_chip;

一、S3C2440 GPIO

根据平台的不同。GPIO控制器的数量也不同,GPIO控制器的寄存器端口映射方式和映射地址都不同,这些都是与平台先关的,需要平台去适配。

当然,不同的GPIO控制器的寄存器数量和功能也不尽相同,一般情况下,GPIO控制器都提供两个寄存器:一个是配置(控制)寄存器,另一个是数据寄存器。

S3C2440提供了9个GPIO控制器,分别为:

  • 端口A(GPA):25位输出端口 ;
  • 端口B(GPB):11 位输入/输出端口 ;
  • 端口C(GPC):16 位输入/输出端口;
  • 端口D(GPD):16 位输入/输出端口;
  • 端口E(GPE):16 位输入/输出端口 ;
  • 端口F(GPF):8 位输入/输出端口;
  • 端口G(GPG):16 位输入/输出端口;
  • 端口H(GPH):9 位输入/输出端口;
  • 端口J(GPJ):13 位输入/输出端口;

其中每个GPIO控制器至少提供了2个32位寄存器:

  • 端口配置寄存器:GPACON~GPJCON,配置寄存器的作用是,用来配置该芯片的每根引脚的输入输出状态或者特殊功能等;
  • 端口数据寄存器:GPADAT~GPJDAT,如果端口配置为输出端口,可以写入数据到 PnDAT 的相应位。如果端口配置为输入端口,可以从 PnDAT 的相应位读取数据;
  • 端口上拉寄存器:GPBUP~GPJUP,端口上拉寄存器控制每个端口组的使能/禁止上拉电阻。当相应位为0时使能引脚的上拉电阻,当为 1 时禁止上拉电阻。如果使能了上拉电阻,那么上拉电阻与引脚的功能设置无关(输入、输出、DATAn、EINTn 等等);

GPIOA的配置寄存器GPACON不同于其他GPIO的配置寄存器:

  • 使用1bit控制GPIO的相应引脚状态,32bit寄存器使用低23bit控制23个引脚;
  • 当控制位值为0的时候,表示该引脚是通用输出状态;
  • 控制位为1的时候,引脚是特殊功能状态,用于控制其他外接器件(此时,数据寄存器GPADAT无效)。

因而,可以看出,GPIOA芯片不能实现通用输入功能。一般来说,GPIOA控制寄存器配置成1,用于控制其他外接器件。

GPIOB~GPIOJ的配置寄存器GPBCON~GPJCON,使用2bit控制的相应引脚状态(bit0,1控制引脚0,bit2,3控制引脚1,以此类推),每个GPIO控制器提供的引脚个数不尽相同,但最多只能提供32/2=16个引脚;

  • 控制位为00时,表示相应引脚处于通用输入状态;
  • 控制位为01时,表示相应引脚为通用输出状态;
  • 控制位为10时,表示相应引脚为特殊功能状态;
  • 控制位为11时,表示相应引脚为特殊功能状态,或者是预留;

在介绍之前,我们先了解一下三星为了GPIO定义的一些数据结构、

1.1 samsung_gpio_cfg

samsung_gpio_cfg这个结构体用来描述三星芯片GPIO的配置,它描述了 GPIO 端口在系统中的不同用途,并提供了一些控制函数来设置和读取GPIO端口的状态和配置信息。

类型定义在arch/arm/plat-samsung/include/plat/gpio-cfg.h:

/**
 * struct samsung_gpio_cfg GPIO configuration
 * @cfg_eint: Configuration setting when used for external interrupt source
 * @get_pull: Read the current pull configuration for the GPIO
 * @set_pull: Set the current pull configuration for the GPIO
 * @set_config: Set the current configuration for the GPIO
 * @get_config: Read the current configuration for the GPIO
 *
 * Each chip can have more than one type of GPIO bank available and some
 * have different capabilites even when they have the same control register
 * layouts. Provide an point to vector control routine and provide any
 * per-bank configuration information that other systems such as the
 * external interrupt code will need.
 *
 * @sa samsung_gpio_cfgpin
 * @sa s3c_gpio_getcfg
 * @sa s3c_gpio_setpull
 * @sa s3c_gpio_getpull
 */
struct samsung_gpio_cfg {
        unsigned int    cfg_eint;

        samsung_gpio_pull_t     (*get_pull)(struct samsung_gpio_chip *chip, unsigned offs);
        int             (*set_pull)(struct samsung_gpio_chip *chip, unsigned offs,
                                    samsung_gpio_pull_t pull);

        unsigned (*get_config)(struct samsung_gpio_chip *chip, unsigned offs);
        int      (*set_config)(struct samsung_gpio_chip *chip, unsigned offs,
                               unsigned config);
};

其成员如下:

  • cfg_eint:用于外部中断源时的配置;
  • get_pull:读取当前GPIO的上拉配置;
  • set_pull:设置当前GPIO上拉配置;
  • set_config:设置GPIO配置;
  • get_config:获取GPIO配置;

该结构体的主要作用是提供每个GPIO bank的配置和控制函数的定义,为操作系统提供接口以便可以对GPIO进行读写操作。

需要注意的是,struct samsung_gpio_cfg 是在平台特定的gpio芯片驱动程序中使用的结构体,它们利用这些函数,通过配置寄存器来控制硬件设备的行为,读取设备状态,对外提供设备的访问接口。

1.2 samsung_gpio_chip

struct samsung_gpio_chip 是一个结构体,用于实现对特定 Samsung 平台上的 GPIO 控制器的封装。它包含了对 gpio_chip 的扩展信息,并提供其他必要的信息,以便让系统其他部分可以使用和识别这些信息。

定义在arch/arm/plat-samsung/include/plat/gpio-core.h:

/**
 * struct samsung_gpio_chip - wrapper for specific implementation of gpio
 * @chip: The chip structure to be exported via gpiolib.
 * @base: The base pointer to the gpio configuration registers.
 * @group: The group register number for gpio interrupt support.
 * @irq_base: The base irq number.
 * @config: special function and pull-resistor control information.
 * @lock: Lock for exclusive access to this gpio bank.
 * @pm_save: Save information for suspend/resume support.
 * @bitmap_gpio_int: Bitmap for representing GPIO interrupt or not.
 *
 * This wrapper provides the necessary information for the Samsung
 * specific gpios being registered with gpiolib.
 *
 * The lock protects each gpio bank from multiple access of the shared
 * configuration registers, or from reading of data whilst another thread
 * is writing to the register set.
 *
 * Each chip has its own lock to avoid any  contention between different
 * CPU cores trying to get one lock for different GPIO banks, where each
 * bank of GPIO has its own register space and configuration registers.
 */
struct samsung_gpio_chip {
        struct gpio_chip        chip;
        struct samsung_gpio_cfg *config;
        struct samsung_gpio_pm  *pm;
        void __iomem            *base;
        int                     irq_base;
        int                     group;
        spinlock_t               lock;
#ifdef CONFIG_PM
        u32                     pm_save[4];
#endif
        u32                     bitmap_gpio_int;
};

其成员如下:

  • chip:gpiolib核心数据结构gpio_chip;
  • base:指向GPIO控制器寄存器基地址;
  • group:GPIO中断支持的组寄存器编号;
  • irq_base:中断base编号;
  • config:用于对 GPIO 进行配置的 samsung_gpio_cfg 结构体指针;
  • lock:用于对该 GPIO控制器进行锁定,保证在多线程环境下的安全性和共享性;
  • bitmap_gpio_int:位映射,用于表示GPIO是否启用中断的位图;

二、分配gpio_chip

2.1 s3c24xx_gpios

我们定位到arch/arm/plat-samsung/gpio-samsung.c文件:

struct samsung_gpio_chip s3c24xx_gpios[] = {
#ifdef CONFIG_PLAT_S3C24XX
        {
                .config = &s3c24xx_gpiocfg_banka,
                .chip   = {
                        .base                   = S3C2410_GPA(0),
                        .owner                  = THIS_MODULE,
                        .label                  = "GPIOA",
                        .ngpio                  = 27,
                        .direction_input        = s3c24xx_gpiolib_banka_input,
                        .direction_output       = s3c24xx_gpiolib_banka_output,
                },
        }, {
                .chip   = {
                        .base   = S3C2410_GPB(0),
                        .owner  = THIS_MODULE,
                        .label  = "GPIOB",
                        .ngpio  = 11,
                },
        }, {
                .chip   = {
                        .base   = S3C2410_GPC(0),
                        .owner  = THIS_MODULE,
                        .label  = "GPIOC",
                        .ngpio  = 16,
                },
        }, {
                .chip   = {
                        .base   = S3C2410_GPD(0),
                        .owner  = THIS_MODULE,
                        .label  = "GPIOD",
                        .ngpio  = 16,
                },
        }, {
                .chip   = {
                        .base   = S3C2410_GPE(0),
                        .label  = "GPIOE",
                        .owner  = THIS_MODULE,
                        .ngpio  = 16,
                },
        }, {
                .chip   = {
                        .base   = S3C2410_GPF(0),
                        .owner  = THIS_MODULE,
                        .label  = "GPIOF",
                        .ngpio  = 8,
                        .to_irq = s3c24xx_gpiolib_fbank_to_irq,
                },
        }, {
                .irq_base = IRQ_EINT8,
                .chip   = {
                        .base   = S3C2410_GPG(0),
                        .owner  = THIS_MODULE,
                        .label  = "GPIOG",
                        .ngpio  = 16,
                        .to_irq = samsung_gpiolib_to_irq,
                },
       }, {
                .chip   = {
                        .base   = S3C2410_GPH(0),
                        .owner  = THIS_MODULE,
                        .label  = "GPIOH",
                        .ngpio  = 15,
                },
        },
                /* GPIOS for the S3C2443 and later devices. */
        {
                .base   = S3C2440_GPJCON,
                .chip   = {
                        .base   = S3C2410_GPJ(0),
                        .owner  = THIS_MODULE,
                        .label  = "GPIOJ",
                        .ngpio  = 16,
                },
        }, {
                .base   = S3C2443_GPKCON,
                .chip   = {
                        .base   = S3C2410_GPK(0),
                        .owner  = THIS_MODULE,
                        .label  = "GPIOK",
                        .ngpio  = 16,
                },
        }, {
                .base   = S3C2443_GPLCON,
                .chip   = {
                        .base   = S3C2410_GPL(0),
                        .owner  = THIS_MODULE,
                        .label  = "GPIOL",
                        .ngpio  = 15,
                },
        }, {
                .base   = S3C2443_GPMCON,
                .chip   = {
                        .base   = S3C2410_GPM(0),
                        .owner  = THIS_MODULE,
                        .label  = "GPIOM",
                        .ngpio  = 2,
                },
        },
#endif
};

根据SoC datasheet定义好每一个GPIO控制器寄存器的基地址和 GPIO的个数。以及支持中断的情况。

关于这个GPIO编号我们上一节已经介绍过了,我们为GPIO控制器下的每个GPIO分配一个唯一的编号,比如GPIOA端口编号范围为0~31。

2.2 s3c24xx_gpiocfg_banka

由于GPIOA比较特殊,所以s3c24xx_gpios对于GPIOA对config单独配置,s3c24xx_gpiocfg_banka:

static struct samsung_gpio_cfg s3c24xx_gpiocfg_banka = {
        .set_config     = s3c24xx_gpio_setcfg_abank,
        .get_config     = s3c24xx_gpio_getcfg_abank,
};

这里我们大概看一下这两个函数s3c24xx_gpio_setcfg_abank、s3c24xx_gpio_getcfg_abank。这俩函数都定义在arch/arm/plat-samsung/gpio-samsung.c文件中。

2.2.1 s3c24xx_gpio_setcfg_abank

s3c24xx_gpio_setcfg_abank用于配置 S3C24XX 平台的 GPIO 端口。它是专门针对 bank A的GPIO 端口的配置函数。该函数的参数包含了要进行配置的GPIO控制器、端口号和配置值。

在 S3C24XX 平台上,每个GPIOA端口的控制寄存器都只有一个位来控制其模式。当该位设置为 1 时,GPIO 处于特殊模式,反之则表示 GPIO 处于输出模式。

s3c24xx_gpio_setcfg_abank函数实际上就是配置GPIOA第off个引脚为输出/其他功能。

/*
 * s3c24xx_gpio_setcfg_abank - S3C24XX style GPIO configuration (Bank A)
 * @chip: The gpio chip that is being configured.
 * @off: The offset for the GPIO being configured.
 * @cfg: The configuration value to set.
 *
 * This helper deal with the GPIO cases where the control register
 * has one bit of configuration for the gpio, where setting the bit
 * means the pin is in special function mode and unset means output.
 */

static int s3c24xx_gpio_setcfg_abank(struct samsung_gpio_chip *chip,
                                     unsigned int off, unsigned int cfg)
{
        void __iomem *reg = chip->base;
        unsigned int shift = off;
        u32 con;

        if (samsung_gpio_is_cfg_special(cfg)) {
                cfg &= 0xf;

                /* Map output to 0, and SFN2 to 1 */
                cfg -= 1;
                if (cfg > 1)
                        return -EINVAL;

                cfg <<= shift;
        }

        con = __raw_readl(reg);   // 读取CPACON寄存器
        con &= ~(0x1 << shift);   // 第shift位清0 
        con |= cfg;               // 配置第shift位
        __raw_writel(con, reg);

        return 0;
}

s3c24xx_gpio_setcfg_abank函数实际上就是去读取GPACON寄存器的值,然后将第off位先清0,然后再去配置第off位的值,其值取自cfg第off位。

需要注意的是,该函数只适用于S3C24XX平台上bank A的GPIO端口的配置。如果需要对其他 GPIO 进行配置,则需要使用其他适配的函数。

2.2.2 s3c24xx_gpio_getcfg_abank

s3c24xx_gpio_getcfg_abank函数用于获取S3C24XX平台的GPIO端口的配置模式。它是专门针对 bank A 的 GPIO 端口的查询函数。该函数的参数包含了要查询的 GPIO 控制器和端口号。

在 S3C24XX 平台上,每个GPIOA端口的控制寄存器只有一个位来控制其模式。当该位设置为 1 时,GPIO 处于特殊模式,反之则表示 GPIO 处于输出模式。因此,通过该函数可以将 GPIO 的工作模式转换成可用的GPIO配置值,以方便对 GPIO 进行状态查询。

s3c24xx_gpio_getcfg_abank函数实际上就是去读取GPACON寄存器的值,然后通过位运算,获取第off位的值。

/*
 * s3c24xx_gpio_getcfg_abank - S3C24XX style GPIO configuration read (Bank A)
 * @chip: The gpio chip that is being configured.
 * @off: The offset for the GPIO being configured.
 *
 * The reverse of s3c24xx_gpio_setcfg_abank() turning an GPIO into a usable
 * GPIO configuration value.
 *
 * @sa samsung_gpio_getcfg_2bit
 * @sa samsung_gpio_getcfg_4bit
 */

static unsigned s3c24xx_gpio_getcfg_abank(struct samsung_gpio_chip *chip,
                                          unsigned int off)
{
        u32 con;

        con = __raw_readl(chip->base);  // 读取GPACON寄存器
        con >>= off;                    // 右移off  
        con &= 1;                       // &运算 
        con++;

        return S3C_GPIO_SFN(con);
}

需要注意的是,该函数只适用于 S3C24XX 平台上 bank A 的 GPIO 端口的状态查询。如果需要对其他 GPIO 进行状态查询,则需要使用其他适配的函数。同时,该函数是 s3c24xx_gpio_setcfg_abank 函数的反向函数,通过两个函数可以方便地操作和查询 GPIO 的状态。

2.3 to_irq

上一节我们介绍过gpio_chip成员将to_irq作用是偏移为offset的GPIO映射到IRQ并返回相关的中断编号;那么我们这里以GPIOF控制器为例,其to_irq被初始化为了s3c24xx_gpiolib_fbank_to_irq:

static int s3c24xx_gpiolib_fbank_to_irq(struct gpio_chip *chip, unsigned offset)
{
        if (offset < 4) {
                if (soc_is_s3c2412())
                        return IRQ_EINT0_2412 + offset;
                else
                        return IRQ_EINT0 + offset;   // 外部中断0 + 偏移
        }

        if (offset < 8)
                return IRQ_EINT4 + offset - 4;

        return -EINVAL;
}

我们去看GPFCON寄存器我们会发现当GPF0~GPF8对应引脚被配置为10时,其功能复用为外部中断。

在中断子系统中,每一个中断也是有一个IRQ编号,比如外部中断0~外部中断7等,定义在arch/arm/mach-s3c24xx/include/mach/irqs.h:

/* main cpu interrupts */
#define IRQ_EINT0      S3C2410_IRQ(0)       /* 16 */
#define IRQ_EINT1      S3C2410_IRQ(1)
#define IRQ_EINT2      S3C2410_IRQ(2)
#define IRQ_EINT3      S3C2410_IRQ(3)
#define IRQ_EINT4t7    S3C2410_IRQ(4)       /* 20 */

/* interrupts generated from the external interrupts sources */
#define IRQ_EINT4      S3C2410_IRQ(36)     /* 52 */
#define IRQ_EINT5      S3C2410_IRQ(37)
#define IRQ_EINT6      S3C2410_IRQ(38)
#define IRQ_EINT7      S3C2410_IRQ(39)

需要注意的是:

  • IRQ_EINT0~IRQ_EINT3、IRQ~EINT4t7为主中断源;
  • IRQ_EINT4~IRQ_EINT7为外部中断源,其对应的主中断源为IRQ~EINT4t7。

三、注册gpio_chip

在arch/arm/plat-samsung/gpio-samsung.c文件,定位到模块入口函数:

/* TODO: cleanup soc_is_* */
static __init int samsung_gpiolib_init(void)
{
        /*
         * Currently there are two drivers that can provide GPIO support for
         * Samsung SoCs. For device tree enabled platforms, the new
         * pinctrl-samsung driver is used, providing both GPIO and pin control
         * interfaces. For legacy (non-DT) platforms this driver is used.
         */
        if (of_have_populated_dt())
                return 0;

        if (soc_is_s3c24xx()) {    // 走这里
                samsung_gpiolib_set_cfg(samsung_gpio_cfgs,
                                ARRAY_SIZE(samsung_gpio_cfgs));
                s3c24xx_gpiolib_add_chips(s3c24xx_gpios,
                                ARRAY_SIZE(s3c24xx_gpios), S3C24XX_VA_GPIO);
        } else if (soc_is_s3c64xx()) {
                samsung_gpiolib_set_cfg(samsung_gpio_cfgs,
                                ARRAY_SIZE(samsung_gpio_cfgs));
                samsung_gpiolib_add_2bit_chips(s3c64xx_gpios_2bit,
                                ARRAY_SIZE(s3c64xx_gpios_2bit),
                                S3C64XX_VA_GPIO + 0xE0, 0x20);
                samsung_gpiolib_add_4bit_chips(s3c64xx_gpios_4bit,
                                ARRAY_SIZE(s3c64xx_gpios_4bit),
                                S3C64XX_VA_GPIO);
                samsung_gpiolib_add_4bit2_chips(s3c64xx_gpios_4bit2,
                                ARRAY_SIZE(s3c64xx_gpios_4bit2));
        }

        return 0;
}
core_initcall(samsung_gpiolib_init);

首先调用samsung_gpiolib_set_cfg初始化samsung_gpio_cfgs数组成员。

然后调用了 samsung_gpiolib_set_cfg的函数,就将gpio_chip结构体相关的成员进行了赋值,并最终调用到了gpiochip_add_data函数,将其注册到了内核的gpiolib 子系统。

3.1 samsung_gpiolib_set_cfg

samsung_gpiolib_set_cfg函数用来遍历samsung_gpio_chip数组,并依次配置:

  • set_config:默认配置为samsung_gpio_setcfg_4bit;
  • get_config:默认配置为samsung_gpio_getcfg_4bit;
  • set_pull:默认配置为samsung_gpio_setpull_updown;
  • get_pull:默认配置为samsung_gpio_getpull_updown;
static void __init samsung_gpiolib_set_cfg(struct samsung_gpio_cfg *chipcfg,
                                           int nr_chips)
{
        for (; nr_chips > 0; nr_chips--, chipcfg++) {
                if (!chipcfg->set_config)
                        chipcfg->set_config = samsung_gpio_setcfg_4bit;
                if (!chipcfg->get_config)
                        chipcfg->get_config = samsung_gpio_getcfg_4bit;
                if (!chipcfg->set_pull)
                        chipcfg->set_pull = samsung_gpio_setpull_updown;
                if (!chipcfg->get_pull)
                        chipcfg->get_pull = samsung_gpio_getpull_updown;
        }
}

samsung_gpio_cfgs数组定义如下,数组长度为8,依次对应S3C2440 GPA~GPH的配置。

static struct samsung_gpio_cfg samsung_gpio_cfgs[] = {
        [0] = {
                .cfg_eint       = 0x0,
        },
        [1] = {
                .cfg_eint       = 0x3,
        },
        [2] = {
                .cfg_eint       = 0x7,
        },
        [3] = {
                .cfg_eint       = 0xF,
        },
        [4] = {
                .cfg_eint       = 0x0,
                .set_config     = samsung_gpio_setcfg_2bit,
                .get_config     = samsung_gpio_getcfg_2bit,
        },
        [5] = {
                .cfg_eint       = 0x2,
                .set_config     = samsung_gpio_setcfg_2bit,
                .get_config     = samsung_gpio_getcfg_2bit,
        },
        [6] = {
                .cfg_eint       = 0x3,
                .set_config     = samsung_gpio_setcfg_2bit,
                .get_config     = samsung_gpio_getcfg_2bit,
        },
        [7] = {
                .set_config     = samsung_gpio_setcfg_2bit,
                .get_config     = samsung_gpio_getcfg_2bit,
        },
};

实际上关于samsung_gpio_cfgs数组的配置,我们在注册gpio_chip到gpiolib时并没有使用到,我们忽略就行。

3.2 s3c24xx_gpiolib_add_chips

samsung_gpiolib_set_cfg函数用来遍历samsung_gpio_chip数组,并依次初始化chip成员:config、base等,最后调用samsung_gpiolib_add注册chip;

static void __init s3c24xx_gpiolib_add_chips(struct samsung_gpio_chip *chip,
                                             int nr_chips, void __iomem *base)
{
        int i;
        struct gpio_chip *gc = &chip->chip;

        for (i = 0 ; i < nr_chips; i++, chip++) {
                /* skip banks not present on SoC */
                if (chip->chip.base >= S3C_GPIO_END)    // #define S3C_GPIO_END    (S3C2410_GPJ(0) + 32) 这里跳过了GPJ以及以及后面的接口
                        continue;

                if (!chip->config)
                        chip->config = &s3c24xx_gpiocfg_default;  // 如果没有设置config,默认是2bit配置操作。对GPIOA,已经显示配置成s3c24xx_gpiocfg_banka
                if (!chip->pm)
                        chip->pm = __gpio_pm(&samsung_gpio_pm_2bit);  // 
                if ((base != NULL) && (chip->base == NULL))
                        chip->base = base + ((i) * 0x10);  // GPIO控制器寄存器基地址 虚拟地址

                if (!gc->direction_input)
                        gc->direction_input = samsung_gpiolib_2bit_input;   // 配置为输入 平台相关配置函数,默认2bit配置操作
                if (!gc->direction_output)
                        gc->direction_output = samsung_gpiolib_2bit_output;   // 配置为输出 平台相关配置函数,默认2bit配置操作

                samsung_gpiolib_add(chip);  // 注册chip
        }
}

执行完上面代码,我们就可以知道:

  • GPIOA chip->config设置为了s3c24xx_gpiocfg_banka;
  • GPIOB~GPIOJ chip->config设置为了s3c24xx_gpiocfg_default;

GPIOA寄存器基地址设置为:

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

GPIOB~GPIO寄存器基地址依次增加0x10。

由于s3c24xx_gpiocfg_banka已经介绍过了,这里我们看一下s3c24xx_gpiocfg_default:

struct samsung_gpio_cfg s3c24xx_gpiocfg_default = {
        .set_config     = samsung_gpio_setcfg_2bit,
        .get_config     = samsung_gpio_getcfg_2bit,
};

这俩函数也都定义在arch/arm/plat-samsung/gpio-samsung.c文件中。

3.2.1 samsung_gpio_setcfg_2bit

 samsung_gpio_setcfg_2bit函数和s3c24xx_gpio_setcfg_abank类似,只是前者使用2位来控制引脚,而后者使用1位来控制引脚。

/*
 * samsung_gpio_setcfg_2bit - Samsung 2bit style GPIO configuration.
 * @chip: The gpio chip that is being configured.
 * @off: The offset for the GPIO being configured.
 * @cfg: The configuration value to set.
 *
 * This helper deal with the GPIO cases where the control register
 * has two bits of configuration per gpio, which have the following
 * functions:
 *      00 = input
 *      01 = output
 *      1x = special function
 */

static int samsung_gpio_setcfg_2bit(struct samsung_gpio_chip *chip,
                                    unsigned int off, unsigned int cfg)
{
        void __iomem *reg = chip->base;
        unsigned int shift = off * 2;
        u32 con;

        if (samsung_gpio_is_cfg_special(cfg)) {
                cfg &= 0xf;
                if (cfg > 3)
                        return -EINVAL;

                cfg <<= shift;
        }

        con = __raw_readl(reg);   // 读取GPBCON~GPJCON寄存器值
        con &= ~(0x3 << shift);   // 清除两位
        con |= cfg;               // 配置引脚
        __raw_writel(con, reg);

        return 0;
}
3.2.2 samsung_gpio_getcfg_2bit

 samsung_gpio_getcfg_2bit函数和s3c24xx_gpio_getcfg_abank类似,只是前者使用2位来控制引脚,而后者使用1位来控制引脚。

/*
 * samsung_gpio_getcfg_2bit - Samsung 2bit style GPIO configuration read.
 * @chip: The gpio chip that is being configured.
 * @off: The offset for the GPIO being configured.
 *
 * The reverse of samsung_gpio_setcfg_2bit(). Will return a value which
 * could be directly passed back to samsung_gpio_setcfg_2bit(), from the
 * S3C_GPIO_SPECIAL() macro.
 */

static unsigned int samsung_gpio_getcfg_2bit(struct samsung_gpio_chip *chip,
                                             unsigned int off)
{
        u32 con;

        con = __raw_readl(chip->base);    // 读取GPBCON~GPJCON寄存器值
        con >>= off * 2;                  // 右移
        con &= 3;                         // 读取两位值 

        /* this conversion works for IN and OUT as well as special mode */
        return S3C_GPIO_SPECIAL(con);
}

3.3 samsung_gpiolib_add

samsung_gpiolib_add函数用于注册Samsung 平台上的gpio_chip,它是一个对 gpiochip_add() 函数的封装,其中包含特定于 Samsung 平台的 gpio_chip 信息,并进行了必要的修改和配置,以便让系统其他部分可以使用和识别这些信息。

/*
 * samsung_gpiolib_add() - add the Samsung gpio_chip.
 * @chip: The chip to register
 *
 * This is a wrapper to gpiochip_add() that takes our specific gpio chip
 * information and makes the necessary alterations for the platform and
 * notes the information for use with the configuration systems and any
 * other parts of the system.
 */

static void __init samsung_gpiolib_add(struct samsung_gpio_chip *chip)
{
        struct gpio_chip *gc = &chip->chip;
        int ret;

        BUG_ON(!chip->base);   // 检查必须参数
        BUG_ON(!gc->label);
        BUG_ON(!gc->ngpio);

        spin_lock_init(&chip->lock);   // 初始化自旋锁

        if (!gc->direction_input)    
                gc->direction_input = samsung_gpiolib_2bit_input;   // 配置为输入
        if (!gc->direction_output)
                gc->direction_output = samsung_gpiolib_2bit_output;  // 配置为输出
        if (!gc->set)
                gc->set = samsung_gpiolib_set;    // 设置输出电平
        if (!gc->get)
                gc->get = samsung_gpiolib_get;    // 获取输出电平

#ifdef CONFIG_PM
        if (chip->pm != NULL) {
                if (!chip->pm->save || !chip->pm->resume)
                        pr_err("gpio: %s has missing PM functions\n",
                               gc->label);
        } else
                pr_err("gpio: %s has no PM function\n", gc->label);
#endif

        /* gpiochip_add() prints own failure message on error. */
        ret = gpiochip_add_data(gc, chip);   // 注册
        if (ret >= 0)
                s3c_gpiolib_track(chip);
}

gpiochip_add_data函数的第二个参数为驱动的私有数据,这里传入了samsung_gpio_chip。

这里我们重点看一下gpio_chip的以下几个成员:

  • direction_input:被配置成了samsung_gpiolib_2bit_input;除了GPIOA端口外(GPIOA端口direction_input被初始化为了s3c24xx_gpiolib_banka_input);
  • direction_output :被配置成了samsung_gpiolib_2bit_output;除了GPIOA端口外(GPIOA端口direction_output被初始化为了s3c24xx_gpiolib_banka_output);
  • set:被配置成了samsung_gpiolib_set;
  • get:被配置成了samsung_gpiolib_get;
3.3.1 samsung_gpiolib_2bit_input

针对GPIOB~GPIOH端口,samsung_gpiolib_2bit_input用于将偏移为offset的GPIO配置为输入;

/*
 * Default routines for controlling GPIO, based on the original S3C24XX
 * GPIO functions which deal with the case where each gpio bank of the
 * chip is as following:
 *
 * base + 0x00: Control register, 2 bits per gpio
 *              gpio n: 2 bits starting at (2*n)
 *              00 = input, 01 = output, others mean special-function
 * base + 0x04: Data register, 1 bit per gpio
 *              bit n: data bit n
*/

static int samsung_gpiolib_2bit_input(struct gpio_chip *chip, unsigned offset)
{
        struct samsung_gpio_chip *ourchip = to_samsung_gpio(chip); // 通过samsung_gpio_chip结构体成员chip的首地址获取整个结构体变量的首地址
        void __iomem *base = ourchip->base;   // 获取GPIO控制器寄存器基地址
        unsigned long flags;
        unsigned long con;

        samsung_gpio_lock(ourchip, flags);      // 获取自旋锁

        con = __raw_readl(base + 0x00);         // 获取控制寄存器值 
        con &= ~(3 << (offset * 2));            // 清空代表offset引脚的位数据  00 为输入

        __raw_writel(con, base + 0x00);          // 写回

        samsung_gpio_unlock(ourchip, flags);    // 释放自旋锁
        return 0;
}
3.3.2 samsung_gpiolib_2bit_output

针对GPIOB~GPIOH端口,samsung_gpiolib_2bit_output用于将偏移为offset的GPIO配置为输出,默认输出值为value;

static int samsung_gpiolib_2bit_output(struct gpio_chip *chip,
                                       unsigned offset, int value)
{
        struct samsung_gpio_chip *ourchip = to_samsung_gpio(chip);  // 通过samsung_gpio_chip结构体成员chip的首地址获取整个结构体变量的首地址
        void __iomem *base = ourchip->base;
        unsigned long flags;
        unsigned long dat;
        unsigned long con;

        samsung_gpio_lock(ourchip, flags);     // 获取自旋锁

        dat = __raw_readl(base + 0x04);       // 获取数据寄存器值
        dat &= ~(1 << offset);                // 获取offset引脚的输出值 
        if (value)
                dat |= 1 << offset;          // 设置输出高电平
        __raw_writel(dat, base + 0x04);      // 写回 

        con = __raw_readl(base + 0x00);      // 获取控制寄存器的值
        con &= ~(3 << (offset * 2));         // 清空代表offset引脚的位数据  
        con |= 1 << (offset * 2);            // 写入10 代表输出

        __raw_writel(con, base + 0x00);
        __raw_writel(dat, base + 0x04);

        samsung_gpio_unlock(ourchip, flags);  // 释放自旋锁
        return 0;
}
3.3.3 samsung_gpiolib_set

针对GPIOA~GPIOH端口,samsung_gpiolib_set用于设置偏移为offset的GPIO的的输出电平;

static void samsung_gpiolib_set(struct gpio_chip *chip,
                                unsigned offset, int value)
{
        struct samsung_gpio_chip *ourchip = to_samsung_gpio(chip);  // 通过samsung_gpio_chip结构体成员chip的首地址获取整个结构体变量的首地址
        void __iomem *base = ourchip->base;
        unsigned long flags;
        unsigned long dat;

        samsung_gpio_lock(ourchip, flags);   // 获取自旋锁

        dat = __raw_readl(base + 0x04);       // 获取数据寄存器值
        dat &= ~(1 << offset);                // 获取offset引脚的输出值 
        if (value)
                dat |= 1 << offset;           // 设置输出高电平
        __raw_writel(dat, base + 0x04);       // 写回

        samsung_gpio_unlock(ourchip, flags);    // 释放自旋锁
}
3.3.4 samsung_gpiolib_get

针对GPIOA~GPIOH端口,samsung_gpiolib_get用于获取偏移为offset的GPIO的的输出电平;

static int samsung_gpiolib_get(struct gpio_chip *chip, unsigned offset)
{
        struct samsung_gpio_chip *ourchip = to_samsung_gpio(chip);     // 通过samsung_gpio_chip结构体成员chip的首地址获取整个结构体变量的首地址
        unsigned long val;

        val = __raw_readl(ourchip->base + 0x04);      // 获取数据寄存器值
        val >>= offset;                               // 右移
        val &= 1;                                     // 获取输出值

        return val;
}
posted @ 2023-03-02 23:15  大奥特曼打小怪兽  阅读(203)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步