linux驱动(第十八课,PINCTRL,GPIO, GPIO_CHIP, IRQ_CHIP)

在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。

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值