使用过单片机的同学都知道,要用一个gpio点亮一个小灯可以分为两步,一是设置“配置寄存器“来配置cpu引脚的复用功能,电气属性(上下拉,速度,驱动能力等),输入输出;二是设置“数据寄存器”的值为0/1,就可以使对应的引脚输出高低电平,让小灯亮灭。
那么linux内核对于不同平台提供了通用的pinctrl子系统,其主要的工作是:获取设备树的pin信息,来配置pin的复用功能和电气属性。
那么比如i2c外设的scl和sda对应的pin的复用功能和电气属性等信息,就会放到这个pinctrl节点里面,下面这个的信息就可以解析为:gpio1的15和16(指定配的是那些具体的pin脚);复用功能为FUNC2功能;且没有上下拉,
i2c0 {
i2c0_xfer: i2c0-xfer {
rockchip,pins =
<1 15 RK_FUNC_2 &pcfg_pull_none>,
<1 16 RK_FUNC_2 &pcfg_pull_none>;
};
};
对用的i2c0节点就会引用这个信息 ,在i2c0的控制器驱动就会根据pinctrl-name来选用pin的配置信息,有的不仅有defalut,也会有sleep来供特殊情况来配置,驱动通过pinctrl_lookup_state和pinctrl_select_state来选择用那个复用信息
i2c0: i2c@ff3c0000 {
compatible = "rockchip,rk3399-i2c";
reg = <0x0 0xff3c0000 0x0 0x1000>;
clocks = <&pmucru SCLK_I2C0_PMU>, <&pmucru PCLK_I2C0_PMU>;
clock-names = "i2c", "pclk";
interrupts = <GIC_SPI 57 IRQ_TYPE_LEVEL_HIGH 0>;
pinctrl-names = "default";
pinctrl-0 = <&i2c0_xfer>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};
在内核目录driver/pinctrl就是这个子系统跑起来的关键文件。先看一下设备树中的pinctrl节点,根据兼容属性可以找到对应的驱动,其驱动就会去做上面提到的工作,下面也可以看到gpio节点也放进来了,有些平台gpio节点是作为独立节点的,大差不差的
pinctrl: pinctrl {
compatible = "rockchip,rk3399-pinctrl";
rockchip,grf = <&grf>;
rockchip,pmu = <&pmugrf>;
#address-cells = <2>;
#size-cells = <2>;
ranges;
gpio0: gpio0@ff720000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xff720000 0x0 0x100>;
clocks = <&pmucru PCLK_GPIO0_PMU>;
interrupts = <GIC_SPI 14 IRQ_TYPE_LEVEL_HIGH 0>;
gpio-controller;
#gpio-cells = <0x2>;
interrupt-controller;
#interrupt-cells = <0x2>;
};
gpio1: gpio1@ff730000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xff730000 0x0 0x100>;
clocks = <&pmucru PCLK_GPIO1_PMU>;
interrupts = <GIC_SPI 15 IRQ_TYPE_LEVEL_HIGH 0>;
gpio-controller;
#gpio-cells = <0x2>;
interrupt-controller;
#interrupt-cells = <0x2>;
};
gpio2: gpio2@ff780000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xff780000 0x0 0x100>;
clocks = <&cru PCLK_GPIO2>;
interrupts = <GIC_SPI 16 IRQ_TYPE_LEVEL_HIGH 0>;
gpio-controller;
#gpio-cells = <0x2>;
interrupt-controller;
#interrupt-cells = <0x2>;
};
gpio3: gpio3@ff788000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xff788000 0x0 0x100>;
clocks = <&cru PCLK_GPIO3>;
interrupts = <GIC_SPI 17 IRQ_TYPE_LEVEL_HIGH 0>;
gpio-controller;
#gpio-cells = <0x2>;
interrupt-controller;
#interrupt-cells = <0x2>;
};
gpio4: gpio4@ff790000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xff790000 0x0 0x100>;
clocks = <&cru PCLK_GPIO4>;
interrupts = <GIC_SPI 18 IRQ_TYPE_LEVEL_HIGH 0>;
gpio-controller;
#gpio-cells = <0x2>;
interrupt-controller;
#interrupt-cells = <0x2>;
};
}
驱动文件通过兼容属性就可以找到,然后看probe函数,主要都是获取设备的信息;然后注册pinctrl,最终注册的pinctrl_des结构的ctrldesc实例的三个ops成员就是半导体厂商实现的对pin的配置
static int rockchip_pinctrl_register(struct platform_device *pdev,
struct rockchip_pinctrl *info)
{
struct pinctrl_desc *ctrldesc = &info->pctl;
struct pinctrl_pin_desc *pindesc, *pdesc;
struct rockchip_pin_bank *pin_bank;
int pin, bank, ret;
int k;
ctrldesc->name = "rockchip-pinctrl";
ctrldesc->owner = THIS_MODULE;
ctrldesc->pctlops = &rockchip_pctrl_ops;//对应那个gpio组,多少号
ctrldesc->pmxops = &rockchip_pmx_ops;//对应复用功能设置
ctrldesc->confops = &rockchip_pinconf_ops;//对应电气属性设置
pindesc = devm_kzalloc(&pdev->dev, sizeof(*pindesc) *
info->ctrl->nr_pins, GFP_KERNEL);
if (!pindesc)
return -ENOMEM;
ctrldesc->pins = pindesc;
ctrldesc->npins = info->ctrl->nr_pins;
pdesc = pindesc;
for (bank = 0 , k = 0; bank < info->ctrl->nr_banks; bank++) {
pin_bank = &info->ctrl->pin_banks[bank];
for (pin = 0; pin < pin_bank->nr_pins; pin++, k++) {
pdesc->number = k;
pdesc->name = kasprintf(GFP_KERNEL, "%s-%d",
pin_bank->name, pin);
pdesc++;
}
}
ret = rockchip_pinctrl_parse_dt(pdev, info);//解析出设备树里的pin信息
if (ret)
return ret;
info->pctl_dev = devm_pinctrl_register(&pdev->dev, ctrldesc, info);
if (IS_ERR(info->pctl_dev)) {
dev_err(&pdev->dev, "could not register pinctrl driver\n");
return PTR_ERR(info->pctl_dev);
}
for (bank = 0; bank < info->ctrl->nr_banks; ++bank) {
pin_bank = &info->ctrl->pin_banks[bank];
pin_bank->grange.name = pin_bank->name;
pin_bank->grange.id = bank;
pin_bank->grange.pin_base = pin_bank->pin_base;
pin_bank->grange.base = pin_bank->gpio_chip.base;
pin_bank->grange.npins = pin_bank->gpio_chip.ngpio;
pin_bank->grange.gc = &pin_bank->gpio_chip;
pinctrl_add_gpio_range(info->pctl_dev, &pin_bank->grange);
}
return 0;
}
rockchip_pinctrl_parse_dt调用rockchip_pinctrl_parse_functions来解析上面pinctrl节点里面的诸如spi0,i2c0等所有子节点(虽然gpio的bank也是其子节点,但是被下面的代码排除了)
static int rockchip_pinctrl_parse_dt(struct platform_device *pdev,
struct rockchip_pinctrl *info)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct device_node *child;
int ret;
int i;
rockchip_pinctrl_child_count(info, np);
dev_dbg(&pdev->dev, "nfunctions = %d\n", info->nfunctions);
dev_dbg(&pdev->dev, "ngroups = %d\n", info->ngroups);
info->functions = devm_kzalloc(dev, info->nfunctions *
sizeof(struct rockchip_pmx_func),
GFP_KERNEL);
if (!info->functions)
return -EINVAL;
info->groups = devm_kzalloc(dev, info->ngroups *
sizeof(struct rockchip_pin_group),
GFP_KERNEL);
if (!info->groups)
return -EINVAL;
i = 0;
for_each_child_of_node(np, child) {
if (of_match_node(rockchip_bank_match, child))
continue;
ret = rockchip_pinctrl_parse_functions(child, info, i++);
if (ret) {
dev_err(&pdev->dev, "failed to parse function\n");
of_node_put(child);
return ret;
}
}
return 0;
}
从下面代码也可以看出来,对于功能和组的定义是什么?功能就是pinctrl节点去除bank节点的其他子节点,nfunctions就是pinctrl所有子节点的组合;组就是子节点的子节点,ngroups就是pinctrl某个子节点的所有子节点的组合
static void rockchip_pinctrl_child_count(struct rockchip_pinctrl *info,
struct device_node *np)
{
struct device_node *child;
for_each_child_of_node(np, child) {
if (of_match_node(rockchip_bank_match, child))
continue;
info->nfunctions++;
info->ngroups += of_get_child_count(child);
}
}
rockchip_pinctrl_parse_functions就是解析pinctrl的单个功能节点,比如spi0功能节点的每个组
static int rockchip_pinctrl_parse_functions(struct device_node *np,
struct rockchip_pinctrl *info,
u32 index)
{
struct device_node *child;
struct rockchip_pmx_func *func;
struct rockchip_pin_group *grp;
int ret;
static u32 grp_index;
u32 i = 0;
dev_dbg(info->dev, "parse function(%d): %s\n", index, np->name);
func = &info->functions[index];
/* Initialise function */
func->name = np->name;
func->ngroups = of_get_child_count(np);
if (func->ngroups <= 0)
return 0;
func->groups = devm_kzalloc(info->dev,
func->ngroups * sizeof(char *), GFP_KERNEL);
if (!func->groups)
return -ENOMEM;
for_each_child_of_node(np, child) {
func->groups[i] = child->name;
grp = &info->groups[grp_index++];
ret = rockchip_pinctrl_parse_groups(child, grp, info, i++);
if (ret) {
of_node_put(child);
return ret;
}
}
return 0;
}
rockchip_pinctrl_parse_groups解析spi0节点下面的单个”rockchip,pins“组里面pin的配置信息(既然是pins,那么里面的pin就会是单个或多个);从下面看spi0这个功能节点有5个组,这个函数也就需要执行5次来解析
.....................
spi0 {
spi0_clk: spi0-clk {
rockchip,pins =
<3 6 RK_FUNC_2 &pcfg_pull_up>;
};
spi0_cs0: spi0-cs0 {
rockchip,pins =
<3 7 RK_FUNC_2 &pcfg_pull_up>;
};
spi0_cs1: spi0-cs1 {
rockchip,pins =
<3 8 RK_FUNC_2 &pcfg_pull_up>;
};
spi0_tx: spi0-tx {
rockchip,pins =
<3 5 RK_FUNC_2 &pcfg_pull_up>;
};
spi0_rx: spi0-rx {
rockchip,pins =
<3 4 RK_FUNC_2 &pcfg_pull_up>;
};
};
.....................
组的定义在下面是<bank pin mux CONFIG>,跟最上面的<1 15 RK_FUNC_2 &pcfg_pull_none>完美对应
static int rockchip_pinctrl_parse_groups(struct device_node *np,
struct rockchip_pin_group *grp,
struct rockchip_pinctrl *info,
u32 index)
{
struct rockchip_pin_bank *bank;
int size;
const __be32 *list;
int num;
int i, j;
int ret;
dev_dbg(info->dev, "group(%d): %s\n", index, np->name);
/* Initialise group */
grp->name = np->name;
/*
* the binding format is rockchip,pins = <bank pin mux CONFIG>,
* do sanity check and calculate pins number
*/
list = of_get_property(np, "rockchip,pins", &size);
/* we do not check return since it's safe node passed down */
size /= sizeof(*list);
if (!size || size % 4) {
dev_err(info->dev, "wrong pins number or pins and configs should be by 4\n");
return -EINVAL;
}
grp->npins = size / 4;
grp->pins = devm_kzalloc(info->dev, grp->npins * sizeof(unsigned int),
GFP_KERNEL);
grp->data = devm_kzalloc(info->dev, grp->npins *
sizeof(struct rockchip_pin_config),
GFP_KERNEL);
if (!grp->pins || !grp->data)
return -ENOMEM;
for (i = 0, j = 0; i < size; i += 4, j++) {
const __be32 *phandle;
struct device_node *np_config;
num = be32_to_cpu(*list++);
bank = bank_num_to_bank(info, num);
if (IS_ERR(bank))
return PTR_ERR(bank);
grp->pins[j] = bank->pin_base + be32_to_cpu(*list++);
grp->data[j].func = be32_to_cpu(*list++);
phandle = list++;
if (!phandle)
return -EINVAL;
np_config = of_find_node_by_phandle(be32_to_cpup(phandle));
ret = pinconf_generic_parse_dt_config(np_config, NULL,
&grp->data[j].configs, &grp->data[j].nconfigs);
if (ret)
return ret;
}
return 0;
}
前面提到的pinctrl_select_state就会使用pinctrl驱动用devm_pinctrl_register注册时,放进去的那三个ops(下面的代码前几行就可以看出)来真正的配置pin的复用功能和电气属性;pinctrl_select_state会调用pinctrl_commit_state,pinctrl_commit_state会调用pinmux_enable_setting 来设置复用功能
int pinmux_enable_setting(struct pinctrl_setting const *setting)
{
struct pinctrl_dev *pctldev = setting->pctldev;
const struct pinctrl_ops *pctlops = pctldev->desc->pctlops;
const struct pinmux_ops *ops = pctldev->desc->pmxops;
int ret = 0;
const unsigned *pins = NULL;
unsigned num_pins = 0;
int i;
struct pin_desc *desc;
if (pctlops->get_group_pins)
ret = pctlops->get_group_pins(pctldev, setting->data.mux.group,
&pins, &num_pins);
if (ret) {
const char *gname;
/* errors only affect debug data, so just warn */
gname = pctlops->get_group_name(pctldev,
setting->data.mux.group);
dev_warn(pctldev->dev,
"could not get pins for group %s\n",
gname);
num_pins = 0;
}
/* Try to allocate all pins in this group, one by one */
for (i = 0; i < num_pins; i++) {
ret = pin_request(pctldev, pins[i], setting->dev_name, NULL);
if (ret) {
const char *gname;
const char *pname;
desc = pin_desc_get(pctldev, pins[i]);
pname = desc ? desc->name : "non-existing";
gname = pctlops->get_group_name(pctldev,
setting->data.mux.group);
dev_err(pctldev->dev,
"could not request pin %d (%s) from group %s "
" on device %s\n",
pins[i], pname, gname,
pinctrl_dev_get_name(pctldev));
goto err_pin_request;
}
}
/* Now that we have acquired the pins, encode the mux setting */
for (i = 0; i < num_pins; i++) {
desc = pin_desc_get(pctldev, pins[i]);
if (desc == NULL) {
dev_warn(pctldev->dev,
"could not get pin desc for pin %d\n",
pins[i]);
continue;
}
desc->mux_setting = &(setting->data.mux);
}
ret = ops->set_mux(pctldev, setting->data.mux.func,
setting->data.mux.group);
if (ret)
goto err_set_mux;
return 0;
err_set_mux:
for (i = 0; i < num_pins; i++) {
desc = pin_desc_get(pctldev, pins[i]);
if (desc)
desc->mux_setting = NULL;
}
err_pin_request:
/* On error release all taken pins */
while (--i >= 0)
pin_free(pctldev, pins[i], NULL);
return ret;
}
也会调用pinconf_apply_setting来设置电气属性,至此完成了单片的两三行完成的工作,但也大大降低了开发人员对不同平台的使用门槛
int pinconf_apply_setting(struct pinctrl_setting const *setting)
{
struct pinctrl_dev *pctldev = setting->pctldev;
const struct pinconf_ops *ops = pctldev->desc->confops;
int ret;
if (!ops) {
dev_err(pctldev->dev, "missing confops\n");
return -EINVAL;
}
switch (setting->type) {
case PIN_MAP_TYPE_CONFIGS_PIN:
if (!ops->pin_config_set) {
dev_err(pctldev->dev, "missing pin_config_set op\n");
return -EINVAL;
}
ret = ops->pin_config_set(pctldev,
setting->data.configs.group_or_pin,
setting->data.configs.configs,
setting->data.configs.num_configs);
if (ret < 0) {
dev_err(pctldev->dev,
"pin_config_set op failed for pin %d\n",
setting->data.configs.group_or_pin);
return ret;
}
break;
case PIN_MAP_TYPE_CONFIGS_GROUP:
if (!ops->pin_config_group_set) {
dev_err(pctldev->dev,
"missing pin_config_group_set op\n");
return -EINVAL;
}
ret = ops->pin_config_group_set(pctldev,
setting->data.configs.group_or_pin,
setting->data.configs.configs,
setting->data.configs.num_configs);
if (ret < 0) {
dev_err(pctldev->dev,
"pin_config_group_set op failed for group %d\n",
setting->data.configs.group_or_pin);
return ret;
}
break;
default:
return -EINVAL;
}
return 0;
}