linux驱动讲究驱动分离和分层,即按照面向对象编程的思想来设计驱动框架,将设备抽象为一个对象结构体(结构体中包含该设备的信息,属性),驱动获取设备树中定义的信息,实现驱动操作
加载函数中的操作
(2) 使用pinctrl+gpio子系统下的驱动框架与没有使用的区别
首先设备树定义不一样
pinctrl子系统的使用 == 在设备树中通过定义pinctrl节点来实现MUX+PAD功能
操作流程, 在iomuxc节点下定义pinctrl节点,在该驱动设备对应的节点下引用该pinctrl节点
imx6ull.dtsi中的iomuxc节点
#include “imx6ull-pinfunc.h”
iomuxc: iomuxc@020e0000 {
compatible = "fsl,imx6ul-iomuxc";
reg = <0x020e0000 0x4000>;
};
imx6ull-alientek-emmc.dts文件中引用扩展iomuxc节点
#include “imx6ull.dtsi”
&iomuxc {
pinctrl-names = “default”;
pinctrl-0 = <&pinctrl_hog_1>;
imx6ul-evk {
pinctrl_hog_1: hoggrp-1 {
fsl,pins = <
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD /
MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059 / SD1 VSELECT /
/ MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059 SD1 RESET /
MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x13058 / USB_OTG1_ID */
>;
};
pinctrl_wjc_gpioled: wjcledgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /*LED0 MUX+PAD */
>;
};
}
==> 将其结合整理后如下
iomuxc: iomuxc@020e0000 {
compatible = “fsl,imx6ul-iomuxc”;
reg = <0x020e0000 0x4000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1>;
imx6ul-evk {
pinctrl_hog_1: hoggrp-1 {
fsl,pins = <
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD */
MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059 /* SD1 VSELECT */
/* MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059 SD1 RESET */
MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x13058 /* USB_OTG1_ID */
>;
};
pinctrl_wjc_gpioled: wjcledgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /*LED0 MUX+PAD */
>;
};
... ... ...
};
(3) 前面说pinctrl代替了前面的mux+pad功能,整理分析一下怎么代替
例如: MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 猜测: MX6UL_PAD_UART1_RTS_B__GPIO1_IO19实现mux功能,0x17059实现电气属性功能
~/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek$ grep -rn “MX6UL_PAD_UART1_RTS_B__GPIO1_IO19” ./
==>
./arch/arm/boot/dts/imx6ul-pinfunc.h:196:#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000 0x5 0x0
调用关系 在imx6ull.dtsi中包含了"imx6ull-pinfunc.h"头文件,而"imx6ull-pinfunc.h"头文件又包含了imx6ul-pinfunc.h头文件
在"imx6ull-pinfunc.h"中共定义了UART1_RTS_B引脚的8种复位情况,而在IMX6ULL参考文档中UART1_RTS_B引脚的可选复用IO就是这8种情况
#define MX6UL_PAD_UART1_RTS_B__UART1_DCE_RTS 0x0090 0x031C 0x0620 0x0 0x3
#define MX6UL_PAD_UART1_RTS_B__UART1_DTE_CTS 0x0090 0x031C 0x0000 0x0 0x0
#define MX6UL_PAD_UART1_RTS_B__ENET1_TX_ER 0x0090 0x031C 0x0000 0x1 0x0
#define MX6UL_PAD_UART1_RTS_B__USDHC1_CD_B 0x0090 0x031C 0x0668 0x2 0x1
#define MX6UL_PAD_UART1_RTS_B__CSI_DATA05 0x0090 0x031C 0x04CC 0x3 0x1
#define MX6UL_PAD_UART1_RTS_B__ENET2_1588_EVENT1_OUT 0x0090 0x031C 0x0000 0x4 0x0
#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000 0x5 0x0
#define MX6UL_PAD_UART1_RTS_B__USDHC2_CD_B 0x0090 0x031C 0x0674 0x8 0x2
分析宏定义格式
#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000 0x5 0x0
0x0090 0x031C 0x0000 0x5 0x0
mux_reg conf_reg input_reg mux_code input_val
mux_reg: mux_reg寄存器偏移地址 reg = <0x020e0000 0x4000>; ==> iomuxc节点的基地址为0x020e0000, 故MX6UL_PAD_UART1_RTS_B的基地址 = 0x020e0000 + 0x0090 (参考书中就是这个值) 这个pin的复用寄存器的地址
conf_reg: conf_reg寄存器偏移地址 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19这个复用之后的基地址 = 0x020e0000 + 0x031C ,conf_reg寄存器 == 表示该io的电气属性
input_reg: input_reg寄存器偏移地址 对于没有input_reg寄存器的设备,就不需要设置
mux_code:这里就相当于设置MX6UL_PAD_UART1_RTS_B这个io复用为MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 非常重要
input_val:input_reg寄存器中的值 没有input_reg寄存器就不需要设置
==>
故该宏定义相当于实现对xxxIO的复用设置
0x17059 == conf_reg寄存器的值,这里设置了该IO的上/下拉,驱动能力等
==>
替代了原本的Pad操作
(4) 分析pinctrl驱动
a. 通过compatible属性来找到匹配的驱动 compatible = “fsl,imx6ul-iomuxc”;
~/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek$ grep -rn ““fsl,imx6ul-iomuxc”” ./
==>
./drivers/pinctrl/freescale/pinctrl-imx6ul.c:327: { .compatible = “fsl,imx6ul-iomuxc”, .data = &imx6ul_pinctrl_info, },
b. 分析驱动文件
//加载驱动
static int __init imx6ul_pinctrl_init(void)
{
return platform_driver_register(&imx6ul_pinctrl_driver); // 从这里就知道使用了platform平台
}
//实现platform_driver对象
static struct platform_driver imx6ul_pinctrl_driver = {
.driver = {
.name = "imx6ul-pinctrl",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(imx6ul_pinctrl_of_match),
},
.probe = imx6ul_pinctrl_probe,
.remove = imx_pinctrl_remove,
};
//和设备树匹配表
static struct of_device_id imx6ul_pinctrl_of_match[] = {
{ .compatible = "fsl,imx6ul-iomuxc", .data = &imx6ul_pinctrl_info, },
{ .compatible = "fsl,imx6ull-iomuxc-snvs", .data = &imx6ull_snvs_pinctrl_info, },
{ /* sentinel */ }
};
//匹配成功,调用probe函数
static int imx6ul_pinctrl_probe(struct platform_device *pdev)
{
... ...
return imx_pinctrl_probe(pdev, pinctrl_info);
}
==>
int imx_pinctrl_probe(struct platform_device *pdev,struct imx_pinctrl_soc_info *info)
{
... ...
ret = imx_pinctrl_probe_dt(pdev, info);
}
==>
static int imx_pinctrl_probe_dt(struct platform_device *pdev,struct imx_pinctrl_soc_info *info)
{
... ...
for_each_child_of_node(np, child)
imx_pinctrl_parse_functions(child, info, i++);
return 0;
}
==>
static int imx_pinctrl_parse_functions(struct device_node *np,struct imx_pinctrl_soc_info *info,u32 index)
{
... ...
for_each_child_of_node(np, child) {
func->groups[i] = child->name;
grp = &info->groups[info->grp_index++];
imx_pinctrl_parse_groups(child, grp, info, i++);
}
return 0;
}
==>
static int imx_pinctrl_parse_groups(struct device_node *np,struct imx_pin_group *grp,struct imx_pinctrl_soc_info *info,u32 index)
{
... ...
/*
* the binding format is fsl,pins = ,
* do sanity check and calculate pins number
*/
list = of_get_property(np, "fsl,pins", &size); //==>这里就决定了我们在pinctrl中的表示io的属性的属性名称必须为"fsl,pins" ***非常关键 每个soc的这个属性名称不同
if (!list) {
dev_err(info->dev, "no fsl,pins property in node %s\n", np->full_name);
return -EINVAL;
}
... ...
for (i = 0; i < grp->npins; i++) {
... ...
//下面的5个赋值,就是配置指定的寄存器
pin_reg->mux_reg = mux_reg;
pin_reg->conf_reg = conf_reg;
pin->input_reg = be32_to_cpu(*list++);
pin->mux_mode = be32_to_cpu(*list++);
pin->input_val = be32_to_cpu(*list++);
/* SION bit is in mux register */
config = be32_to_cpu(*list++);
if (config & IMX_PAD_SION)
pin->mux_mode |= IOMUXC_CONFIG_SION;
pin->config = config & ~IMX_PAD_SION;
dev_dbg(info->dev, "%s: 0x%x 0x%08lx", info->pins[pin_id].name,
pin->mux_mode, pin->config);
}
return 0;
}
综上所述: pinctrl中宏定义配置的好处是:用户只需要提供要操作的io的名称和配置的参数即可,至于将参数配置到指定寄存器的操作函数会帮忙自动完成
pinctrl子系统重点是设置PIN的复用和电气属性
gpio子系统
当pinctrl定义的pin被复用为GPIO时,这时候就是gpio子系统开始作用了
通常pinctrl+gpio联合使用时,先将要操作的pin定义为pinctrl节点,然后在设备对应的节点中引用该pinctrt节点,然后定义gpio属性,该属性的值就是pinctrl复用的gpio的信息
(1) 联合使用的demo (imx6ull-alientek-emmc.dts)
先定义pinctrl节点信息
pinctrl_wjc_gpioled: wjcledgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /*LED0 MUX+PAD */
>;
};
在定义gpio节点信息
wjcgpioled {
#address-cells = <1>;
#size-cells = <1>;
compatible = “wjc-gpioled”;
pinctrl-names = “default”; /* 设置这个设备要引用的PINCTRL子系统的名称*/
pinctrl-0 = <&pinctrl_wjc_gpioled>; /* 该设备要引用的PINCTRL子系统的节点*/
wjcled-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>; /* 我们自定义gpio相关属性,属性值表示该pin的GPIO编号和极性*/
status = “okay”;
};
关键点: 这里必须保证我们使用的pin(pinctrl_wjc_gpioled + wjcled-gpio)是唯一的即只有在这个节点用到,不然会引起冲突
(2) gpio子系统设备树代码分析
soc: imx6ull.dtsi
/:{
soc {
aips1{
gpio1: gpio@0209c000 {
compatible = “fsl,imx6ul-gpio”, “fsl,imx35-gpio”;
reg = <0x0209c000 0x4000>;
interrupts =
gpio-controller; //表示gpio1节点是个GPIO控制器
#gpio-cells = <2>; //表示一共有两个cell,第一个cell为GPIO编号 例如&gpio1 3,第二个cell为GPIO的极性,例如GPIO_ACTIVE_LOW(表示低电平有效)
interrupt-controller;
#interrupt-cells = <2>;
};
}
}
}
(3) GPIO驱动分析
a. 根据compatible属性值寻找匹配的驱动
~/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek$ grep -rn “fsl,imx35-gpio” ./
==>
./drivers/gpio/gpio-mxc.c:156: { .compatible = “fsl,imx35-gpio”, .data = &mxc_gpio_devtype[IMX35_GPIO], },
b. 加载函数
postcore_initcall(gpio_mxc_init);
static int __init gpio_mxc_init(void)
{
return platform_driver_register(&mxc_gpio_driver); //显而易见是platform平台
}
//实现platform_driver对象
static struct platform_driver mxc_gpio_driver = {
.driver = {
.name = "gpio-mxc",
.of_match_table = mxc_gpio_dt_ids,
},
.probe = mxc_gpio_probe,
.id_table = mxc_gpio_devtype,
};
//实现设备树匹配表
static const struct of_device_id mxc_gpio_dt_ids[] = {
{ .compatible = "fsl,imx1-gpio", .data = &mxc_gpio_devtype[IMX1_GPIO], },
{ .compatible = "fsl,imx21-gpio", .data = &mxc_gpio_devtype[IMX21_GPIO], },
{ .compatible = "fsl,imx31-gpio", .data = &mxc_gpio_devtype[IMX31_GPIO], },
{ .compatible = "fsl,imx35-gpio", .data = &mxc_gpio_devtype[IMX35_GPIO], },
{ /* sentinel */ }
};
//匹配成功调用probe函数
static int mxc_gpio_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node; //设备节点
struct mxc_gpio_port *port; //mxc_gpio_port就是LMX6ULL GPIO的抽象
|
struct mxc_gpio_port {
struct list_head node;
void __iomem *base;
int irq;
int irq_high;
struct irq_domain *domain;
struct bgpio_chip bgc;
u32 both_edges;
};
struct resource *iores;
int irq_base;
int err;
mxc_gpio_get_hw(pdev); //获取硬件相关数据 result: mxc_gpio_hwdata == &imx35_gpio_hwdata && mxc_gpio_hwtype == hwtype ==IMX35_GPIO
|
static void mxc_gpio_get_hw(struct platform_device *pdev)
{
... ...
if (mxc_gpio_hwtype) {
/*
* The driver works with a reasonable presupposition,
* that is all gpio ports must be the same type when
* running on one soc.
*/
BUG_ON(mxc_gpio_hwtype != hwtype);
return;
}
if (hwtype == IMX35_GPIO)
mxc_gpio_hwdata = &imx35_gpio_hwdata; //这里IMX6ULL的硬件类型就是IMX35_GPIO
else if (hwtype == IMX31_GPIO)
mxc_gpio_hwdata = &imx31_gpio_hwdata;
else
mxc_gpio_hwdata = &imx1_imx21_gpio_hwdata;
mxc_gpio_hwtype = hwtype;
}
//申请一个GPIO对象的内存空间
port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL);
if (!port)
return -ENOMEM;
//获取地址资源 + 地址映射 == 设备的虚拟地址的基地址 == 可以操作该gpio设备的所有寄存器 reg = <0x0209c000 0x4000>;
iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
port->base = devm_ioremap_resource(&pdev->dev, iores);
if (IS_ERR(port->base))
return PTR_ERR(port->base);
//获取中断号 interrupts = ,;
port->irq_high = platform_get_irq(pdev, 1);
port->irq = platform_get_irq(pdev, 0);
if (port->irq < 0)
return port->irq;
/* disable the interrupt and clear the status */
//失能中断 + 清除中断标志(所有中断)
writel(0, port->base + GPIO_IMR);
writel(~0, port->base + GPIO_ISR);
if (mxc_gpio_hwtype == IMX21_GPIO) {
... ...
} else {
/* setup one handler for each entry */\
//请求中断,设置中断函数都是mx3_gpio_irq_handler
irq_set_chained_handler(port->irq, mx3_gpio_irq_handler);
irq_set_handler_data(port->irq, port);
if (port->irq_high > 0) {
/* setup handler for GPIO 16 to 31 */
irq_set_chained_handler(port->irq_high,
mx3_gpio_irq_handler);
irq_set_handler_data(port->irq_high, port);
}
}
//******* 非常关键 这里就是创造那些GPIO api函数的地方,gpio操作函数 == 对直接操作寄存器过程的封装 == 类似于前面的pinctrl中的宏
err = bgpio_init(&port->bgc, &pdev->dev, 4,
port->base + GPIO_PSR,
port->base + GPIO_DR, NULL,
port->base + GPIO_GDIR, NULL, 0);
if (err)
goto out_bgio;
/*
&port->bgc == struct bgpio_chip *bgc
struct bgpio_chip {
struct gpio_chip gc;
... ...
}
gpio_chip结构体就是抽象出来的GPIO控制器(里面包含了那些gpio操作函数)
struct gpio_chip {
const char *label;
struct device *dev;
struct module *owner;
struct list_head list;
int (*request)(struct gpio_chip *chip,
unsigned offset);
void (*free)(struct gpio_chip *chip,
unsigned offset);
int (*get_direction)(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);
... ...
}
port->base + GPIO_PSR
port->base: 前面获取到的gpio节点的基地址 reg = <0x0209c000 0x4000>; 0x0209c000的虚拟地址
GPIO_PSR
==>
#define GPIO_PSR (mxc_gpio_hwdata->psr_reg)
static struct mxc_gpio_hwdata *mxc_gpio_hwdata;
==>
在前面的mxc_gpio_get_hw函数中 mxc_gpio_hwdata = &imx35_gpio_hwdata;
==>
static struct mxc_gpio_hwdata imx35_gpio_hwdata = {
.dr_reg = 0x00,
.gdir_reg = 0x04,
.psr_reg = 0x08,
.icr1_reg = 0x0c,
.icr2_reg = 0x10,
.imr_reg = 0x14,
.isr_reg = 0x18,
.edge_sel_reg = 0x1c,
.low_level = 0x00,
.high_level = 0x01,
.rise_edge = 0x02,
.fall_edge = 0x03,
}
综上所述: port->base + GPIO_PSR == 0x0209c000的虚拟地址 + 0x08 ==>PSR寄存器的地址
*/
|
int bgpio_init(struct bgpio_chip *bgc, struct device *dev,unsigned long sz, void __iomem *dat, void __iomem *set,
void __iomem *clr, void __iomem *dirout, void __iomem *dirin,
unsigned long flags)
{
... ...
//不在跟了,这里就是将这些代表寄存器地址的参数赋值给bgc中对应的参数 ***非常关键 这里你就相当于把这个gpio的DR,GDIR,psr寄存器地址保存在bgc对象中
ret = bgpio_setup_io(bgc, dat, set, clr);
if (ret)
return ret;
ret = bgpio_setup_accessors(dev, bgc, flags & BGPIOF_BIG_ENDIAN,
flags & BGPIOF_BIG_ENDIAN_BYTE_ORDER);
if (ret)
return ret;
ret = bgpio_setup_direction(bgc, dirout, dirin);
if (ret)
return ret;
return ret;
}
综上所述: bgpio_chip对象中保存了该gpio的寄存器信息,又有gpio的操作函数(成员gc中定义的),所以只要得到bgc就可以对GPIO进行操作
port->bgc.gc.to_irq = mxc_gpio_to_irq;
port->bgc.gc.base = (pdev->id < 0) ? of_alias_get_id(np, "gpio") * 32 :
pdev->id * 32;
... ...
}
(4) GPIO子系统的API函数
关键点: 这里这些api函数都需要知道操作的gpio编号,而gpio编号是在设备树中,驱动开发者自定义的gpio属性中定义的,所以想使用api函数,首先要获取编号
demo如下:
gpioled.led_gpio = of_get_named_gpio(gpioled.nd,"wjcled-gpio",0);
gpioled.led_gpio: 获取到的gpio编号
gpioled.nd: 设备节点
"wjcled-gpio": 自定义的gpio属性的名称
0:下标,如果有多组gpio,这里是获取下标为0的那组gpio信息对应的gpio编号
扩展知识:
gpio_resquest()和gpio_resquest_one()的区别
gpio_resquest(): 申请了该gpio资源之后,该函数不会主动释放,需要我们手动执行gpio_free()来释放申请到的gpio资源,两个参数
gpio_resquest_one(): 申请并配置gpio资源一次,他与gpio_resquest函数相比还有个flag参数,来表示你想配置为(输入/输出/其他),函数最后会释放该gpio资源
为什么gpio_resquest_one函数怎么设计? 申请 -- 配置 -- 释放
原因: 释放了,别人才能使用这个资源,你释放了不是说前面的配置就失效了,那些配置还是存在的,除非别人申请之后重新配置才会改变这个gpio的配置