Linux驱动开发实战(十):pinctrl子系统实验:RGB LED引脚初始化详解

发布于:2025-04-05 ⋅ 阅读:(141) ⋅ 点赞:(0)

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
                    >;
            };
  1. 引脚复用:
  • 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
  1. 电气特性参数 (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状态的配置
总的来说就是
在这里插入图片描述

二、驱动代码

代码总体框架

  1. 字符设备部分 - 提供用户空间访问接口
  2. 平台驱动部分 - 负责设备树解析和硬件初始化
  3. 资源管理结构体 - 保存寄存器映射和设备节点

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子系统的配置方法,理解电气参数对信号完整性的影响。建议结合示波器进行实际波形测量,加深对配置参数的理解。


网站公告

今日签到

点亮在社区的每一天
去签到