在linux中,pinctrl被定义为一个子系统。pinmux功能被pinctrl子系统所管理。
这个功能通常有SOC厂商提供。我们需要关心的是,如何使用它的功能。
pin_configuration_node 是一个多维向量,通常的组成形式是:
pinctrl1:xxxxgroup_1{
xxx,pins= <
PIN_FUNCTION1 PIN_SETTING1
PIN_FUNCTION2 PIN_SETTING2
PIN_FUNCTION3 PIN_SETTING3
...
>;
};
pinctrl在DTS中,有两种描述方式:
1)pinctrl-<IDNUM>= <&phandle1 &phandle2 …>;
它的值是一个phandle的列表,而每个phandle则标记一个pin_configuration_node。
2)pinctrl-names = “<name>”;
注意,在DTS的书写习惯里,“-”减号是具有特殊分隔作用的comma,要注意使用。
例如常见的“xxx-names”,"xxx-gpios"等等。
来看一个例子
gpio-keys{
compatible = "gpio-keys";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_io_foo &pinctrl_io_bar>;
};
gpios{
pinctrl_io_foo: pinctrl_io_foo{
fsl,pins= <
MX6QDL_PAD_DISP0_DAT15__GPIO5_IO09 0x1f059
MX6QDL_PAD_DISP0_DAT13__GPIO5_IO07 0x1f059
>;
};
pinctrl_io_bar: pinctrl_io_bar{
fsl,pins= <
MX6QDL_PAD_DISP0_DAT11__GPIO5_IO05 0x1f059
MX6QDL_PAD_DISP0_DAT9__GPIO4_IO30 0x1f059
MX6QDL_PAD_DISP0_DAT7__GPIO4_IO28 0x1f059
MX6QDL_PAD_DISP0_DAT5__GPIO4_IO026 0x1f059
>;
};
};
再来看看GPIO在DTS里怎么定义。
gpio1:gpio1@1{
gpio-controller;
#gpio-cells = <2>;
};
gpio2:gpio2@5{
gpio-controller;
#gpio-cells = <1>;
};
...
cd-gpios = <&gpio1 17 0>,
<&gpio2 2>,
<0>,
<&gpio1 17 0>;
reset-gpios = <&gpio1 30 0>;
cd-gpios = <&gpio2 10>;
gpio1要求用一个二维向量来描述,而gpio2要求用一个一维标量来描述,
所以如果是描述gpio1的向量,需要三个cell,因为第一个cell必须是gpioctrl的phandle。
在驱动里,通过OFAPI可以得到相关的资源。
例如:
of_get_named_gpio,
gpio_request,
看下面的代码:
static unsigned int gpio_red;
static unsigned int gpio_green;
static unsigned int gpio_btn1;
static unsigned int gpio_btn2;
static int my_pdrv_probe(struct platform_device* pdev)
{
int ret;
struct device_node* np = &pdev->dev.of_node;
gpio_red = of_get_named_gpio(np, "led", 0);
gpio_green = of_get_named_gpio(np, "led", 1);
gpio_btn1 = of_get_named_gpio(np, "btn1", 0);
gpio_btn2 = of_get_named_gpio(np, "btn2", 0);
gpio_request(gpio_red, "red-led");
gpio_request(gpio_green, "green-led");
gpio_request(gpio_btn1, "button-1");
gpio_request(gpio_btn2, "button-2");
....
}
/****************************************************************
in the dts
/****************************************************************
foo_device{
compatible = "packt, gpio-legacy-sample";
led-gpios = <&gpio2 15 0>,
<&gpio2 16 0>;
btn1-gpios = <&gpio2 1 1>;
bin1-gpios = <&gpio2 2 1>;
};
API会给传入的lable自动添加后缀“-gpios”,组成完整的名字。
GPIO可以映射到IRQ。
来看一个例子。
gpio4: gpio4{
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
my_label: node@0{
reg = <0>;
spi_max_frequency = <1000000>;
interrupt-parent = <&gpio4>;
interrupts = <29 IRQ_TYPE_LEVEL_LOW>;
};
其中定义了一个节点gpio4,它既是一个GPIOCTRL,又是一个INTRCTRL。
又定义了一个节点my_label,它的intrparent被赋值为phandle。
IRQ映射的描述,用一个二维向量来描述。第一个cell描述产生IRQ的GPIONUM,第二个cell 描述GPIO上出现什么样的物理状态,就会触发IRQ生效。
有两种途径可以得到IRQNUM:
1)如果是一个I2C或者SPI的设备,那么i2c_client.irq或者spi_device.irq成员就存放这IRQNUM。
2)如果是一个PDEV ,那么可以使用API
int platform_get_irq(struct platform_device* dev, unsigned int num);
完成GPIONUM到IRQNUM的映射。
在linux内核中,
GPIOCTRL是一个设备对象,它对应自己的驱动GPIOCTRLDRIVER,
PINCTRL也是一个设备对象,它也对应自己的驱动PINCTRLDRIVER。
GPIOCTRL需要使用PINCTRL提供的服务。
所以我们在DTS里看到如下格式:
pinctrl0: pinctrl@80018000{
compatible = "fs,imx28-pinctrl","simple-bus"
reg = <0x80018000 2000>;
gpio0: gpio@0{
compatible = "fsl,imx28-gpio";
interrupts = <127>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interruput-cells = <2>;
};
gpio1: gpio@1{
compatible = "fsl,im28-gpio";
interrupts = <126>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
...
};
sdhci0: sdhci@c8000400{
status = "okay";
cd-gpios = <&gpio0 1 0>;
wp-gpios = <&gpio0 2 0>;
power-gpios = <&gpio0 3 0>;
bus-width = <4>;
};
nand0:gpio-nand@1,0{
compatible = "gpio-control-nand";
reg = <1 0x0000 0x2>;
#address-cells = <1>;
#size-cells = <1>;
gpios = <
&gpio1 1 0
&gpio1 2 0
&gpio1 3 0
&gpio1 4 0
0
>;
...
};
内核解析DTB时,是递归解析的,所以,总是会先生成DT中的叶子节点的设备对象,然后再生成上层节点的设备对象。
所以,在这个例子里,首先会生成叶子节点,也就是gpio0设备对象,然后返回上一层,即pinctrl0,检查发现,pinctrl0仍然有其他的下层子节点,于是再次进入下一层,即gpio1,发现这是一个叶子节点,生成gpio1,然后返回上一层,检查发现,已经没有子节点未生成,所以开始生成pinctrl0的设备节点。
从这个过程中,也可以发现,GPIOCTRL是使用PINCTRL提供的服务的。
另外一个设备对象sdhci0需要使用gpio0提供的服务。所以在它的节点里做出了描述。
另外一个设备对象nand0需要使用gpio1提供的服务。所以在它的节点里做出了描述。与sdhci0不同的时,它并不对gpio命名,而是以TABLE的形式的给出所有的描述。
这样,DRIVER中就只能使用API
of_get_gpio()
来获取GPIONUM了。
在内核中,是用GPIOCHIP来描述一个GPIOCTRL的。
struct gpio_chip(
const char* label;
struct module* owner;
struct device* dev;
struct device_node* of_node;
const struct of_phandle_args* gpiospec;
int base;
u16 ngpio;
const char* const* name;
unsigned exported;
int (*request)(struct gpio_chip* chip, unsigned offset);
int (*free)(struct gpio_chip* chip, unsigned offset);
int (*direction_input)(struct gpio_chip* chip, unsigned offset);
int (*direction_output)(struct gpio_chip* chip, unsigned offset, int value);
int (*get)(struct gpio_chip* chip, unsigned offset);
int (*set)(struct gpio_chip* chip, unsigned offset, int value);
int (*to_irq)(struct gpio_chip* chip, unsigned offset);
int (*set_debounce)(struct gpio_chip* chip, unsigned offset, unsigned debounce);
...
);
GPIOCHIP是一个控制块。它内部有大量的函数指针 ,用来作为函数接口。
通常SOC厂商提供了GPIOCHIP的驱动,并注册到内核。
在其他设备的驱动中,如果需要GPIOCHIP提供服务,可以使用内核提供的API,内核根据GPIONUM,找到对应的GPIOCHIP,并使用其Callback。
int gpio_request(unsigned gpio, const char* label);
void gpio_free(unsigned gpio);
int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value);
int gpio_set_debounce(unsigned gpio, unsigned debounce);
int gpio_request_one(unsigned gpio, unsigned long flags, const char* label);
int gpio_request_array(const struct gpio* array, size_t num);
void gpio_free_array(const struct gpio*array, size_t num);
int devm_gpio_request(struct device* dev, unsigned gpio, unsigned long flags, const char* label);
int devm_gpio_request_one(struct device* dev, unsigned gpio, unsigned long flags, const char* label);
void devm_gpio_free(struct device* dev, unsigned int gpio);
内核中,很对MEM,IRQ,CLK,GPIO,PINCTRL,REGULATOR,都提供了devm的API,devm分配的资源,会被内核进行管理,类似于JAVA的垃圾回收。所以,devm分配的资源,不用在代码中手动释放。
对于GPIO,内核会创建/sys/class/gpio/gpioN/目录,用户程序可以echo或者cat 这些文件的值,来设置GPIO的方向和电平。
当一个GPIOCHIP被注册到内核后,内核会创建/sys/class/gpio/gpiochipX/目录,用户程序可以echo或者cat这些文件的内容。
类似于GPIOCHIP,内核提供了IRQCHIP,用来描述IRQCTRL。它提供了一系列的Callback,可以让内核调用这些Callback。
内核通过调用这些Callback,来完成硬件上的GIC的设置。
struct irq_chip{
struct device* parent_device;
const char* name;
unsigned long flags;
void (*irq_enable)(struct irq_data* data);
void (*irq_disable)(struct irq_data* data);
void (*irq_mask)(struct irq_data* data);
void (*irq_unmask)(struct irq_data* data);
void (*irq_ack)(struct irq_data* data);
void (*irq_retrigger)(struct irq_data* data);
...
};
IRQCHIP关联到一个DEVICE,作为父设备。
当用户程序调用request_irq()等内核API时,内核会在其中调用IRQCHIP的Callback,完成一些需要的硬件设置工作。
IRQCHIP是内核负责管理和使用的驱动,当我们在其他驱动函数中使用request_irq时,是不需要关心IRQCHIP的。
我们来简单回顾一下在linux中怎么使用IRQ。
request_irq()向内核注册了一个TOPHALF。当TH执行时,在TH内调度BH准备后续执行。
request_irq()向内核注册一个TOPHALF和一个BOTTOMHALF,内核会将BH放在一个kernel thread里面执行。
这往往会引起麻烦。
内核提供了API,
int request_any_context_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char* name,
void* dev_id);
来简化申请IRQ的操作。
具有权项interrupt-controller的设备节点,在内核内会有一个irq_chip。
它有一个成员parent_device。所以,需要给它配置权项interrupt-parent。
如果有设备需要使用某个IRQCTRL设备节点,需要用phandle来引用。
expander: mcp23016@20{
compatible = "microchip,mcp23016";
reg = <0x20>;
interrupt-controller;
#interrupt-cells = <2>;
interrupt-parent = <&gpio4>;
interrupts = <29 IRQ_TYPE_EDGE_FALLING>;
gpio-controller;
#gpio-cells = <2>;
};
foo_device: foo_device@1c{
reg = <0x1c>;
interrupt-parent = <&expander>;
interrupts = <2 IRQ_TYPE_ENDGE_RISING>;
};
bar_device: bar_device@2f{
reset-gpios = <&expander 8 GPIO_ACTIVE_HIGH>;
power-gpios = <&expander 12 GPIO_ACTIVE_HIGH>;
};
expander是一个GPIOCTRL,所以,内核会从这个节点的描述中,创建一个gpio_chip。
expander是一个IRQCTRL,所以,内核会从这个节点的描述中,创建一个irq_chip。它也具有自己的irq_domain。
expander需要使用上一层IRQCTRL提供的IRQ,所以它描述了IRQPARENT,以及使用的IRQ。
foo_device需要使用expander的IRQ,所以它描述了IRQPARENT,以及使用的IRQ。