概述:
在各个平台(cpu厂商)上,都会用很多pin用于和外设通讯,同一个pin也会有多种功能,如同一个pin具有gpio,spi,uart,I2C等功能,这些功能会根据需求切换,以及在不同功能下都会上下拉,驱动能力,电压域的配置。linux为管理这些pin,就设计出了pinctrl subsystem来解决这类问题。当然,也可以不用pinctrl来管理,平台厂商如果不用,就会自己设计接口来管理,这种情况就具体看了。
pinctrl subsystem的结构:
个人理解,在linux提供给驱动用的subsystem基本上都是:
- 外设驱动开发接口
- 平台(cpu)开发接口
- pinctrl core
本文将按照这三个部分,来讲述pinctrl subsystem。
pinctrl core:
pinctrl core目的(效果,作用):
驱动开发者,在开发外设驱动时,将外部设备与主控连接的pin配置需要的功能时,只需要简单的提供设备的struct device和功能的名字,这两个信息就可以设置到需要的功能。为实现目的,设计三个结构体为核心(drivers\pinctrl\core.h):
struct pinctrl_dev;
struct pinctrl_map;
struct pinctrl;
同时也定义了这三个结构体链表,方便管理和查询pinctrldev_list,pinctrl_maps,pinctrl_list(定义都在drivers\pinctrl\core.c)
(1)首先,平台厂商通过pinctrl_register()注册struct pinctrl_dev到core,
struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc,struct device *dev, void *driver_data)
重点就在构建struct pinctrl_desc 和driver_data.
(2)一般在驱动开发中,都是通过platform注册,在probe前会调用在drivers\base\dd.c里面函数really_preboe(),在这个函数里面,又会调用pinctrl_bind_pins(),这个函数就会为struct device创建pins成员,如果device中of_node(dts中节点)指向的dts节点中,有:
pinctrl-names = "default","gpio";
pinctrl-0 = <0xb8 0xb9>;
pinctrl-1 = <0xba>;
则根据phandle值,建立struct pinctrl_map,这样,设备device的pins(struct pinctrl)通过pinctrl_map与pinctrl_dev联系起来,达到了通过device查询到pinctrl_dev里面pin的配置。改变pin属性配置,就变成了只需要修改dts里面设备节点phandle的值了。
外设驱动开发接口:
平台提供商在releas的code里面,如果用pinctrl来管理pin,那么一般都会做好cpu的pin资源与pinctrl subsystem的对接。那剩下给外设驱动开发,配置某个pin成需要的功能,就非常简单了。主要用下面两个函数即可,用下面的函数需要在驱动模块里面包含如下头文件:
#include <linux/pinctrl/consumer.h>
函数:
struct pinctrl_state *pinctrl_lookup_state(struct pinctrl *p,
const char *name);
int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *state)
用法:
- 首先用pinctrl_lookup_state()查询是否有name指定的功能,如果有返回pinctrl_state这个结构体的指针,没有返回负数。
- 然后用pinctrl_select_state()切换pin到需要的功能。
例子:
struct device dev;
struct pinctrl_state *pinfunc;
pinfunc = pinctrl_lookup_state(dev.pins->p,"gpio");
if(IS_ERR(pinfunc))
return -1;//error
if(pinctrl_select_state(dev.pins->p,pinfunc))
return -1;//error
return 0;
在dts中查看pin可以配置那些功能:
一般情况,为了描述CPU可控制pin的情况,会在dts中创建一个pinctrl node,如下:
pinctrl@ff770000{
...
...
gpi08_i2c1{
i2c1-sda{
rockchip,pins = <0x8a41>;//具体哪个pin和pin配置成什么功能,这里是i2c的sda
rockchip,pull = <0x4>;//I2C功能,pin上下拉配置要求
rockchip,drive = <0x0>;//pin的驱动能力
linux,phandle = <0xb8>;//此配置的编号,这个值在dts中是唯一的(老版本写法,为了兼容)
phandle = <0xb8>;//和linux,phandle是一样的,此处是新版本,
};
i2c1-scl{
rockchip,pins = <0x8a51>;
rockchip,pull = <0x4>;
rockchip,drive = <0x0>;
linux,phandle = <0xb9>;
phandle = <0xb9>;
};
i2c1-gpio{
rockchip,pins = <0x8a40>;
rockchip,drive = <0x0>;
linux,phandle = <0xba>;
phandle = <0xba>;
};
};
};
...
...
下面是配置成I2C功能,需要指定pin配置:
i2c@ff140000{
...
...
pinctrl-names = "default","gpio";//这个节点提供了两种功能default和gpio
pinctrl-0 = <0xb8 0xb9>;//对应default功能(i2c),pin配置采用上面"phandle = <0xb8>"和"phandle = <0xb9>"所在节点(dts节点)属性配置
pinctrl-1 = <0xba>;//对应gpio,pin配置采用“phandle = <0xba>”所在节点(dts节点)属性配置
...
...
};
回到下面这行代码,就很清楚了:
pinfunc = pinctrl_lookup_state(dev.pins->p,”gpio”);
待续……