GPIO子系统

文章详细介绍了Linux内核中关于GPIO(通用输入输出)子系统和引脚控制(pinctrl)的工作原理。GPIO是数字线,可以设置为输入或输出。pinctrl子系统管理引脚的复用和配置,通过设备树(DT)进行配置。在驱动程序中,使用pinctrl_get()等函数来选择和配置引脚状态。同时,文章还提到了GPIO的中断处理和在设备树中声明GPIO的方式。
摘要由CSDN通过智能技术生成

一、引脚控制

        SoC会复用引脚,这意味着引脚可能会有多个功能。比如MX6QDL_PAD_SD3_DAT1可以是SD3数据线1、UART1的cts/rts、FlexCan2的Rx或标准GPIO。引脚工作模式的选择机制称为引脚多路复用,负责选择的系统被称为引脚控制器。内核中引脚控制子系统(pinctrl)能够管理引脚复用。

        DT中需要引脚以某种方式多路复用的设备必须声明它所需要的引脚控制配置。引脚控制子系统分为两部分:1. 引脚复用;2. 引脚配置:应用引脚的电器特性。

        pinctrl只是收集引脚的方式,并把他们传递给驱动程序。引脚控制驱动程序负责解析DT中的引脚描述,并将其配置应用到芯片中。驱动程序通常需要一组两个嵌套节点来描述引脚组配置,第一个节点描述组的功能,第二个节点用于保存引脚配置。

引脚应用示例:

uhdc@0219c000 {
    non-removable;
    vmmc-supply = <&reg_3p3v>
    status ="okay";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_usdhc4_1>;  // 设置引用的pinctrl组
};

gpio-keys {
    compatible = "gpio-keys";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_io_foo &pinctrl_io_bar>;
};

iomuxc@020e0000 {
    compatible = "fsl,imx6q-iomuxc";
    reg = <0x020e0000 0x4000>;

    // 共享pinctrl设置
    usdhc4 {
        pinctrl_usdhc4_1: usdhc4grp-1 {
            fsl,pins= <
                MX6QDL_PAD_SD4_CMD_SD4_CMD 0X17059 
                MX6QDL_PAD_SD4_CLK_SD4_CLK 0X1059
                MX6QDL_PAD_SD4_DAT0_SD4_DATA0 0X17059
                MX6QDL_PAD_SD4_DAT1_SD4_DATA1 0X17059
                MX6QDL_PAD_SD4_DAT2_SD4_DATA2 0X17059
                MX6QDL_PAD_SD4_DAT3_SD4_DATA3 0X17059
                MX6QDL_PAD_SD4_DAT4_SD4_DATA4 0X17059
                MX6QDL_PAD_SD4_DAT5_SD4_DATA5 0X17059
                MX6QDL_PAD_SD4_DAT6_SD4_DATA6 0X17059
                MX6QDL_PAD_SD4_DAT7_SD4_DATA7 0X17059
            >;
        };
    };
    gpios {
        pinctrl_io_foo:pinctrl_io_foo {
            fsl,pins = <
                MX6QDL_PAD_DISP0_DAT15_GPIO5_I009 0X1f059
                MX6QDL_PAD_DISP0_DAT13_GPIO5_I007 0X1f059
            >;
        };
        pinctrl_io_bar:pinctrl_io_bar {
            fsl,pins = <
                MX6QDL_PAD_DISP0_DAT11_GPIO5_I005 0X1f059
                MX6QDL_PAD_DISP0_DAT9_GPIO4_I030 0X1f059
            >;
        };
    };
};

        上面的DT配置从驱动程序的相关节点调用,这些引脚在相应驱动程序初始化期间配置。在选择引脚之前必须使用pinctrl_get()函数获取引脚控制,然后调用pinctrl_lookup_state()检查请求的状态是否存在,最后调用select_state()应用状态。下面是示例:

struct pinctrl *p;
struct pinctrl_state *s;
int ret;
p = pinctrl_get(dev);
if (IS_ERR(p)) return p;
s = pinctrl_lookup_state(p, name);
ret = pinctrl_select_state(p, s);
if (ret < 0) { devm_pinctrl_put(p); return ERR_PTR(ret);
}

更有用的函数有pinctrl_get_select_default(),可以直接应用pinctrl的状态,示例如下:

// DT文件
dcan1 : d_can@481d0000 {
    status="okay";
    pinctrl-names = "default";
    pinctrl-0 = <&d_can1_pins>;
};

// 在probe函数中:
pinctrl = devm_pinctrl_get_select_default(&pdev->dev);

二、GPIO子系统

        从硬件角度来看,GPIO是功能,是引脚可以运行的模式。从软件角度来看,GPIO是数字线,可以作为输入和输出,只有有0和1两个状态。

        在驱动程序模块使用GPIO之前,应该向内核声明它,以防止和其他内核模块冲突。在获得模块的权限后,可以设置方向,用作输出时可以切换输出状态,在用作输入时可以设置去抖动间隔。对于映射到IRQ的GPIO线,则可以定义触发中断的方式并注册中断处理程序。

1. 旧的使用整数的GPIO接口的传统方法

        GPIO使用正常标识。

1 声明和配置

// 声明占用和释放GPIO
static int gpio_request(unsigned gpio, const char* label);
void gpio_free(unsigned gpio);

// 判断gpio释放可用
static bool gpio_is_valid(unsigned gpio);

// 设置gpio为输入还是输出
static int gpio_direction_input(unsigned gpio);
static int gpio_direction_output(unsigned gpio, int value); 

// 设置gpio的去抖动时间,其中debounce以ms为单位
static int gpio_set_debounce(unsigned gpio, unsigned debounce);

2 访问GPIO--获取和设置值

// 当gpio没有连接到I2C或SPI等慢速总线上,不会导致睡眠,可以在原子上下文中使用
static int gpio_get_value(unsigned gpio);
void gpio_set_value(unsigned gpio, int value); // value为bool值,0表示低电平,非0高电平

// 可以用gpio_can_sleep()判断gpio线是否可能睡眠
bool gpio_cansleep(unsigned gpio);

// 当gpio没有连接到I2C或SPI等慢速总线上,不会导致睡眠,可以在原子上下文中使用
static int gpio_get_value_cansleep(unsigned gpio);
void gpio_set_value_cansleep(unsigned gpio, int value); 

// 当gpio映射到irq时,使用方式如下gpio_to_irq,返回irq号,接下来可以使用request_irq申请irq
int gpio_to_irq(unsigned gpio);

2. 基于描述符的GPIO使用方式

使用struct gpio_desc{}代表一个gpio接口,在使用gpio_desc{}之前,必须映射gpio数字到gpio_desc{}数据结构,这是和以前直接用数字的方式需要用gpio_request记录。

struct gpio_desc {
    struct gpio_chip *chip;
    unsigned long flags;
    const char *label;
};

在DT文件中描述GPIO:

foo_device {
    compatible = "acme,foo";
    [...];
    led-gpios = <&gpio 15 GPIO_ACTIVE_HIGH> //红色
                <&gpio 16 GPIO_ACTIVE_HIGH> //绿色
                <&gpio 17 GPIO_ACTIVE_HIGH> //蓝色
                ;
    power-gpios = <&gpio 1 GPIO_ACTIVE_LOW>;
    reset-gpios = <&gpio 1 GPIO_ACTIVE_LOW>;
};

在代码中获取GPIO的方式:

//获取gpio的代码
sruct gpio_desc * gpiod_get_index(struct device *dev, char *con_id,
                                enum gpiod_flags flags);
sruct gpio_desc * gpiod_get(struct device *dev, char *con_id,
                                enum gpiod_flags flags);
void gpiod_put(sruct gpio_desc *desc);

//使用示例:
struct gpio_desc *red, *green, *blue, *power, *reset;
red = gpiod_get_index(dev, "led", 0, GPIO_OUT_HIGH);
green = gpiod_get_index(dev, "led", 1, GPIO_OUT_HIGH);
blue = gpiod_get_index(dev, "led", 2, GPIO_OUT_HIGH);

power = gpiod_get(dev, "power", GPIO_OUT_HIGH);
reset = gpiod_get(dev, "reset", GPIO_OUT_HIGH);

其他类似的功能函数

int gpiod_direction_input(struct gpio_desc *desc);
int gpiod_direction_output(struct gpio_desc *desc, int value);

int gpiod_set_debounce(struct gpio_desc *desc, unsigned debounce);

void gpiod_set_value(struct gpio_desc *desc, int val);
void gpiod_set_value_cansleep(struct gpio_desc *desc, int val);

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

// 二者之间相互转换
struct gpio_desc *gpio_to_desc(unsigned gpio);
int desc_to_gpio(struct gpio_desc *desc);

3. 设备树DT和GPIO接口

        在DT中如何指定GPIO取决于提供他们的控制器,尤其是#gpio-cells属性,它决定用于GPIO说明符的单元数。GPIO说明符至少包含一个控制器句柄和若干参数,通常第一个参数是gpio偏移号,第二参数是gpio配置。在从设备中,gpio应命名为<name>-gpio或<name>-gpios。

gpio1: gpio1 {
    gpio-controller;
    #gpio-cells = <2>;
};

gpio2 : gpio2 {
    gpio-controller;
    #gpio-cells = <1>;
};
foo-device {
    cs-gpios = <&gpio1 17 0>
                <&gpio1 2>
                <&gpio1 17 0>;
    reset-gpio = <&gpio1 30 0>;
    cs-gpios = <&gpio2 10>;
};

// 在传统使用gpio的方式中,需要获取gpio编号,获取方式如下:
int n_gpios = of_get_named_gpio_count(dev.of_node, "cs-gpios");
int first_gpio = of_get_named_gpio(dev.of_node, "cs-gpios");

在设备树内将gpio映射到IRQ的方式,可以使用下面两种方式将gpio映射到IRQ:

i). interrupt-parent: GPIO的gpio控制器;

ii). interrupts: 中断说明符表;

在GPIO控制器中#interrupt-cell确定中断时使用的单元数。通常第一个单元表示要映射到IRQ的gpio编号,第二个cell表示触发中断的电平/边沿。

示例如下:

gpio4 : gpio4 {
    gpio-controller;
    #gpio-cells = <2>;
    interrupt-controller;
    #interrupt-cells = <2>;
};

my_label : node0 {
    reg = <0>;
    spi-max-frequency = <10000000>;
    interrupt-parent = <&gpio4>;
    interrupts = <29 IRQ_TYPE_LEVEL_LOW>;
}

有两种方式可以获取在DT中的绑定的IRQ:

i). 当设备位于I2c或SPI总线上时,IRT映射将完成它,并在probe函数中提供struct i2c_client或struct spi_device{},通过i2c_client.irq或spi_device.irq使用即可。

ii). 当设备是platform设备时,则可以在probe函数中通过struct platform_device{}提供的函数int platform_get_irq(struct platform_device *dev, unsigned int num);

4. GPIO和sysfs目录

1. 在目录/sys/class/gpio/中有两个文件export 和unexport,前者可以将某个gpio编号的端口导出到用户空间,例如export 21 > export。

2. 目录/sys/class/gpio/gpio<N>/,其中N是gpio编号,在该目录中有如下文件:direction、value、edge、active_low等。

三、GPIO控制器驱动程序

1. GPIO chip驱动体系和数据结构

        GPIO驱动程序主要提供的功能有:

  • 建立GPIO方向(输入或输出)的是方法;访问GPIO值的方法;将给定的GPIO映射到IRQ并返回相关编号的方法;
  • 说明相关的GPIO方法是否可能进入睡眠状态;
  • 称作基号的GPIO编号;

在内核中gpio控制器驱动表示为struct gpio_chip{}

strucr gpio_chip {
    const char *label;
    struct device *dev;
    struct module *owner;
    int (*request)(struct gpio_chip *chip, unsigned offset); // 可选,在gpio_request时调用
    int (*free)(struct gpio_chip *chip, unsigned offset);  // 可选,在gpio_free之时调用
    int (*get_direction)(struct gpio_chip *chip, unsigned offset);
    int (*direction_output)(struct gpio_chip *chip, unsigned offset, int value);
    int (*direction_input)(struct gpio_chip *chip, unsigned offset);
    int (*get)(struct gpio_chip *chip, unsigned offset);
    int (*set)(struct gpio_chip *chip, unsigned offset, int value);
    int (*set_multiple)(struct gpio_chip *chip, unsigned long *mask, unsigned long *bits);
    int (*set_debounce)(struct gpio_chip *chip, unsigned offset, unsigned debounce);
    /*上面这些都是gpio操作的钩子函数*/

    int base;
    u16 ngpios;
    const char * const *names;
    bool can_sleep;
    bool irq_not_threaded;
    bool exported;
#ifdef CONGIF_GPIOLIB_IRQCHIP
    struct irq_chip *irqchip;
    struct irq_domain *irqdomain;
    unsigned int irq_base;
    irq_flow_handler_t irq_handler;
    unsigned int irq_default_type;
#endif
#ifdef CONFIG_OF_GPIO
    struct device_node *of_node;
    int of_gpio_n_cells;
    int (*of_xlate)(struct gpio_chip *chip, struct of_phandle_args *pgpiospec, u32 *flags);
#endif
};

结构体gpio_chip{}的注册和取消,在执行完gpiochip_add()后会在/sys/class/gpio/gpiochip<X>(其中X为gpio chip的基地址):

// sruct gpio_chip{}的注册和取消
int gpiochip_add(struct gpio_chip *chip);
void  gpiochip_remove(struct gpio_chip *chip);

// struct gpio_chip的注册一般在probe函数中完成,示例如下:

#define GPIO_NUM 16
stuct mcp23016 {
    struct i2c_client *client;
    struct gpio_chip chip;  
};

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);
    mcp->chip.label = client.name;
    mcp->chip.base = -1;
    mcp->chip.dev = &client->dev;
    mcp->client = client;
    i2c_set_clientdata(client, mcp);
    return gpiochip_add();
}

gpio控制器在DT中:

expander_1 : mcp23016@27 {
    compatible = "microchip,mcp23016";
    interrupt-controller;
    gpio-controller;
    #gpio-cells = <2>;
    interrupt-parent = <&gpio6>;
    interrupts = <31 IRQ_TYPE_LEVEL_LOW>;
    reg=<0x27>;
    #interrupt-cells=<2>;
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值