概述
一、pinctrl子系统
分析基于 NXP
提供源码。
1、设备树分析
imx6ull.dtsi
文件中有以下节点:
iomuxc: iomuxc@020e0000 {
compatible = "fsl,imx6ul-iomuxc";
reg = <0x020e0000 0x4000>;
};
gpr: iomuxc-gpr@020e4000 {
compatible = "fsl,imx6ul-iomuxc-gpr",
"fsl,imx6q-iomuxc-gpr", "syscon";
reg = <0x020e4000 0x4000>;
};
iomuxc_snvs: iomuxc-snvs@02290000 {
compatible = "fsl,imx6ull-iomuxc-snvs";
reg = <0x02290000 0x10000>;
};
imx6ull-14x14-evk.dts
中有以下节点:
&iomuxc {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1>;
imx6ul-evk {
…… // 内容太多,只摘取部分数据
pinctrl_usdhc1: usdhc1grp {
fsl,pins = <
MX6UL_PAD_SD1_CMD__USDHC1_CMD 0x17059
MX6UL_PAD_SD1_CLK__USDHC1_CLK 0x10071
MX6UL_PAD_SD1_DATA0__USDHC1_DATA0 0x17059
MX6UL_PAD_SD1_DATA1__USDHC1_DATA1 0x17059
MX6UL_PAD_SD1_DATA2__USDHC1_DATA2 0x17059
MX6UL_PAD_SD1_DATA3__USDHC1_DATA3 0x17059
>;
};
pinctrl_usdhc1_100mhz: usdhc1grp100mhz {
fsl,pins = <
MX6UL_PAD_SD1_CMD__USDHC1_CMD 0x170b9
MX6UL_PAD_SD1_CLK__USDHC1_CLK 0x100b9
MX6UL_PAD_SD1_DATA0__USDHC1_DATA0 0x170b9
MX6UL_PAD_SD1_DATA1__USDHC1_DATA1 0x170b9
MX6UL_PAD_SD1_DATA2__USDHC1_DATA2 0x170b9
MX6UL_PAD_SD1_DATA3__USDHC1_DATA3 0x170b9
>;
};
pinctrl_usdhc1_200mhz: usdhc1grp200mhz {
fsl,pins = <
MX6UL_PAD_SD1_CMD__USDHC1_CMD 0x170f9
MX6UL_PAD_SD1_CLK__USDHC1_CLK 0x100f9
MX6UL_PAD_SD1_DATA0__USDHC1_DATA0 0x170f9
MX6UL_PAD_SD1_DATA1__USDHC1_DATA1 0x170f9
MX6UL_PAD_SD1_DATA2__USDHC1_DATA2 0x170f9
MX6UL_PAD_SD1_DATA3__USDHC1_DATA3 0x170f9
>;
};
…… // 内容太多,只摘取部分数据
}
}
&iomuxc_snvs {
pinctrl-names = "default_snvs";
pinctrl-0 = <&pinctrl_hog_2>;
imx6ul-evk {
…… // 内容太多,只摘取部分数据
ts_reset_pin: ts_reset_pin_mux {
fsl,pins = <
MX6ULL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x49
>;
};
…… // 内容太多,只摘取部分数据
}
}
pinctrl_usdhc1
、pinctrl_usdhc1_100mhz
和 pinctrl_usdhc1_200mhz
操作引脚相同,区别在于对相应引脚赋值不同。
在 imx6ull.dtsi
文件中定义节点,在 imx6ull-14x14-evk.dts
文件中追加内容。
2、PIN信息分析
通过以下引脚为例进行分析:
MX6UL_PAD_SD1_CMD__USDHC1_CMD 0x17059 // 0x17059 为引脚配置寄存器设置值
MX6UL_PAD_SD1_CMD__USDHC1_CMD
定义在 archarmbootdtsimx6ul-pinfunc.h
文件中定义,定义如下:
#define MX6UL_PAD_SD1_CMD__USDHC1_CMD 0x01BC 0x0448 0x0000 0x0 0x0
MX6UL_PAD_SD1_CMD__USDHC1_CMD
对应一个引脚的一种复用功能,0x01BC 0x0448 0x0000 0x0 0x0
这5
个数字的具体含义如下:
<mux_reg conf_reg input_reg mux_mode input_val>
mux_reg:复用配置寄存器偏移地址
conf_reg:引脚配置寄存器偏移地址
input_reg:输入配置寄存器偏移地址
mux_mode:复用配置寄存器值
input_val:输入配置寄存器值
3、pinctrl 驱动
设备树中的设备节点如何和驱动匹配?
通过 compatible
属性,相关驱动进行匹配。
static struct imx_pinctrl_soc_info imx6ul_pinctrl_info = {
.pins = imx6ul_pinctrl_pads, // pinmux子系统的Pad名称
.npins = ARRAY_SIZE(imx6ul_pinctrl_pads),
};
static struct imx_pinctrl_soc_info imx6ull_snvs_pinctrl_info = {
.pins = imx6ull_snvs_pinctrl_pads,
.npins = ARRAY_SIZE(imx6ull_snvs_pinctrl_pads),
.flags = ZERO_OFFSET_VALID,
};
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 */ }
};
imx6ul_pinctrl_of_match
结构体数组用于为设备树和驱动相匹配。imx6ul_pinctrl_of_match
结构体数组一共有两个兼容性字符串,分别为 “fsl,imx6ul-iomuxc”
和 “fsl,imx6ull-iomuxc-snvs”
。
static int imx6ul_pinctrl_probe(struct platform_device *pdev)
{
const struct of_device_id *match;
struct imx_pinctrl_soc_info *pinctrl_info;
match = of_match_device(imx6ul_pinctrl_of_match, &pdev->dev);
if (!match)
return -ENODEV;
pinctrl_info = (struct imx_pinctrl_soc_info *) match->data;
return imx_pinctrl_probe(pdev, pinctrl_info);
}
imx6ul_pinctrl_probe
为 pinctrl
处理函数。
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,
};
imx6ul_pinctrl_driver
为 struct platform_driver
数据类型,主要作用是将 imx6ul_pinctrl_of_match
和 imx6ul_pinctrl_probe
进行绑定。
static int __init imx6ul_pinctrl_init(void)
{
return platform_driver_register(&imx6ul_pinctrl_driver);
}
arch_initcall(imx6ul_pinctrl_init);
static void __exit imx6ul_pinctrl_exit(void)
{
platform_driver_unregister(&imx6ul_pinctrl_driver);
}
module_exit(imx6ul_pinctrl_exit);
imx6ul_pinctrl_init
函数主要作用是向系统注册 imx6ul_pinctrl_driver
结构体变量。arch_initcall
为系统初始化函数,arch_initcall(imx6ul_pinctrl_init)
在系统启动过程中被调用执行。
4、imx6ul_pinctrl_probe分析
函数调用:
imx6ul_pinctrl_probe
--> imx_pinctrl_probe
--> platform_get_resource // 从设备树中获取节点名中 @ 后的地址信息
--> devm_ioremap_resource // 将获取的物理地址转化为虚拟地址
--> imx_pinctrl_desc 初始化
--> imx_pinctrl_probe_dt // 设备树相关操作
--> pinctrl_register // 向系统注册 imx_pinctrl_desc
注:详细分析目前未完成。
二、gpio子系统
pinctrl
子系统重点是设置 PIN
(有的 SOC
叫做 PAD
)的复用和电气属性,如果 pinctrl
子系统将一个 PIN
复用为 GPIO
的话,那么接下来就要用到 gpio
子系 统了。gpio
子系统顾名思义,就是用于初始化 GPIO
并且提供相应的 API
函数,比如设置 GPIO
为输入输出,读取 GPIO
的值等。gpio
子系统的主要目的就是方便驱动开发者使用 gpio
,驱动 开发者在设备树中添加 gpio
相关信息,然后就可以在驱动程序中使用 gpio
子系统提供的 API
函数来操作 GPIO
,Linux
内核向驱动开发者屏蔽掉了 GPIO
的设置过程,极大的方便了驱动开发者使用 GPIO
。
关于 I.MX
系列 SOC
的 GPIO
控 制 器 绑 定 信 息 请 查 看 文 档 Documentation/devicetree/bindings/gpio/ fsl-imx-gpio.txt
。
1、设备树中的 gpio 信息
以 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19
引脚为例进行相关说明。
1、引脚复用
通过 iomuxc
相关节点进行查找,引脚相关配置。
&iomuxc {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1>; // 默认引脚复用 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_IO00__ANATOP_OTG1_ID 0x13058 /* USB_OTG1_ID */
>;
};
…… // 其他内容未复制
};
};
设置 UART1_RTS_B
这个 PIN
复用为 GPIO1_IO19
。
2、gpio操作
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19
作为 SD1
的 cd
引脚。
&usdhc1 {
pinctrl-names = "default", "state_100mhz", "state_200mhz";
pinctrl-0 = <&pinctrl_usdhc1>;
pinctrl-1 = <&pinctrl_usdhc1_100mhz>;
pinctrl-2 = <&pinctrl_usdhc1_200mhz>;
cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>; // 引脚比较特殊(iomuxc中会配置,其他引脚必须被显示调用)
keep-power-in-suspend;
enable-sdio-wakeup;
vmmc-supply = <®_sd1_vmmc>;
no-1-8-v;
status = "okay";
};
usdhc1
中引用 gpio1
节点。
gpio1: gpio@0209c000 {
compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
reg = <0x0209c000 0x4000>;
interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
通过 gpio1
中 compatible
查找相关驱动。fsl,imx6ul-gpio
无相关匹配驱动,fsl,imx35-gpio
有相关匹配驱动。
2、GPIO驱动程序
drivers/gpio/gpio-mxc.c
就是 I.MX6ULL
的 GPIO
驱动文件,在此文件中有如下所示 of_device_id
匹配表:
// IMX35_GPIO 等于 3
static struct platform_device_id mxc_gpio_devtype[] = {
{
.name = "imx1-gpio",
.driver_data = IMX1_GPIO,
}, {
.name = "imx21-gpio",
.driver_data = IMX21_GPIO,
}, {
.name = "imx31-gpio",
.driver_data = IMX31_GPIO,
}, {
.name = "imx35-gpio",
.driver_data = IMX35_GPIO,
}, {
/* sentinel */
}
};
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 */ }
};
mxc_gpio_dt_ids
结构体数组用于设备树和驱动进行匹配。
static int mxc_gpio_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct mxc_gpio_port *port;
struct resource *iores;
int irq_base;
int err;
mxc_gpio_get_hw(pdev);
port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL);
if (!port)
return -ENOMEM;
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);
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) {
/*
* Setup one handler for all GPIO interrupts. Actually setting
* the handler is needed only once, but doing it for every port
* is more robust and easier.
*/
irq_set_chained_handler(port->irq, mx2_gpio_irq_handler);
} else {
/* setup one handler for each entry */
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);
}
}
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.gc.to_irq = mxc_gpio_to_irq;
port->bgc.gc.base = (pdev->id < 0) ? of_alias_get_id(np, "gpio") * 32 :
pdev->id * 32;
err = gpiochip_add(&port->bgc.gc);
if (err)
goto out_bgpio_remove;
irq_base = irq_alloc_descs(-1, 0, 32, numa_node_id());
if (irq_base < 0) {
err = irq_base;
goto out_gpiochip_remove;
}
port->domain = irq_domain_add_legacy(np, 32, irq_base, 0,
&irq_domain_simple_ops, NULL);
if (!port->domain) {
err = -ENODEV;
goto out_irqdesc_free;
}
/* gpio-mxc can be a generic irq chip */
mxc_gpio_init_gc(port, irq_base);
list_add_tail(&port->node, &mxc_gpio_ports);
return 0;
out_irqdesc_free:
irq_free_descs(irq_base, 32);
out_gpiochip_remove:
gpiochip_remove(&port->bgc.gc);
out_bgpio_remove:
bgpio_remove(&port->bgc);
out_bgio:
dev_info(&pdev->dev, "%s failed with errno %dn", __func__, err);
return err;
}
mxc_gpio_probe
为 gpio
子系统处理函数。
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,
};
主要作用是将 mxc_gpio_dt_ids
和 mxc_gpio_probe
进行绑定。
static int __init gpio_mxc_init(void)
{
return platform_driver_register(&mxc_gpio_driver);
}
postcore_initcall(gpio_mxc_init);
向系统中注册 mxc_gpio_driver
。
pio-mxc.c
所在的目录为 drivers/gpio
,打开这 个目录可以看到很多芯片的 gpio
驱动文件, “gpiolib”
开始的文件是 gpio
驱动的核心文件。
3、gpiolib
保留
4、mxc_gpio_probe分析
保留
三、gpio 子系统API 函数
对于驱动开发人员,设置好设备树以后就可以使用 gpio
子系统提供的**API
函数来操作指定的 GPIO
**,gpio
子系统向驱动开发人员屏蔽了具体的读写寄存器过程。
注:详细内容未完成。
四、添加 pinctrl 节点模板
关于 I.MX
系列 SOC
的 pinctrl
设备树绑定信息可以参考文档 Documentation/devicetree/bindings/pinctrl/fsl,imx-pinctrl.txt
。
以正点原子 ALPHA
开发板进行测试。
1、查找引脚是否已经使用
LED
使用 GPIO1_IO03
引脚操作,在设备树 pinctrl
子系统相关配置中查找引脚是否使用。
1、查看 GPIO1_IO03
可复用功能:
2、在设备树中查找 GPIO1_IO03
引脚是否被使用。
3、通过查找 GPIO1_IO03
已经被使用,因此需要将其删除(设备树移植于官方开发板,因为在官方开发板中可能被用作其他用途,因此需要查找是否使用)。
&gpio1 3
在设置默认电平时需要配置,因此这个也需要注释。
2、创建节点
在 iomuxc
节点中添加 pinctrl_led: gpioledgrp
,用来表示 led
相关 pinctrl
子系统相关配置。
3、添加“fsl,pins”属性
4、添加 PIN 配置信息
五、添加 gpio 节点模板
1、创建节点
在设备树中创建操作 LED
,为了方便节点创建在根节点下。
2、引用 pinctrl 信息
3、添加GPIO属性信息
六、测试修改后设备树
1、编译
onlylove@ubuntu:~/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga$ make dtbs
CHK include/config/kernel.release
CHK include/generated/uapi/linux/version.h
CHK include/generated/utsrelease.h
make[1]: 'include/generated/mach-types.h' is up to date.
CHK include/generated/bounds.h
CHK include/generated/asm-offsets.h
CALL scripts/checksyscalls.sh
DTC arch/arm/boot/dts/imx6ull-lq-evk.dtb
onlylove@ubuntu:~/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga$
2、在系统中测试
/ # cd /sys/firmware/devicetree/base/
/sys/firmware/devicetree/base # ls -al
total 0
-r--r--r-- 1 0 0 4 Jan 1 00:01 #address-cells
-r--r--r-- 1 0 0 4 Jan 1 00:01 #size-cells
drwxr-xr-x 17 0 0 0 Jan 1 00:01 .
drwxr-xr-x 3 0 0 0 Jan 1 00:01 ..
drwxr-xr-x 2 0 0 0 Jan 1 00:01 aliases
drwxr-xr-x 2 0 0 0 Jan 1 00:01 backlight
drwxr-xr-x 2 0 0 0 Jan 1 00:01 chosen
drwxr-xr-x 6 0 0 0 Jan 1 00:01 clocks
-r--r--r-- 1 0 0 34 Jan 1 00:01 compatible
drwxr-xr-x 3 0 0 0 Jan 1 00:01 cpus
drwxr-xr-x 2 0 0 0 Jan 1 00:01 gpioled
drwxr-xr-x 2 0 0 0 Jan 1 00:01 interrupt-controller@00a01000
drwxr-xr-x 2 0 0 0 Jan 1 00:01 lq-led
drwxr-xr-x 2 0 0 0 Jan 1 00:01 memory
-r--r--r-- 1 0 0 36 Jan 1 00:01 model
-r--r--r-- 1 0 0 1 Jan 1 00:01 name
drwxr-xr-x 2 0 0 0 Jan 1 00:01 pxp_v4l2
drwxr-xr-x 5 0 0 0 Jan 1 00:01 regulators
drwxr-xr-x 3 0 0 0 Jan 1 00:01 reserved-memory
drwxr-xr-x 12 0 0 0 Jan 1 00:01 soc
drwxr-xr-x 2 0 0 0 Jan 1 00:01 sound
drwxr-xr-x 3 0 0 0 Jan 1 00:01 spi4
/sys/firmware/devicetree/base # random: nonblocking pool is initialized
/sys/firmware/devicetree/base # cd gpioled/
/sys/firmware/devicetree/base/gpioled # ls
compatible name pinctrl-names
led-gpio pinctrl-0 status
/sys/firmware/devicetree/base/gpioled # cat compatible
/sys/firmware/devicetree/base/gpioled # cat status
okay
/sys/firmware/devicetree/base/gpioled #
/sys/firmware/devicetree/base/gpioled #
/sys/firmware/devicetree/base/gpioled # cat compatible
gpioled
/sys/firmware/devicetree/base/gpioled #
通过以上日志可以确定 gpioled
节点解析成功。
七、与 gpio 相关的OF函数
保留
八、总结
1、在设备树中添加引脚复用相关配置(也就是 pinctrl
子系统)。
2、在设备树中添加节点信息,在节点中引用复用相关配置(通过节点 pinctrl-x
属性)。
3、在驱动中使用 gpio
子系统(通过 gpio
子系统相关 api
接口)操作 GPIO
引脚。
举例说明(以 usdhc1
为例):
引脚复用:
pinctrl_usdhc1: usdhc1grp {
fsl,pins = <
MX6UL_PAD_SD1_CMD__USDHC1_CMD 0x17059
MX6UL_PAD_SD1_CLK__USDHC1_CLK 0x10071
MX6UL_PAD_SD1_DATA0__USDHC1_DATA0 0x17059
MX6UL_PAD_SD1_DATA1__USDHC1_DATA1 0x17059
MX6UL_PAD_SD1_DATA2__USDHC1_DATA2 0x17059
MX6UL_PAD_SD1_DATA3__USDHC1_DATA3 0x17059
>;
};
pinctrl_usdhc1_100mhz: usdhc1grp100mhz {
fsl,pins = <
MX6UL_PAD_SD1_CMD__USDHC1_CMD 0x170b9
MX6UL_PAD_SD1_CLK__USDHC1_CLK 0x100b9
MX6UL_PAD_SD1_DATA0__USDHC1_DATA0 0x170b9
MX6UL_PAD_SD1_DATA1__USDHC1_DATA1 0x170b9
MX6UL_PAD_SD1_DATA2__USDHC1_DATA2 0x170b9
MX6UL_PAD_SD1_DATA3__USDHC1_DATA3 0x170b9
>;
};
pinctrl_usdhc1_200mhz: usdhc1grp200mhz {
fsl,pins = <
MX6UL_PAD_SD1_CMD__USDHC1_CMD 0x170f9
MX6UL_PAD_SD1_CLK__USDHC1_CLK 0x100f9
MX6UL_PAD_SD1_DATA0__USDHC1_DATA0 0x170f9
MX6UL_PAD_SD1_DATA1__USDHC1_DATA1 0x170f9
MX6UL_PAD_SD1_DATA2__USDHC1_DATA2 0x170f9
MX6UL_PAD_SD1_DATA3__USDHC1_DATA3 0x170f9
>;
};
设备树中关于 usdhc1
定义三种状态,如果都进行执行,一定会产生冲突。也就是说,以上只是进行定义,而未使用。
引脚配置:
&usdhc1 {
pinctrl-names = "default", "state_100mhz", "state_200mhz";
pinctrl-0 = <&pinctrl_usdhc1>;
pinctrl-1 = <&pinctrl_usdhc1_100mhz>;
pinctrl-2 = <&pinctrl_usdhc1_200mhz>;
cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
keep-power-in-suspend;
enable-sdio-wakeup;
vmmc-supply = <®_sd1_vmmc>;
no-1-8-v;
status = "okay";
};
设备树 usdhc1
节点中对 usdhc1
三种状态根据不同配置进行引用(使用)。
总结:引脚复用配置在使用时才会调用。
九、通过led进行测试
1、编写驱动
#include "linux/init.h"
#include "linux/module.h"
#include "linux/kdev_t.h"
#include "linux/fs.h"
#include "linux/cdev.h"
#include "linux/device.h"
#include "asm/io.h"
#include "asm/uaccess.h"
#include "linux/of.h"
#include "linux/of_address.h"
#include "linux/of_gpio.h"
#include "linux/gpio.h"
#define NEWCHRDEV_MAJOR 0 /* 主设备号(如果为0则让系统自动分配,如果大于0则使用指定设备号) */
#define NEWCHRDEV_MINOR 0 /* 次设备号 */
#define NEWCHRDEV_COUNT 1 /* 设备号个数 */
#define NEWCHRDEV_NAME "newchrdev" /* 名子 */
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */
/* 寄存器物理地址 */
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)
/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
typedef struct{
struct cdev dev; /* cdev 结构体 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
dev_t devid; /* 设备号 */
struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *nd; /* 设备树节点 */
int led_gpio; /* led 所使用的 GPIO 编号 */
}newchrdev_t;
newchrdev_t newchrdev;
/*
* @description : LED打开/关闭
* @param - sta : LEDON(0) 打开LED,LEDOFF(1) 关闭LED
* @return : 无
*/
void led_switch(u8 sta)
{
u32 val = 0;
if(sta == LEDON) {
val = readl(GPIO1_DR);
val &= ~(1 << 3);
writel(val, GPIO1_DR);
}else if(sta == LEDOFF) {
val = readl(GPIO1_DR);
val|= (1 << 3);
writel(val, GPIO1_DR);
}
}
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int led_open(struct inode *inode, struct file *filp)
{
printk("led_open!rn");
filp->private_data = &newchrdev; /* 设置私有数据 */
return 0;
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
printk("led_read!rn");
return 0;
}
/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int retvalue;
unsigned char databuf[1];
unsigned char ledstat;
printk("led_write!rn");
retvalue = copy_from_user(databuf, buf, cnt);
if(retvalue < 0) {
printk("kernel write failed!rn");
return -EFAULT;
}
ledstat = databuf[0]; /* 获取状态值 */
if(ledstat == LEDON) {
gpio_set_value(newchrdev.led_gpio,0); /* 打开LED灯 */
} else if(ledstat == LEDOFF) {
gpio_set_value(newchrdev.led_gpio,1); /* 关闭LED灯 */
}
return 0;
}
/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int led_release(struct inode *inode, struct file *filp)
{
printk("led_release!rn");
return 0;
}
static const struct file_operations newchrdevops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
/* 驱动入口函数 */
static int __init newchrdev_init(void)
{
/* 驱动入口函数具体内容 */
int ret;
struct property;
/* 1、字符设备号分配 */
newchrdev.major = NEWCHRDEV_MAJOR;
if(newchrdev.major){
newchrdev.minor = NEWCHRDEV_MINOR;
newchrdev.devid = MKDEV(newchrdev.major, newchrdev.minor);
ret = register_chrdev_region(newchrdev.devid,NEWCHRDEV_COUNT,NEWCHRDEV_NAME);
printk("newchrdev.major > 0!rn");
}else{
ret = alloc_chrdev_region(&newchrdev.devid,0,NEWCHRDEV_COUNT,NEWCHRDEV_NAME);
newchrdev.major = MAJOR(newchrdev.devid);
newchrdev.minor = MINOR(newchrdev.devid);
printk("newchrdev.major = 0!rn");
}
if(ret < 0){
printk("newchrdev xxx_chrdev_region failed!rn");
goto newchrdev_chrdev_region_failed;
}
printk("newchrdev devid = %d newchrdev major=%d,minor=%drn",newchrdev.devid,newchrdev.major,newchrdev.minor);
/* 2、注册字符设备 */
newchrdev.dev.owner = THIS_MODULE;
cdev_init(&newchrdev.dev,&newchrdevops);
ret = cdev_add(&newchrdev.dev,newchrdev.devid,NEWCHRDEV_COUNT);
if(ret < 0){
printk("newchrdev cdev_add failed!rn");
goto newchrdev_cdev_add_failed;
}
/* 3、创建类 */
newchrdev.class = class_create(THIS_MODULE,NEWCHRDEV_NAME);
if(IS_ERR(newchrdev.class)) {
printk("newchrdev class_create failed!rn");
goto newchrdev_class_create_failed;
}
/* 4、创建设备 */
newchrdev.device = device_create(newchrdev.class,NULL,newchrdev.devid,NULL,NEWCHRDEV_NAME);
if(IS_ERR(newchrdev.device)){
printk("newchrdev device_create failed!rn");
goto neschrdev_device_creat_failed;
}
/* 设置 LED 所使用的GPIO */
/* [1]、获取设备节点 gpioled */
newchrdev.nd = of_find_node_by_path("/gpioled");
if(newchrdev.nd == NULL){
printk("gpioled node cant not found!rn");
goto neschrdev_device_creat_failed;
}else{
printk("gpioled node has been found!rn");
}
/* [2]、获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */
newchrdev.led_gpio = of_get_named_gpio(newchrdev.nd, "led-gpio", 0);
if(newchrdev.led_gpio < 0){
printk("can't get led-gpio");
goto neschrdev_device_creat_failed;
}
printk("led-gpio num = %drn", newchrdev.led_gpio);
/* [3]、申请 IO */
ret = gpio_request(newchrdev.led_gpio,"gpio-led");
if(ret){
printk("newchrdev gpio_request failed!rn");
goto neschrdev_device_creat_failed;
}
/* [4]、设置 GPIO1_IO03 为输出 */
ret = gpio_direction_output(newchrdev.led_gpio, 1);
if(ret < 0) {
printk("can't set gpio!rn");
goto neschrdev_gpio_request_failed;
}
/*[5]、输出高电平,默认关闭 LED 灯*/
gpio_set_value(newchrdev.led_gpio,1);
printk("newchrdev_init succed!rn");
return 0;
neschrdev_gpio_request_failed:
gpio_free(newchrdev.led_gpio);
neschrdev_device_creat_failed:
class_destroy(newchrdev.class);
newchrdev_class_create_failed:
cdev_del(&newchrdev.dev);
newchrdev_cdev_add_failed:
unregister_chrdev_region(newchrdev.devid,NEWCHRDEV_COUNT);
newchrdev_chrdev_region_failed: /* 字符设备号分配失败处理函数(未分配资源,因此不做处理) */
printk("failed!rn");
return ret;
}
/* 驱动卸载函数 */
static void __exit newchrdev_exit(void)
{
/* 驱动卸载函数具体内容 */
/* 取消映射 */
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
/* 5、释放IO*/
gpio_free(newchrdev.led_gpio);
/* 4、删除设备 */
device_destroy(newchrdev.class,newchrdev.devid);
/* 3、删除类 */
class_destroy(newchrdev.class);
/* 2、注销字符设备 */
cdev_del(&newchrdev.dev);
/* 1、释放设备号 */
unregister_chrdev_region(newchrdev.devid,NEWCHRDEV_COUNT);
printk("newchrdev_exit succed!rn");
}
module_init(newchrdev_init);
module_exit(newchrdev_exit);
MODULE_LICENSE("GPL");
2、测试
/ # pwd
/
/ # ls
bin gpio-led.ko lib proc sys
dev led linuxrc root tmp
etc led_dts.ko mnt sbin usr
/ # lsmod
Module Size Used by Tainted: G
/ # insmod gpio-led.ko
newchrdev.major = 0!
newchrdev devid = 260046848 newchrdev major=248,minor=0
gpioled node has been found!
led-gpio num = 3
newchrdev_init succed!
/ # lsmod
Module Size Used by Tainted: G
gpio_led 2992 0
/ # ./led /dev/newchrdev
led_open!
led_write!
led_write!
led_write!
led_write!
led_write!
led_write!
led_write!
^Cled_release!
/ # rmmod gpio-led.ko
newchrdev_exit succed!
/ # lsmod
Module Size Used by Tainted: G
/ #
通过在开发板上测试,led
正常闪烁,驱动测试通过。
十、说明
知识积累不够,文章逻辑不够清晰,内容有可能不是很准确。后续进行完善。
最后
以上就是拉长宝贝为你收集整理的Linux 驱动开发 二十二:pinctrl子系统和gpio子系统分析一、pinctrl子系统二、gpio子系统三、gpio 子系统API 函数四、添加 pinctrl 节点模板五、添加 gpio 节点模板六、测试修改后设备树七、与 gpio 相关的OF函数八、总结九、通过led进行测试十、说明的全部内容,希望文章能够帮你解决Linux 驱动开发 二十二:pinctrl子系统和gpio子系统分析一、pinctrl子系统二、gpio子系统三、gpio 子系统API 函数四、添加 pinctrl 节点模板五、添加 gpio 节点模板六、测试修改后设备树七、与 gpio 相关的OF函数八、总结九、通过led进行测试十、说明所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复