嵌入式Linux中文站

Pinctrl子系统之一了解基础概念


1.Linux Pinctrl子系统简介
在许多soc内部都包含有pin控制器,通过pin控制器的寄存器,我们可以配置一个或者一组引脚的功能和特性。在软件方面,Linux内核提供了pinctrl子系统,目的是为了统一各soc厂商的pin脚管理。

2.Linux Pinctrl子系统提供的功能
    (1)管理系统中所有的可以控制的pin,在系统初始化的时候,枚举所有可以控制的pin,并标识这些pin。

    (2)管理这些pin的复用(Multiplexing)。对于SOC而言,其引脚除了配置成普通的GPIO之外,若干个引脚还可以组成一个pin group,行程特定的功能。pin control subsystem要管理所有的pin group。

    (3)配置这些pin的特性。例如使能或关闭引脚上的pull-up、pull-down电阻,配置引脚的driver strength。

3.pinctrl相关概念
普通driver调用pin control subsystem的主要目标有两个:

    (1)设定该设备的功能复用。

    (2)设定该device对应的pin的电气特性。

设定设备的功能复用需要了解两个概念,一个是function,另外一个是pin group。function是功能抽象,对应一个HW逻辑block,例如SPI0.虽然给定了具体的function name,我们并不能确定其使用的pins的情况。例如为了设计灵活,芯片内部的SPI0的功能引出到pin group{C6,C7,C8,C9},也可能引出的另外一个pin group{C22.C23,C24,C25},但毫无疑问,这两个pin group不能同时active,毕竟芯片内部的SPI0的逻辑功能电路只有一个,因此只有给出function selector以及function的pin group selector才能进行function mux 的设定。

此外,由于电源管理的要求,某个device可能处于某个电源管理状态,例如idle或者sleep,这时候,属于device的所有pin就会需要处于另外的状态。

综合上述的需求,就定义了pin control state的概念,也就是说设备可能处于非常多的状态中的一个,device driver可以切换设备处于的状态。为了方便管理pin control state 就有了pin control state holder的概念,用来管理一个设备的所有的pin control状态。

综上所述,普通的driver调用pin control subsystem 的接口就是只有三个步骤:

(1)驱动加载或是运行时,获取pin control state holder的句柄

(2)设定pin control的状态

(3)驱动卸载或是退出时,释放pin control state holder的句柄

4.与GPIO子系统的关系


上图显示了gpio子系统和pinctrl子系统之间的关系,即pinctrl子系统实际上也把gpio一起管理起来,所有的gpio操作也需要透过pinctrl子系统来完成,这样,如果一个pin已经被申请为gpio,再通过pinctrl子系统申请为某个function时就会返回错误。

5.与统一设备驱动模型的关系
从"pinctrl子系统关系图"中得知,linux kernel中的统一设备驱动模型也调用了pinctrl子系统的功能。linux kernel中的统一设备驱动模型提供了driver 和device的绑定机制,一旦匹配就会调用driver 的probe函数。

而在调用票probe函数之前,驱动模型实际上已经申请过一次pin了,(前提是dts文件中该驱动节点中有定义名为“default”的pinctrl配置)。

驱动模型中调用driver的probe函数的地方:

static int really_probe(struct device *dev, struct device_driver *drv)
{
int ret = 0;
        ......
/* If using pinctrl, bind pins now before probing */
ret = pinctrl_bind_pins(dev); //对该device涉及的pin进行pin control相关设定
if (ret)
goto probe_failed;

if (driver_sysfs_add(dev)) {
printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
__func__, dev_name(dev));
goto probe_failed;
}

if (dev->bus->probe) { //下面是真正的probe过程。
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) {
ret = drv->probe(dev);
if (ret)
goto probe_failed;
}

driver_bound(dev);
ret = 1;
pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
        .....
}
pinctrl_bind_pins函数定义:

#define PINCTRL_STATE_DEFAULT "default"
#define PINCTRL_STATE_IDLE "idle"
#define PINCTRL_STATE_SLEEP "sleep"
int pinctrl_bind_pins(struct device *dev)
{
int ret;

dev->pins = devm_kzalloc(dev, sizeof(*(dev->pins)), GFP_KERNEL);
if (!dev->pins)
return -ENOMEM;

dev->pins->p = devm_pinctrl_get(dev); //获取pinctrl 
if (IS_ERR(dev->pins->p)) {
dev_dbg(dev, "no pinctrl handle\n");
ret = PTR_ERR(dev->pins->p);
goto cleanup_alloc;
}

dev->pins->default_state = pinctrl_lookup_state(dev->pins->p, //查找这个pin的default状态
PINCTRL_STATE_DEFAULT);
if (IS_ERR(dev->pins->default_state)) {
dev_dbg(dev, "no default pinctrl state\n");
ret = 0;
goto cleanup_get;
}

ret = pinctrl_select_state(dev->pins->p, dev->pins->default_state); //设置pin的default状态
if (ret) {
dev_dbg(dev, "failed to activate default pinctrl state\n");
goto cleanup_get;
}

return 0;
从驱动模型的实现中,不难看出在驱动probe前,就已经申请到default的pin配置了,当然pinctrl的计数已经+1了。

Driver的probe函数可以通过devm_pinctrl_get获得pinctrl的句柄,再自行调用pinctrl_select_state设置pin state。

6.与device tree的关系
在device tree source 文件中可以在驱动节点中定义该驱动需要用到的pin配置。

device-node-name{
   //定义该device自己的属性
    pinctrl-names= "sleep","default","idle";
    pinctrl-0 = &xxx_State_sleep;
    pinctrl-1 = &xxx_state_default;
    pinctrl-2 = &xxx_state_idle;
}
pinctrl-0 pinctrl-1 pinctrl-2 .....表示了该设备的一个个状态,这里我们定义了三个pinctrl-0 pinctrl-1 pinctrl-2,数字0、1、2就是pinctrl-names中对应的字符串数组的index。其中pinctrl-0就是“sleep”状态,pinctrl-1就是“default”状态,pinctrl-2就是“idle”状态。而xxx_state_sleep,xxx_state_default,xxx_state_idel就是驱动具体的pin配置项了,需要在pinctrl设备节点处定义:

pinctrl@e01b0000{
pinctrl-names = "default";
      pinctrl-0 = <&state_default>;

     state_default:pinctrl_default{
      };

xxx_state_sleep:xxx_sleep{
         xxx_mfp{
            actions,groups = "fmp1_3_1spi0_ss","mfp1_3_1_spi0_miso","mfp1_5_4";
            actions,function = "spi0";
         };
       };

Pinctrl子系统在加载时,会调用pinctrl_dt_to_map函数将dts文件中有关pinctrl的配置项解析出来,并根据dts各驱动节点对pinctrl的引用关系,将phandle挂到各个驱动的device tree子节点,各个驱动就可以通过自己的dev句柄获得pinctrl的配置了。


7.与主控驱动的关系
在kernel的machine driver中,需要将主控的pinctrl相关硬件操作形象成一个符合linux pinctrl子系统规范的结构pinctrl_desc,并调用pinctrl_register注册到pinctrl子系统中,这样pinctrl子系统就可以将上层行为转换成具体的硬件操作了:

struct pinctrl_desc {
const char *name;
struct pinctrl_pin_desc const *pins; //描述每个pin的信息为何。
unsigned int npins; //表示可以控制多少个pin。pins和npins这两个成员就确定了一个pin controller所能控制的引脚信息。
const struct pinctrl_ops *pctlops;//全局的控制函数
const struct pinmux_ops *pmxops;//复用引脚的相关的操作函数
const struct pinconf_ops *confops; //用来配置引脚特性(如pull-up/pull-down)
struct module *owner;
};
pctlops成员callback函数说明:

struct pinctrl_ops {
int (*get_groups_count) (struct pinctrl_dev *pctldev); //该pin controller支持多少个pin group
const char *(*get_group_name) (struct pinctrl_dev *pctldev,//给定一个selector(index),获取指定的pin group的name
unsigned selector);
int (*get_group_pins) (struct pinctrl_dev *pctldev, //给定一个selector(index)获取指定的pin group中pin的信息(该pin group
unsigned selector,,                                  //包括多少个pin,每个pin的ID是什么)
const unsigned **pins,
unsigned *num_pins);
void (*pin_dbg_show) (struct pinctrl_dev *pctldev, struct seq_file *s,//debug fs的callback接口
unsigned offset);
int (*dt_node_to_map) (struct pinctrl_dev *pctldev, //分析一个pin configuration node并把分析结果保存成mapping table 
struct device_node *np_config, //entry 每一个entry表示一个setting(一个功能复用设定,或者电气特性设定)
struct pinctrl_map **map, unsigned *num_maps);
void (*dt_free_map) (struct pinctrl_dev *pctldev, //dt_node_to_map的逆函数。
struct pinctrl_map *map, unsigned num_maps);
};
pmxops成员callback函数说明:

struct pinmux_ops {
int (*request) (struct pinctrl_dev *pctldev, unsigned offset);//pinctrl子系统进行具体的复用设定之前需要调用该函数,主要用来请底层的driver判断某个引脚的复用设定是否ok
int (*free) (struct pinctrl_dev *pctldev, unsigned offset);//request的逆函数,调用request函数请求占用了某些pin的资源,调用free可以释放这些资源。
int (*get_functions_count) (struct pinctrl_dev *pctldev);//返回pin controller支持的function的数目。
const char *(*get_function_name) (struct pinctrl_dev *pctldev,//给定一个selector(index)获取指定function的name。
unsigned selector);
int (*get_function_groups) (struct pinctrl_dev *pctldev,//给定一个selector(index)获取指定的function 的pin group信息。
unsigned selector,
const char * const **groups,
unsigned * const num_groups);
int (*enable) (struct pinctrl_dev *pctldev, unsigned func_selector, //enable一个function当然要给我出function selector和pin group的selector
unsigned group_selector);
void (*disable) (struct pinctrl_dev *pctldev, unsigned func_selector,//enable的逆函数
unsigned group_selector);
int (*gpio_request_enable) (struct pinctrl_dev *pctldev,//request并且enable一个单独的gpio pin
struct pinctrl_gpio_range *range,
unsigned offset);
void (*gpio_disable_free) (struct pinctrl_dev *pctldev,//gpio_request_free的逆函数
struct pinctrl_gpio_range *range,
unsigned offset);
int (*gpio_set_direction) (struct pinctrl_dev *pctldev,//设定gpio方向的回调函数
struct pinctrl_gpio_range *range,
unsigned offset,
bool input);
};
confops成员的callback函数说明:

struct pinconf_ops {
#ifdef CONFIG_GENERIC_PINCONF
bool is_generic;
#endif
int (*pin_config_get) (struct pinctrl_dev *pctldev,//给定一个pin ID以及config type ID,获取该引脚上指定的type的配置。
unsigned pin,
unsigned long *config);
int (*pin_config_set) (struct pinctrl_dev *pctldev,//设定一个指定pin的配置
unsigned pin,
unsigned long config);
int (*pin_config_set_bulk) (struct pinctrl_dev *pctldev,
unsigned pin,
unsigned long *configs,
unsigned num_configs);
int (*pin_config_group_get) (struct pinctrl_dev *pctldev,//以pin group为单位,获取pin上的配置信息。
unsigned selector,
unsigned long *config);
int (*pin_config_group_set) (struct pinctrl_dev *pctldev,//以pin group为单位,设定pin group 的特性配置。
unsigned selector,
unsigned long config);
int (*pin_config_group_set_bulk) (struct pinctrl_dev *pctldev,
unsigned selector,
unsigned long *configs,
unsigned num_configs);
int (*pin_config_dbg_parse_modify) (struct pinctrl_dev *pctldev, //debug接口
const char *arg,
unsigned long *config);
void (*pin_config_dbg_show) (struct pinctrl_dev *pctldev, //debug接口
struct seq_file *s,
unsigned offset);
void (*pin_config_group_dbg_show) (struct pinctrl_dev *pctldev, //debug接口
struct seq_file *s,
unsigned selector);
void (*pin_config_config_dbg_show) (struct pinctrl_dev *pctldev,//debug接口
struct seq_file *s,
unsigned long config);
};
7.dts文件中的pinctrl关键词表
7.1 pin命名表
    pin的命名遵循IC spec上的命名,以下命名表以每个pin默认的功能命名,但实际使用中各个pin的功能会随着配置发生变化。

    在dts中使用关键词“actions,pins”(不同厂商不同,这是炬芯的)后跟名字数组来定义需要使用哪些pin,如:

i2c0_over_uart0_pull_up{
actions,pins = “P_UART0_RX”,“P_UART0_TX”;
actions,pull = <2>;
}
7.2 MUX Group-Function表
  7.2.1 Mux Group命名表
    pin group按照寄存器的名字和bit来命名。比如:MFP_CTL1 bit22:21,定义了10个pin的mux:P_LVDS_OEP,P_LVDS_OEN,P_LVDS_ODP,P_LVDS_ODN,P_LVDS_OCP,P_LVDS_OCN,P_LVDS_OBP,P_LVDS_OBN,P_LVDS_OAP,P_LVDS_OAN。该pin group的名字为mfp1_22_21.

    有些mfp寄存器的cell中,设置某一个值会将多个pin配置为不同的功能。那么这个cell中的pin就不能归为同一个pin group。需按情况拆解开,那么拆解开的pin group的名字还会加上一些后缀。

    比如:根据IC SPEC,mfp1 bit[31:29]可控制3根pin:P_KS_IN0,P_KS_IN1,P_KS_IN2.该cell的0x11选项会将这2跟pin分别定义为pwm0,pwm1,pwm4.

那么就将其分拆为3个group:分别为“mfp1_31_29_ks_in2”、“mfp1_31_29_ks_in2”、“mfp1_31_29_ks_in0”。这几个mfp cell分割以后,他们之间仍然存在硬件上的互斥关系,比如将mfp1_31_29_ks_in2的function选为pwm0,mfp1_31_29_ks_in1的function选为pwm1,可以工作。但是将mfp1_31_29_ks_in2的function选为pwm0,mfp1_31_29_ks_in1的function选为jtag,就会返回错误。

    在pin group的命名表中还会看到“xxx_dummy”的命名,这种pin group在pinmux设置中可能会用到。这些pin group只有一种mux功能,所以在mfp寄存器中不会表示,但是这些pin可能和gpio复用,申请这些pin有助于发现它和gpio存在的潜在复用错误。

在dts中使用关键词“actions,groups”后跟mux Group名字数组来定义需要使用哪些Mux group,如:

sd0_mfp_cmd_clk{

     actions,groups = "mfp2_8_7","mfp2_6_5";

     actions,function = "sd0";

};

7.2.2MUX Group-function
    Mux Group所控制的pin,可以通过设置Mux Function的方式改变IC内部连通性,使得同一组pin用作不同的功能,例如:

sd0_mfp_cmd_clk{
actions,groups ="mfp2_8_7","mfp2_6_5";
actions,function = "jtag";
};
7.2.3 Drive Group
paddrv使用的配置值完全等同于IC spec中关于PAD_DRVx寄存器的定义。

对于某些pin可以设置pin的驱动能力(即供电能力),可以通过配置driver group的等级对pin的驱动能力进行配置。

在dts中使用关键词“actions,paddrv”后跟驱动能力等级来定义“actions,groups”指定drive group所代表的pin组使用哪种驱动能力,如:

sd0_d0_d3_cmd_clk_paddrv{
actions,groups="paddrv1_19_18","paddrv1_17_16";
actions,paddrv = "1" /*level 1, range :0 ~ 3*/
}
表示“paddrv1_19_18”所代表的P_SD0_CMD和paddrv1_17_16 所代表的P_SD0_CLK,使用驱动能力1来提升数据传输稳定性。
7.2.4 Pin Pull Up/Down
在dts中使用关键词“actions,pull”后跟上下拉数据定义“actions,pin”指定的pin组使用哪种上下拉,如:

sd0_pull_d0_d3_cmd{
actions,pins = "P_SD0_CMD","P_SD0_CLK";
actions,pull = <1>; 
};
表示将P_SD0_CMD和P_SD0_CLK这两个pin下拉。

actions,pull = <0>,表示上下拉功能关闭

actions,pull = <1>,表示下拉

actions,pull = <2>,表示上拉

7.2.5 GPIO-PIN
pin除了可以复用作各种功能外,还可以配置成GPIO使用,pinctrl子系统将GPIO子系统也管理起来了,因此申请GPIO的时候会去检查该gpio所对应的pin是否已经被其他驱动申请作其他功能了。如果已经被申请则申请时会报错,反之亦然。

8 使用dts描述pinctrl配置
8.1dts中pinctrl配置方法
所有的pinctrl-state都定义在pin controller device节点中:

在kernel/arch/arm64/boot/dts/s700_pinctrl.dtsi 中

/ {

pinctrl@e01b0000 {
compatible = "actions,s700-pinctrl";
reg = <0 0xe01b0000 0 0x1000>;

pinctrl-names = "default";
pinctrl-0 = <&state_default>;
clocks = <&clock CLK_GPIO>;
clock-names = "mfp";

state_default: pinctrl_default {
};

而个驱动引用定义在pin controller device 节点中的子节点,即pinctrl-state节点。

各驱动使用如下方法引用pinctrl-state节点:

pinctrl-N:描述该设备需要使用的pin的一个状态(pin state),相当于上述的state。N的数值必须从0开始顺序递增。pinctrl-N属性的值为pin configuration nodes的phandle。pinctrl-N属性引用的pin configuration nodes必须是pin controller device node的直接子节点。

pinctrl-names:为每个pin state定义一个名字。每个名字顺序对应一个pin state。比如pinctrl-0的名字为“default”,pinctrl-1的名字对应“idle”。若不能指定pinctrl-names属性,这样的话,pin state的名字就是该属性的“N”字符。比如pinctrl-0的名字为字符“0”

mmc@e0330000{
pinctrl-names = "pinctrl_mmc0","share_uart2_5"; 
pinctrl-0 = <&mm0_pinctrl_state>;
pinctrl-1 = <&mmc_share_uart_state>;
}
上面的例子中,mmc驱动定义的pinctrl-state有两个,其中mmc0_state_default是定义pin controller device  node下的直接子节点,表示sd0 的pin group 作为sd功能使用,而mmc_share_uart_state则表示作为serial功能使用,其中mmc_state_default对应的pinctrl-names 属性为default,而mmc_share_uart_state对应的pinctrl-names属性为share_uart2_5。
8.2 pin mapping database的建立
pin controller device节点配置好后,内核是如何获取到pin mapping database并使用的呢?

通过查看pinctrl子系统代码,了解到pin mapping database建立有两种情况:

一种情况是主控驱动调用pinctrl_register注册machine的struct pinctrl_desc结构到pinctrl子系统时,由pinctrl_register调用pinctrl_get创建。

    另一种就是各驱动在加载时由统一设备驱动模型在driver和device绑定时调用devm_pinctrl_get转而调用pinctrl_get创建。

本文永久更新链接:http://embeddedlinux.org.cn/emb-linux/system-development/201909/29-8826.html



分享:

评论