X-023-KERNEL-Linux pinctrl driver的移植
作者:wowo 发布于:2017-7-14 21:58 分类:X Project
1. 前言
本文是“linux内核中的GPIO系统之(4):pinctrl驱动的理解和总结”的一个实例,结合”X Project”的开发过程,介绍pinctrl driver的移植步骤,进而加深对pinctrl framework的理解。
注1:本文后续的描述,kernel基于本站“X Project”所使用的kernel版本[4],硬件基于 ”X Project”所使用的“Bubbugum-96”平台。
2. pinctrl driver的移植步骤
2.1 添加pinctrl driver在驱动模型中的框架
在linux kernel中,pin controller也是一个普通的platform device,因此需要基于platform设备的框架(可参考[1])添加自身的驱动模型。对于这些单纯的设备,可以直接用一些标准化的接口来完成,如下(都是轻车熟路的东西,不再解释了):
---->drivers/pinctrl/pinctrl-owl.c
/*============================================================================ |
(另外,需要编辑drivers/pinctrl中的Kconfig和Makefile文件,加入我们新的pinctrl driver,并更改”X Project”的kernel配置项,加入该driver的编译,具体步骤略,代码可参考如下的patch:https://github.com/wowotechX/linux/commit/45ca29b3680db7b6d62735439a0ef93ab9797537,
https://github.com/wowotechX/linux/commit/749f114564765025d907618d7306bc668c0a0a93)。
2.2 添加pinctrl driver自身的框架
参考“Documentation/pinctrl.txt[2]"中的示例,创建一个struct pinctrl_desc变量,并在probe中调用pinctrl_register注册到kernel中。
另外,为了在同一个driver中支持多个soc,可以将struct pinctrl_desc变量的指针保存在每个soc的match table中,并在probe中借助of_device_get_match_data将其获取出来(这是device tree时代的惯用伎俩,大家可以多使用),如下黄色背景所示:
static const struct of_device_id owl_pinctrl_of_match[] = { ... static int __init owl_pinctrl_probe(struct platform_device *pdev) desc = of_device_get_match_data(dev); pctl = pinctrl_register(desc, dev, NULL); |
可以直接从Documentation/pinctrl.txt中copy过来,将foo换成自己平台的代号(如这里的s900),然后在owl_pinctrl_probe中调用pinctrl_register注册即可。
以上代码可参考如下的patch:https://github.com/wowotechX/linux/commit/9ce89fe7e5d68f2d84dab39af0665acfc3b62751
2.3 定义管脚(pin)
driver框架确定了之后,就简单了,根据[3]中的介绍,一个一个的添加各种软件抽象就行了。例如可以根据自己平台的实际情况,定义管脚。
注2:这里以“Bubbugum-96”平台为例,硬件有关的说明可参考之前的文章。另外,为了说明pinctrl driver的编写过程,这里简化一下,暂时只关心UART5有关的pin,其它的后面用的时候再加就行了。
根据原理图,uart5有关的pin脚包括(好东西,足够我们把pinctrl的原理展示完了):
GPIOA25/UART5_RX/SENS0_VSYNC/PWM2(A7)
GPIOA27/UART5_TX/SENS0_HSYNC/PWM2(D8)
GPIOA28/UART2_RX/UART5_RX/SD0_D0(C5)
GPIOA29/UART2_TX/UART5_TX/SD0_D1(B6)
GPIOF6/UART5_RX/UART3_RTSB(G2)
GPIOF7/UART5_TX/UART3_CTSB(G1)
名字,好办,就用A7、D8之类的就行了,编号怎么办呢?
先随便定吧(后面可能会根据寄存器的特性调整),直接用字母(转成数字,A是0,B是1,等等)乘以10加数字(数字好像是从1开始,那就数字减一),即:
A7 = (1 – 1)* 10 + (7 – 1)= 6
D8 = (4 – 1) * 10 + (8 – 1) = 37
C5 = (3 – 1) * 10 + (5 - 1) = 24
B6 = (2 – 1) * 10 + (6 – 1) = 15
G2 = (7 – 1) * 10 + (2 – 1) = 61
G1 = (7 – 1) * 10 + (1 – 1) =60
最终定义出来的管脚如下:
static const struct pinctrl_pin_desc s900_pins[] = { |
2.4 定义pin group
暂时不考虑GPIO,上面6个pin可以分解出如下groups(麻雀虽小五脏俱全啊!不过简单起见,这里只关注UART5有关的group,因为其它group可能会涉及更多的pin):
uart5_0_group,包括A7和D8两个pin,由bubblegum96的spec[4]可知,当MFP_CTL1 寄存器(0xE01B0044)的bit28:26和bit25:23分别为001(UART5_RX)和100(UART5_TX)时,该group生效;
uart5_1_group,包括C5和B6两个pin,由bubblegum96的spec[4]可知,当MFP_CTL2 寄存器(0xE01B0048)的bit19:17和bit16:14分别为101(UART5_RX)和101(UART5_TX)时,该group生效;
uart5_2_group,包括G2和G1两个pin,由bubblegum96的spec[4]可知,当MFP_CTL2 寄存器(0xE01B0048)的bit21和bit20分别为1(UART5_RX)和1(UART5_TX)时,该group生效。
最后参考[2]中的例子,定义并注册struct pinctrl_ops变量,具体可参考如下patch:https://github.com/wowotechX/linux/commit/478f3fcf2fc950daa6fb85eb302f943a427f0878
2.5 Pin configuration的设计
参考[4]中“5.7.3.24 PAD_PULLCTL0 ”~“5.7.3.35 PAD_SR2 ”章节有关pin configuration的描述,发现s900的pinconf有点特殊,是以特定的功能(function,例如UART0_RX)为单位进行控制的,也就是说,对某一个功能来说,可能映射到不同的管脚上,但对它的配置,都是由相同的寄存器控制的。
基于这样的硬件框架,怎么去抽象pinconf的operations,值得思考。不过由于本文的例子(uart5)没有可配置的选项,所以我们就先略过这里。后面有机会再补回来吧(不影响我们对pinctrl的整体理解)。
2.6 Pin multiplexing的设计
同理,参考[2]中的示例,首先抽象出Pin multiplexing中的functions,本文的例子中,暂时只有一个:uart5,定义如下:
/* drivers/pinctrl/pinctrl-owl.c */ static const char * const uart5_groups[] = { static const struct owl_pmx_func s900_functions[] = { |
该function可以映射到三个不同的group中:"uart5_0_grp", "uart5_1_grp", "uart5_2_grp"(它们的名字要和2.4中定义的groups name相同)。
然后,定义一个struct pinmux_ops变量,并实现相应的回调函数,如下:
static struct pinmux_ops s900_pmxops = { |
上面get_functions_count 、get_function_name以及get_function_groups三个函数比较简单,这里重点提一下set_mux,它的原型如下:
int (*set_mux) (struct pinctrl_dev *pctldev, unsigned func_selector,
unsigned group_selector);
有两个参数:func_selector,用于指定function的number(就是s900_functions中的数组index);group_selector,用于指定group的number(就是2.4中介绍的s900_groups的数组index)。pinctrl driver拿到这两个参数之后,怎么去配置寄存器使某个group的某个function生效呢?要根据具体的pin controller的硬件情况来定。以本文作为示例的s900为例,该SoC的Pin multiplexing的设置,并不需要function和group的一一对应,以uart5 function的group0为例,参考2.4中的介绍:
只要把MFP_CTL1 寄存器(0xE01B0044)的bit28:26和bit25:23分别设置为001(UART5_RX)和100(UART5_TX),就可以使uart5的该group生效。
也就是说,s900 SoC可以单独以group为单位进行pinmux配置。那么我们在代码中可以把相应的寄存器配置信息和groups的定义绑定,在pinctrl core调用.set_mux回调函数的时候,忽略 func_selector,直接通过group_selector获取相应的寄存器信息后,配置寄存器即可,如下:
+struct owl_pmx_reginfo {
+ const unsigned *offset;
+ const unsigned *mask;
+ const unsigned *val;
+
+ unsigned num_entries;
+};
+
struct owl_pinctrl_group {
const char *name;
const unsigned *pins;
unsigned num_pins;
+
+ const struct owl_pmx_reginfo *reginfo;
+};
......
static const struct owl_pinctrl_group s900_groups[] = {
{
.name = "uart5_0_grp",
.pins = s900_uart5_0_pins,
.num_pins = ARRAY_SIZE(s900_uart5_0_pins),
+ .reginfo = &s900_uart5_0_reginfo,
},
{
.name = "uart5_1_grp",
.pins = s900_uart5_1_pins,
.num_pins = ARRAY_SIZE(s900_uart5_1_pins),
+ .reginfo = &s900_uart5_1_reginfo,
},
{
.name = "uart5_2_grp",
.pins = s900_uart5_2_pins,
.num_pins = ARRAY_SIZE(s900_uart5_2_pins),
+ .reginfo = &s900_uart5_2_reginfo,
},
};
...+static int s900_set_mux(struct pinctrl_dev *pctldev, unsigned selector,
+ unsigned group)
+{
+ int i;
+ uint32_t val;
+
+ struct owl_pinctrl_priv *priv = pctldev->driver_data;
+
+ const struct owl_pmx_reginfo *reginfo;
+
+ reginfo = s900_groups[gourp].reginfo;
+
+ for (i = 0; i < reginfo->num_entries; i++) {
+ val = readl(priv->mem_base + reginfo->offset[i]);
+ val &= ~reginfo->mask[i];
+ val |= reginfo->val[i];
+ writel(val, priv->mem_base + reginfo->offset[i]);
+ }
+
+ return 0;
+}
以上代码比较直接、简单,又和具体的硬件平台有关,我就不详细解释了,大家在编写自己平台的pinctrl driver时,可以具体情况具体对待。
以上可参考如下patch:https://github.com/wowotechX/linux/commit/52d6b8337a31267fd9b158d5f1f9d9590a16f92d。
2.7 dt_node_to_map的设计
上面的pinmux设计完成之后,我们需要提供一种机制,让consumer(这里为serial driver)可以方便的使用。这里介绍利用device tree的方法----步骤有三:
1)定义pinctrl device的dts node,并包含一个包含uart5 function的子节点(节点的格式可以自行决定,因为是自己解析),例如:
--- a/arch/arm64/boot/dts/actions/s900-bubblegum.dts + pinctrl@0xe01b0000 { |
这里子节点的名字为uart5_default(别名为uart5_state_default,以便consumer device的dts node引用),代表了一个pin state(相关概念可参考[3]中的介绍);
简单期间,该state里面包含一个entry----pinmux,该entry有两个关键字,function和group,都是字符串类型的。
2)在pinctrl driver中,实现.dt_node_to_map和.dt_free_map回调函数,其中.dt_node_to_map用于将dts node中的信息转换为pin map,示例如下:
+static int s900_dt_node_to_map(struct pinctrl_dev *pctldev, |
这个例子比较简单:
参数主要包括:np_config,dts节点指针,对应上面的“uart5_state_default”;map,pinctrl map指针的指针,需要pinctrl driver申请空间;num_maps,map个数的指针,driver可以修改该指针的值,告诉上层软件最终的map个数。
得到dts node指针之后,我们可以遍历该node下的所有子节点,并检查对应的子节点下是否有有效的function和group字符串,如果有,则代表找到了一个有效的entry,将相应的信息(function name和group name)保存在maps数组中的一个entry即可。
3)在对应的consumer的dts node中,引用上述的pinctrl state,如下:
serial5: serial@e012a000 { |
上面pinctrl-names指定为"defualt",这样可以在设备probe的时候由kernel自行获取相应的pinctrl state并使之生效。
然后在pinctrl-0字段中填入pinctrl state的地址:&uart5_state_default。
最后serial driver在probe的时候,将会按照[3]中介绍过程(probe-->devm_pinctrl_get or pinctrl_get-->create_pinctrl-->pinctrl_dt_to_map-->dt_to_map_one_config-->pctlops->dt_node_to_map-->s900_dt_node_to_map),将该设备对应的pinctrl state保存下来,并通过pinctrl_lookup_state查找名称为"default“的state,调用pinctrl_select_state,使其生效。
以上代码可参考如下patch:https://github.com/wowotechX/linux/commit/7c7b7b948b6f4eb25ef7926c89eb1199e07a7bc9。
3. 编译与调试
完成上面的移植工作之后,我们增加一些debug信息,并使能该driver的DEBUG宏,如下(具体可参考“https://github.com/wowotechX/linux/commit/3acb915db41a4e87defa8b4ea412d92feff62c57”):
--- a/drivers/pinctrl/pinctrl-owl.c static int s900_get_groups_count(struct pinctrl_dev *pctldev) |
然后编译、运行起来,得到如下的log:
[ 0.080093] DMA: preallocated 256 KiB pool for atomic allocations
[ 0.086499] clocksource: Switched to clocksource arch_sys_counter
[ 0.092437] Unpacking initramfs...
[ 0.177156] Freeing initrd memory: 1044K (ffffffc07feaf000 - ffffffc07ffb4000)
[ 0.182031] workingset: timestamp_bits=60 max_order=19 bucket_order=0
[ 0.188093] owl_pinctrl e01b0000.pinctrl: owl_pinctrl_probe
[ 0.193593] owl_pinctrl e01b0000.pinctrl: s900_get_functions_count
[ 0.199749] owl_pinctrl e01b0000.pinctrl: s900_get_fname, selector 0
[ 0.206093] owl_serial_init
[ 0.208874] owl_pinctrl e01b0000.pinctrl: s900_dt_node_to_map
[ 0.214562] owl_pinctrl e01b0000.pinctrl: child_cnt 1
[ 0.219593] owl_pinctrl e01b0000.pinctrl: got a pinmux entry: uart5-uart5_0_grp
[ 0.226874] owl_pinctrl e01b0000.pinctrl: s900_get_functions_count
[ 0.232999] owl_pinctrl e01b0000.pinctrl: s900_get_fname, selector 0
[ 0.239343] owl_pinctrl e01b0000.pinctrl: s900_get_groups, selector 0
[ 0.245749] owl_pinctrl e01b0000.pinctrl: s900_get_groups_count
[ 0.251656] owl_pinctrl e01b0000.pinctrl: s900_get_group_name, selector 0
[ 0.258406] owl_pinctrl e01b0000.pinctrl: s900_get_group_pins, selector 0
[ 0.265156] owl_pinctrl e01b0000.pinctrl: s900_set_mux, selector 0, group 0
[ 0.272093] owl_pinctrl e01b0000.pinctrl: offset 44, mask 1c000000, val 4000000
[ 0.279468] owl_pinctrl e01b0000.pinctrl: offset 44, mask 3800000, val 2000000
[ 0.286749] owl_serial e012a000.serial: owl_serial_probe
[ 0.292062] e012a000.serial: ttyS5 at MMIO 0xe012a000 (irq = 5, base_baud = 0) is a unknown
[ 0.300343] console [ttyS5] enabled
[ 0.300343] console [ttyS5] enabled
大功告成!!后续其它的功能,在需要的时候慢慢完善就行了~~
4. 总结
经过上面的步骤,我们开发了一个简单的pinctrl driver,并通过consumer的dts node将其绑定。这样consumer driver在probe的时候,会自动调用pinctrl subsystem提供的API,获取一个default state,并使其生效。
当然,本文所描述的pinctrl driver只算一个简单的demo(只有uart5有关的管脚),实际的项目开发过程中要比这复杂的多,但万变不离其宗,仅仅是代码量和编程技巧的差异而已。
5. 参考文档
[3] linux内核中的GPIO系统之(4):pinctrl驱动的理解和结
原创文章,转发请注明出处。蜗窝科技,www.wowotech.net。
标签: Linux Kernel driver porting pinctrl

功能
最新评论
- wangjing
写得太好了 - wangjing
写得太好了! - DRAM
圖面都沒辦法顯示出來好像掛點了。 - Simbr
bus至少是不是还有个subsystem? - troy
@testtest:只要ldrex-modify-strex... - gh
Linux 内核在 sparse 内存模型基础上实现了vme...
文章分类
随机文章
文章存档
- 2025年4月(5)
- 2024年2月(1)
- 2023年5月(1)
- 2022年10月(1)
- 2022年8月(1)
- 2022年6月(1)
- 2022年5月(1)
- 2022年4月(2)
- 2022年2月(2)
- 2021年12月(1)
- 2021年11月(5)
- 2021年7月(1)
- 2021年6月(1)
- 2021年5月(3)
- 2020年3月(3)
- 2020年2月(2)
- 2020年1月(3)
- 2019年12月(3)
- 2019年5月(4)
- 2019年3月(1)
- 2019年1月(3)
- 2018年12月(2)
- 2018年11月(1)
- 2018年10月(2)
- 2018年8月(1)
- 2018年6月(1)
- 2018年5月(1)
- 2018年4月(7)
- 2018年2月(4)
- 2018年1月(5)
- 2017年12月(2)
- 2017年11月(2)
- 2017年10月(1)
- 2017年9月(5)
- 2017年8月(4)
- 2017年7月(4)
- 2017年6月(3)
- 2017年5月(3)
- 2017年4月(1)
- 2017年3月(8)
- 2017年2月(6)
- 2017年1月(5)
- 2016年12月(6)
- 2016年11月(11)
- 2016年10月(9)
- 2016年9月(6)
- 2016年8月(9)
- 2016年7月(5)
- 2016年6月(8)
- 2016年5月(8)
- 2016年4月(7)
- 2016年3月(5)
- 2016年2月(5)
- 2016年1月(6)
- 2015年12月(6)
- 2015年11月(9)
- 2015年10月(9)
- 2015年9月(4)
- 2015年8月(3)
- 2015年7月(7)
- 2015年6月(3)
- 2015年5月(6)
- 2015年4月(9)
- 2015年3月(9)
- 2015年2月(6)
- 2015年1月(6)
- 2014年12月(17)
- 2014年11月(8)
- 2014年10月(9)
- 2014年9月(7)
- 2014年8月(12)
- 2014年7月(6)
- 2014年6月(6)
- 2014年5月(9)
- 2014年4月(9)
- 2014年3月(7)
- 2014年2月(3)
- 2014年1月(4)
发表评论: