Linux驱动开发实战(十):pinctrl子系统实验:RGB LED引脚初始化详解
文章目录
前言
前文都是对pinctrl内核实现的解析,希望学内核裁剪的可以多看几遍,本文是对pinctrl如何初始化rgb进行解释,教大家如何运行pinctrl子系统
一、设备树配置解析
引脚复用配置
pinctrl_rgb_led:rgb_led{
fsl,pins = <
/* 格式:<引脚复用宏> <电气参数> */
MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0x000010B1
MX6UL_PAD_CSI_HSYNC__GPIO4_IO20 0x000010B1
MX6UL_PAD_CSI_VSYNC__GPIO4_IO19 0x000010B1
>;
};
- 引脚复用:
- MX6UL_PAD_GPIO1_IO04__GPIO1_IO04
将GPIO1_IO04配置为GPIO模式 - MX6UL_PAD_CSI_HSYNC__GPIO4_IO20
将CSI_HSYNC引脚复用为GPIO4_IO20 - MX6UL_PAD_CSI_VSYNC__GPIO4_IO19
将CSI_VSYNC引脚复用为GPIO4_IO19
- 电气特性参数 (0x000010B1):
寄存器值: 0x000010B1 = 0000 0000 0000 0000 0001 0000 1011 0001
31 — 24 | 23 — 16 | 15 — 8 | 7 — 0 |
---|---|---|---|
0000 0000 | 0000 0000 | 0001 0000 | 1011 0001 |
有效配置位域分布
字段 | 位置 | 描述 |
---|---|---|
SRE | bit16 | 压摆率控制 |
DSE | 14-12 | 驱动强度控制 |
SPEED | 11-10 | 速度控制 |
PUS | 6-4 | 上拉/下拉选择 |
PUE | bit3 | 上拉/下拉使能 |
HYS | bit0 | 滞后使能 |
#define PAD_CTL_HYS_ENABLE (1 << 0) 0x00000001 滞回使能
#define PAD_CTL_PUS_100K_UP (1 << 4) 0x00000010 100KΩ上拉
#define PAD_CTL_SPEED_MED (2 << 10) 0x00000800 高速模式
#define PAD_CTL_DSE_40OHM (5 << 12) 0x00005000 40Ω驱动强度
#define PAD_CTL_SRE_SLOW (0 << 16) 0x00000000 慢转换率
左移计算拓展
注:
(1 << 0)<<表示左移运算。(1 << n)的含义是将数字1的二进制形式向左移动n位,右边空出的位用0填充。
(1
<< n) = 2
^n
(1 << 3) = 8 → 二进制1000
(1 << 5) = 32 → 二进制100000
那(2 << 10)该怎么算
为什么用2而不是1:
在i.MX6ULL的电气参数配置中,SPEED字段占2个bit(bit10和bit11)
:
- 00:低速(100MHz)
- 01:中速(100-200MHz)
- 10:高速(200MHz)
- 11:最大速度
明明是10为什么是两位呢
其实两位组合的数值是通过(bit11 << 1) | bit10
bit11=1, bit10=0 → 组合值=2
所以
(2 << 10) = 2 * (2^10) = 2 * 1024 = 2048 = 0x800
在根节点添加设备节点定义引脚状态
rgb_led{
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_rgb_led>;
#address-cells = <1>;
#size-cells = <1>;
compatible = "fire,rgb_led";
/*红灯节点*/
ranges;
rgb_led_red@0x020C406C{
reg = <0x020C406C 0x00000004
0x020E006C 0x00000004
0x020E02F8 0x00000004
0x0209C000 0x00000004
0x0209C004 0x00000004>;
status = "okay";
};
/*绿灯节点*/
rgb_led_green@0x020C4074{
reg = <0x020C4074 0x00000004
0x020E01E0 0x00000004
0x020E046C 0x00000004
0x020A8000 0x00000004
0x020A8004 0x00000004>;
status = "okay";
};
/*蓝灯节点*/
rgb_led_blue@0x020C4074{
reg = <0x020C4074 0x00000004
0x020E01DC 0x00000004
0x020E0468 0x00000004
0x020A8000 0x00000004
0x020A8004 0x00000004>;
status = "okay";
};
};
状态机控制逻辑:
pinctrl-names = “default”, “sleep”; 状态名称声明顺序
pinctrl-0 → 对应default状态的配置
pinctrl-1 → 对应sleep状态的配置
总的来说就是
二、驱动代码
代码总体框架
- 字符设备部分 - 提供用户空间访问接口
- 平台驱动部分 - 负责设备树解析和硬件初始化
- 资源管理结构体 - 保存寄存器映射和设备节点
LED资源结构体
struct led_resource
{
struct device_node *device_node; //rgb_led_red的设备树节点
void __iomem *virtual_CCM_CCGR;
void __iomem *virtual_IOMUXC_SW_MUX_CTL_PAD;
void __iomem *virtual_IOMUXC_SW_PAD_CTL_PAD;
void __iomem *virtual_DR;
void __iomem *virtual_GDIR;
};
这个结构体为每个LED保存了设备树节点和关键寄存器的虚拟地址映射。实例化了三个结构体变量:
struct led_resource led_red;
struct led_resource led_green;
struct led_resource led_blue;
平台驱动
匹配表
static const struct of_device_id rgb_led[] = {
{.compatible = "fire,rgb_led"},
{/* sentinel */}
};
平台驱动结构体
struct platform_driver led_platform_driver = {
.probe = led_probe,
.driver = {
.name = "rgb-leds-platform",
.owner = THIS_MODULE,
.of_match_table = rgb_led,
}
};
获取设备树资源(重要)
static int led_probe(struct platform_device *pdv)
{
int ret = -1; //保存错误状态码
unsigned int register_data = 0;
printk(KERN_ALERT "\t match successed \n");
/*获取rgb_led的设备树节点*/
rgb_led_device_node = of_find_node_by_path("/rgb_led");
if (rgb_led_device_node == NULL)
{
printk(KERN_ERR "\t get rgb_led failed! \n");
return -1;
}
/*获取rgb_led节点的红灯子节点*/
led_red.device_node = of_find_node_by_name(rgb_led_device_node,"rgb_led_red");
if (led_red.device_node == NULL)
{
printk(KERN_ERR "\n get rgb_led_red_device_node failed ! \n");
return -1;
}
/*获取 reg 属性并转化为虚拟地址*/
led_red.virtual_CCM_CCGR = of_iomap(led_red.device_node, 0);
led_red.virtual_IOMUXC_SW_MUX_CTL_PAD = of_iomap(led_red.device_node, 1);
led_red.virtual_IOMUXC_SW_PAD_CTL_PAD = of_iomap(led_red.device_node, 2);
led_red.virtual_DR = of_iomap(led_red.device_node, 3);
led_red.virtual_GDIR = of_iomap(led_red.device_node, 4);
// 类似的代码也用于获取绿色和蓝色LED的节点和地址映射
// ...
/*---------------------注册 字符设备部分-----------------*/
// 字符设备注册代码
// ...
return 0;
}
字符设备操作
字符设备打开操作
static int led_chr_dev_open(struct inode *inode, struct file *filp)
{
printk("\n open form driver \n");
return 0;
}
字符设备写操作
static ssize_t led_chr_dev_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int ret,error;
unsigned int register_data = 0; //暂存读取得到的寄存器数据
unsigned char receive_data[10]; //用于保存接收到的数据
unsigned int write_data; //用于保存接收到的数据
if(cnt>10)
cnt =10;
error = copy_from_user(receive_data, buf, cnt);
if (error < 0)
{
return -1;
}
ret = kstrtoint(receive_data, 16, &write_data);
if (ret) {
return -1;
}
/*设置 GPIO1_04 输出电平*/
if (write_data & 0x04)
{
register_data = ioread32(led_red.virtual_DR);
register_data &= ~(0x01 << 4);
iowrite32(register_data, led_red.virtual_DR); // GPIO1_04引脚输出低电平,红灯亮
}
else
{
register_data = ioread32(led_red.virtual_DR);
register_data |= (0x01 << 4);
iowrite32(register_data, led_red.virtual_DR); // GPIO1_04引脚输出高电平,红灯灭
}
// 类似的代码用于控制绿色和蓝色LED
// ...
return cnt;
}
字符设备操作函数集
static struct file_operations led_chr_dev_fops =
{
.owner = THIS_MODULE,
.open = led_chr_dev_open,
.write = led_chr_dev_write,
};
硬件控制逻辑
对每个GPIO的控制逻辑(以红色LED为例):
/*设置 GPIO1_04 输出电平*/
if (write_data & 0x04)
{
register_data = ioread32(led_red.virtual_DR);
register_data &= ~(0x01 << 4);
iowrite32(register_data, led_red.virtual_DR); // GPIO1_04引脚输出低电平,红灯亮
}
else
{
register_data = ioread32(led_red.virtual_DR);
register_data |= (0x01 << 4);
iowrite32(register_data, led_red.virtual_DR); // GPIO1_04引脚输出高电平,红灯灭
}
初始化和清理
驱动初始化函数
static int __init led_platform_driver_init(void)
{
int DriverState;
DriverState = platform_driver_register(&led_platform_driver);
printk(KERN_ALERT "\tDriverState is %d\n", DriverState);
return 0;
}
驱动注销函数
static void __exit led_platform_driver_exit(void)
{
/*取消物理地址映射到虚拟地址*/
iounmap(led_green.virtual_CCM_CCGR);
iounmap(led_green.virtual_IOMUXC_SW_MUX_CTL_PAD);
iounmap(led_green.virtual_IOMUXC_SW_PAD_CTL_PAD);
iounmap(led_green.virtual_DR);
iounmap(led_green.virtual_GDIR);
// 同样释放红色和蓝色LED的映射
// ...
/*删除设备*/
device_destroy(class_led, led_devno); //清除设备
class_destroy(class_led); //清除类
cdev_del(&led_chr_dev); //清除设备号
unregister_chrdev_region(led_devno, DEV_CNT); //取消注册字符设备
/*注销字符设备*/
platform_driver_unregister(&led_platform_driver);
printk(KERN_ALERT "led_platform_driver exit!\n");
}
模块入口和出口注册
module_init(led_platform_driver_init);
module_exit(led_platform_driver_exit);
MODULE_LICENSE("GPL");
字符设备注册部分
在probe函数中的字符设备注册过程:
//第一步
//采用动态分配的方式,获取设备编号,次设备号为0,
//设备名称为rgb-leds,可通过命令cat /proc/devices查看
//DEV_CNT为1,当前只申请一个设备编号
ret = alloc_chrdev_region(&led_devno, 0, DEV_CNT, DEV_NAME);
if (ret < 0)
{
printk("fail to alloc led_devno\n");
goto alloc_err;
}
//第二步
//关联字符设备结构体cdev与文件操作结构体file_operations
led_chr_dev.owner = THIS_MODULE;
cdev_init(&led_chr_dev, &led_chr_dev_fops);
//第三步
//添加设备至cdev_map散列表中
ret = cdev_add(&led_chr_dev, led_devno, DEV_CNT);
if (ret < 0)
{
printk("fail to add cdev\n");
goto add_err;
}
//第四步
/*创建类 */
class_led = class_create(THIS_MODULE, DEV_NAME);
/*创建设备*/
device = device_create(class_led, NULL, led_devno, NULL, DEV_NAME);
实验操作
配置设备树
iomuxc节点添加引脚配置信息
在根节点下添加引脚状态与引脚的信息
终端命令:
配置内核:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- npi_v7_defconfig
编译dts
sudo make ARCH=arm -j4 CROSS_COMPILE=arm-linux-gnueabihf- dtbs
把设备树复制到共享文件夹
sudo cp arch/arm/boot/dts/imx6ull-mmc-npi.dtb /../home/embedfire/workdir/imx6ull-mmc-npi.dtb
移植设备树
挂载nfs服务器
sudo mount -t nfs 192.168.155.118:/home/embedfire/workdir /mnt
覆盖原本的设备树
sudo cp /mnt/imx6ull-mmc-npi.dtb /usr/lib/linux-image-4.19.35-imx6/imx6ull-mmc-npi.dtb
重启
编译驱动
点灯
查看设备树
ls /proc/device-tree
有rgb_led的
加载驱动
sudo insmod dts_led.ko
用应用程序点灯
总结
通过本实验可掌握i.MX6ULL平台下pinctrl子系统的配置方法,理解电气参数对信号完整性的影响。建议结合示波器进行实际波形测量,加深对配置参数的理解。