Linux 设备树下的LED驱动实验-基于正点原子IMX6ULL开发板

1 设备树 LED 驱动原理
 
上一个实验我们直接在驱动文件 newchrled.c 中定义有关 寄存器物理地址,然后使用 io_remap 函数进行内存映射,得到对应的虚拟地址,最后操作寄存 器对应的虚拟地址完成对 GPIO 的初始化。本节在上一个实验基础上完成,我们使用设备树来向 Linux 内核传递相关的寄存器物理地址, Linux 驱动文件使用 OF 函数从设备树中获取所需的属性值,然后使用获取到的属性值来初始化相关的 IO。本次实验重点内容如下:
①、在 imx6ull-alientek-emmc.dts 文件中创建相应的设备节点。
②、编写驱动程序(在新字符设备驱动实验基础上完成),获取设备树中的相关属性值。
③、使用获取到的有关属性值来初始化 LED 所使用的 GPIO。

2 硬件原理图分析

Linux 设备树下的LED驱动实验-基于正点原子IMX6ULL开发板_第1张图片
 
3 实验程序编写

3.1
修改设备树文件
 
在imx6ull-alientek-emmc.dts 文件根节点“/”下创建一个名为“alphaled”的子节点
	alphaled{
			#address-cells =<1>;
			#size-cells=<1>;
			compatible = "alientek,alphaled";
			status = "okay";
			reg =<0x020C406C 0x04 /*CCM_CCGR1_BASE*/
				  0x020E0068 0x04 /*SW_MUX_GPIO1_IO03_BASE*/
				  0X020E02F4 0x04 /*SW_PAD_GPIO1_IO03_BASE*/
				  0X0209C000 0x04 /*GPIO1_DR_BASE*/
				  0X0209C004 0x04>; /*GPIO1_GDIR_BASE*/
	};
设备树修改完成,重新编译 imx6ull-alientek-emmc.dts,得到 imx6ull-alientek-emmc.dtb
Linux 设备树下的LED驱动实验-基于正点原子IMX6ULL开发板_第2张图片
 

 将imx6ull-alientek-emmc.dtb拷贝至/home/znn/linux/tftpboot/ 目录下

 重启Linux内核,进入/proc/device-tree/ 查看“alphaled”这个节点

Linux 设备树下的LED驱动实验-基于正点原子IMX6ULL开发板_第3张图片

进入alphaled 目录中,查看属性文件,与我们设置的一致。


 

3.2 LED 灯驱动程序编写

本次实验在《新字符设备驱动实验》 newchrled.c 的基础上修改而来。新建工程,新建dtsled.c
输入如下内容。
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define DTSLED_CNT 1		 /*设备号个数*/
#define DTSLED_NAME "dtsled" /*设备名称*/

#define LEDOFF 0 /*关闭*/
#define LEDON 1	 /*打开*/

/*地址映射后的虚拟地址指针*/
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;

/*dtsled 设备结构体*/
struct dtsled_dev
{
	dev_t devid;			/*设备号*/
	struct cdev cdev;		/*字符设备*/
	struct class *class;	/*类*/
	struct device *device;	/*设备*/
	int major;				/*主设备号*/
	int minor;				/*次设备号*/
	struct device_node *nd; /*设备节点*/
};

struct dtsled_dev dtsled; /*led 设备*/

/*LED 灯打开/关闭 */
static void led_switch(u8 sta)
{
	u32 val = 0;
	if (sta == LEDON)
	{
		val = readl(GPIO1_DR);
		val &= ~(1 << 3); /*bit3清零,打开LED*/
		writel(val, GPIO1_DR);
	}
	else if (sta == LEDOFF)
	{
		val = readl(GPIO1_DR);
		val |= 1 << 3; /*bit3置1,关闭LED*/
		writel(val, GPIO1_DR);
	}
}

static int dtsled_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &dtsled;
	return 0;
}

static int dtsled_release(struct inode *inode, struct file *filp)
{
	struct dtsled_dev *dev = (struct dtsled_dev *)filp->private_data;
	return 0;
}

static ssize_t dtsled_write(struct file *filp, const char __user *buf,
							size_t count, loff_t *ppos)
{
	struct dtsled_dev *dev = (struct dtsled_dev *)filp->private_data;

	int retvalue;
	unsigned char databuf[1];

	retvalue = copy_from_user(databuf, buf, count);
	if (retvalue < 0)
	{
		return -EFAULT;
	}

	/*判断是开灯还是关灯*/
	led_switch(databuf[0]);

	return 0;
}

/*dtsled字符设备操作集*/
static struct file_operations dtsled_fops = {
	.owner = THIS_MODULE,
	.write = dtsled_write,
	.open = dtsled_open,
	.release = dtsled_release,
};

/*入口*/
static int __init dtsled_init(void)
{
	int ret = 0;
	const char *str;
	u32 regdata[10] = {0};
	u8 i = 0;
	unsigned int val = 0;

	/*注册字符设备*/
	/*1.申请设备号*/
	dtsled.major = 0; /*设备号由内核分配*/
	if (dtsled.major) /*定义了设备号*/
	{
		dtsled.devid = MKDEV(dtsled.major, 0);
		ret = register_chrdev_region(dtsled.devid, DTSLED_CNT, DTSLED_NAME);
	}
	else /*没有给定设备号*/
	{
		ret = alloc_chrdev_region(&dtsled.devid, 0, DTSLED_CNT, DTSLED_NAME);
		dtsled.major = MAJOR(dtsled.devid);
		dtsled.minor = MINOR(dtsled.devid);
	}

	if (ret < 0)
	{
		goto fail_devid;
	}

	/*2.添加字符设备*/
	dtsled.cdev.owner = THIS_MODULE;
	cdev_init(&dtsled.cdev, &dtsled_fops);
	ret = cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT);
	if (ret < 0)
	{
		goto fail_cdev;
	}

	/*3.自动创建设备节点*/
	dtsled.class = class_create(THIS_MODULE, DTSLED_NAME);
	if (IS_ERR(dtsled.class))
	{
		ret = PTR_ERR(dtsled.class);
		goto fail_class;
	}

	dtsled.device = device_create(dtsled.class, NULL, dtsled.devid, NULL, DTSLED_NAME);
	if (IS_ERR(dtsled.device))
	{
		ret = PTR_ERR(dtsled.device);
		goto fail_device;
	}




	/*获取设备节点*/
	dtsled.nd = of_find_node_by_path("/alphaled");
	if (dtsled.nd == NULL)
	{ /*失败*/
		ret = -EINVAL;
		goto fail_findnd;
	}

	/*获取compatible属性*/
	ret = of_property_read_string(dtsled.nd, "compatible", &str);
	if (ret < 0)
	{
		goto fail_rs;
	}
	else
	{
		printk("compatible=%s\r\n", str);
	}

#if 0
	ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 10);
	if (ret < 0)
	{
		goto fail_rs;
	}
	else
	{
		printk("reg data:\r\n");
		for (i = 0; i < 10; i++)
		{
			printk("%#x ", regdata[i]);
		}
		printk("\r\n");
	}

	/*LED灯初始化*/
	/*1.初始化LED,地址映射*/
	
	IMX6U_CCM_CCGR1 = ioremap(regdata[0], regdata[1]);
	SW_MUX_GPIO1_IO03 = ioremap(regdata[2], regdata[3]);
	SW_PAD_GPIO1_IO03 = ioremap(regdata[4], regdata[5]);
	GPIO1_DR = ioremap(regdata[6], regdata[7]);
	GPIO1_GDIR = ioremap(regdata[8], regdata[9]);
#endif
    /* 一次性完成读取 reg 属性以及内存映射 */
	IMX6U_CCM_CCGR1 = of_iomap(dtsled.nd, 0);
	SW_MUX_GPIO1_IO03 = of_iomap(dtsled.nd, 1);
	SW_PAD_GPIO1_IO03 = of_iomap(dtsled.nd, 2);
	GPIO1_DR = of_iomap(dtsled.nd, 3);
	GPIO1_GDIR = of_iomap(dtsled.nd, 4);

	/*初始化GPIO1_IO03*/
	val = readl(IMX6U_CCM_CCGR1);
	val &= ~(3 << 26); /*先清除以前的配置bit26,27*/
	val |= 3 << 26;	   /*bit26,27置1*/
	writel(val, IMX6U_CCM_CCGR1);

	writel(0x5, SW_MUX_GPIO1_IO03);	   /*设置复用*/
	writel(0x10B0, SW_PAD_GPIO1_IO03); /*设置电气属性*/

	val = readl(GPIO1_GDIR);
	val |= 1 << 3; /*bit3置1,设置为输出*/
	writel(val, GPIO1_GDIR);

	val = readl(GPIO1_DR);
	val |= 1 << 3; /*bit3置1,关闭LED*/
	writel(val, GPIO1_DR);

	return 0;

fail_rs:

fail_findnd:
	device_destroy(dtsled.class, dtsled.devid);
fail_device:
	class_destroy(dtsled.class);
fail_class:
	cdev_del(&dtsled.cdev);
fail_cdev:
	unregister_chrdev_region(dtsled.devid, DTSLED_CNT);
fail_devid:

	return ret;
}

/*出口*/
static void __exit dtsled_exit(void)
{
	unsigned int val = 0;
	val = readl(GPIO1_DR);
	val |= 1 << 3; /*bit3置1,打开LED*/
	writel(val, GPIO1_DR);

	/*1.取消地址映射*/
	iounmap(IMX6U_CCM_CCGR1);
	iounmap(SW_MUX_GPIO1_IO03);
	iounmap(SW_PAD_GPIO1_IO03);
	iounmap(GPIO1_DR);
	iounmap(GPIO1_GDIR);

	/*删除字符设备*/
	cdev_del(&dtsled.cdev);

	/*释放设备号*/
	unregister_chrdev_region(dtsled.devid, DTSLED_CNT);

	/*摧毁设备*/
	device_destroy(dtsled.class, dtsled.devid);
	/*摧毁类*/
	class_destroy(dtsled.class);
}

/*注册驱动和卸载驱动*/
module_init(dtsled_init);
module_exit(dtsled_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("supersmart");
新增以下内容:
1、在设备结构体 dtsled_dev 中添加了成员变量 nd,nd 是 device_node 结构体类型指
针,表示设备节点。如果我们要读取设备树某个节点的属性值,首先要先得到这个节点,一般
在设备结构体中添加 device_node 指针变量来存放这个节点。
2、通过 of_find_node_by_path 函数得到 alphaled 节点,后续其他的 OF 函数要使用 device_node。
3、通过of_property_read_string函数获取 alphaled 节点的compatible 属性值。
4、使用 of_iomap 函数一次性完成读取 reg 属性以及内存映射。




3.3 编写测试 APP

《新字符设备驱动实验》测试APP一样:
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/*
 *argc:应用程序参数个数
 * argv[]:具体的参数内容,字符串形式
 * ./ledAPP  <0:1> 0 关灯,1 开灯
 * ./ledAPP /dev/dtsled 0 关灯
 * ./ledAPP /dev/dtsled 1 开灯
 */

#define LEDOFF 0
#define LEDON 1
int main(int argc, char *argv[])
{
    int fd, retvalue;
    char *filename;
    unsigned char databuf[1];

    if (argc != 3)
    {
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];

    fd = open(filename, O_RDWR);
    if (fd < 0)
    {
        printf("file %s open failed!\r\n", filename);
        return -1;
    }

    databuf[0] = atoi(argv[2]); /*将字符转化为数字*/
    retvalue = write(fd, databuf, sizeof(databuf));
    if (retvalue < 0)
    {
        printf("LED Control Failed ! \r\n");
        close(fd);
        return -1;
    }
    
    close(fd);

    return 0;
}
4 运行测试

4.1 编译驱动程序和测试 APP
1 、编译驱动程序
编写 Makefile 文件
KERNELDIR := /home/znn/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENT_PAHT := $(shell pwd)
obj-m := dtsled.o

build :kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PAHT) modules

clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PAHT) clean

编译驱动模块
Linux 设备树下的LED驱动实验-基于正点原子IMX6ULL开发板_第4张图片

 注意:warnning可忽略

2、编译测试 APP
 

arm-linux-gnueabihf-gcc ledApp.c -o ledApp


编译得到ledAPP 这个应用程序

 
4.2 运行测试
1、将dtsled.ko ledApp 这两个文件拷贝到 rootfs/lib/modules/4.1.15 目录 中。

2、重启开发板,进入到目录 lib/modules/4.1.15 中,加载 dtsled.ko 驱动模块。

3、使用 ledAPP 软件来测试驱动是否工作正常

打开led

Linux 设备树下的LED驱动实验-基于正点原子IMX6ULL开发板_第5张图片

关闭led


Linux 设备树下的LED驱动实验-基于正点原子IMX6ULL开发板_第6张图片

本文中红色加粗部分为重点学习内容。

你可能感兴趣的:(arm,linux,vscode,功能测试,c++)