I.MX6U嵌入式Linux驱动开发(4)设备树下led驱动实验

在Ubuntu中:

cd IMX6ULL/Linux_Driver/
mkdir 5_dtsled
cp 4_dtsof/ * 5_dtsled/ -rf
cp 4_dtsof/.vscode 5_dtsled/ -rf
cd 5_dtsled/
ls
rm dtsof.code-workspace
mv dtsof.c dtsled.c
cd ..
cp 3_newchrled/ledAPP.c ./5_dtsled/

1、修改设备树文件

打开设备树源码,找到im6ull-alientek-emmc.dts添加设备节点。最好把设备节点添加到一级节点的最下面。在根节点“/”下创建一个名为“alphaled”的子节点。

alphaled{
	#address-cells = <1>;
	#size-cells = <1>;
	campatible = "alitentek, 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*/
};

属性#address-cells 和#size-cells 都为 1,表示 reg 属性中起始地址占用一个字长(cell),地址长度也占用一个字长(cell)。
reg 属性设置了驱动里面所要使用的寄存器物理地址。“0X020C406C 0X04”表示 I.MX6ULL 的 CCM_CCGR1 寄存器,其中寄存器首地址为 0X020C406C,长度为 4 个字节。

验证:

make dtbs
cp arch/arm/boot/dts/imx6ull-alientek=emmc.dtb /home/yang/linux/tftpboot/ -f

启动开发板:

/ # cd /proc/device-tree/
/sys/firmware/devicetree/base # ls
/sys/firmware/devicetree/base # cd alphaled/

2、编写驱动框架

使用vscode打开5_dtsled,修改Makefile文件,将其修改为:dtsled.o。
打开dtsof.c文件,只留取头文件内容,把剩余的都删掉。

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

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

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;
	//下面就可以直接访问dev
	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;
}

/* dtsled字符设备操作集合 */
static const 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;
	
	/* 注册字符设备 */
	/* 1、申请设备号 */
	dtsled.major = 0;/* 设备号由内核分配 */
	if(dtsled.najor){/* 定义了设备号 */
		dtsled.devid = MKDEV(dtsled.major, 0);
		ret = register_chrdev_region(dtsled.devid, DTSLED_CNT, DTSLED_NAME);
	}else{ /* 没有给定设备号 */
		ret = alloc_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.dev, &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;
	}

	rerutn 0;
fail_device:
	class_destory(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)
{
	/* 删除字符设备 */
	cdev_del(&dtsled.cdev);
	/* 释放设备号 */	
	unregister_chrdev_region(dtsled.devid, DTSLED_CNT);
	/* 摧毁设备 */
	device_destroy(dstled.class, dtsled.devid);
	/* 摧毁类 */
	class_destroy(dstled.class);
}

/* 注册驱动和卸载驱动 */
module_init(dtsled_init);
module_exit(dtsled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("yang");

测试:

make
sudo cp dtsled.ko /home/yang/linux/nfs/rootfs/lib/modules/4.1.15 -f

开发板上电:

/lib/modules/4.1.15/ # ls //查看newchrled.ko是否存在
/lib/modules/4.1.15/ # depmod
/lib/modules/4.1.15/ # modprobe dtsled.ko
/lib/modules/4.1.15/ # ls /dev/
/lib/modules/4.1.15/ # lsmod
/lib/modules/4.1.15/ # ls /proc/devices
/lib/modules/4.1.15/ # cat /proc/devices

3、完善驱动框架

获取设备树属性内容,写入入口函数中。首先要添加一个节点,在结构体dtsled_dev中,struct device_node *nd;
获取设备树属性内容,使用函数of_find_node_by_path(),返回值保存到nd中。若获取失败,意味着前面的都已经做好了,就先把设备给摧毁掉。

const char *str;
u32 regdata[10];
u8 i = 0;

/*4、 获取设备树属性内容 */
dtsled.nd = of_find_node_by_path("/alphaled");
if(dtsled.nd == NULL){
	ret = -EINVAL;
	goto fail_findnd;
}

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

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

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");
}

fail_rs:
fail_findnd:
	device_destroy(dstled.class, dtsled.devid);

编译:

make
sudo cp dtsled.ko /home/yang/linux/nfs/rootfs/lib/modules/4.1.15 -f

开发板上电:

/lib/modules/4.1.15/ # ls //查看newchrled.ko是否存在
/lib/modules/4.1.15/ # modprobe dtsled.ko
/lib/modules/4.1.15/ # rmmod dtsled.ko

要多测几次驱动加载和卸载,如果加载不成功,说明驱动写的有问题,哪个地方忘了释放。

下面就开始初始化led灯,将内存映射的几句代码放到前面,

	/* 5、led灯初始化 */
	/* 初始化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]);

在dtsled_exit()中要释放内存映射

		iounmap(IMX6U_CCM_CCGR1);
		iounmap(SW_MUX_GPIO1_IO03);
		iounmap(SW_PAD_GPIO1_IO03 );
		iounmap(GPIO1_DR );
		iounmap(GPIO1_GDIR );

初始化IO,

/* 初始化 */
/* 时钟配置 */
val = readl(IMX6U_CCM_CCGR1 );
val &= ~(3 << 26);/* 清除以前的配置bit26,27 */
value|= 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置0,打开led
writel(val, GPIO1_DR );

打开灯、关灯,这两种操作在dtsled字符设备操作集合中实现,在open、release中设置一下私有数据,重点是write函数中,实现对灯的控制。

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}

将led_switch()函数复制过来。

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);
	}
}

在退出时,需要把灯关了。

static void __exit dtsled_exit(void)
{
	unsigned int val = 0;
	
	val = readl(GPIO1_DR);
	val |= (1 << 3);
	writel(val, GPIO1_DR);
	
	/* 取消地址映射 */
}
make
arm-linux-gnueabihf-gcc ledAPP.c -o ledAPP
sudo cp dtsled.ko ledAPP /home/yang/linux/nfs/rootfs/lib/modules/4.1.15 -f
# modprobe dtsled.ko
# ./ledAPP /dev/dtsled 1
# ./ledAPP /dev/dtsled 0
# rmmod dtsled.ko

还可以通过其他函数完成寄存器的读取。

/* 初始化LED灯,地址映射 */
#if 0
	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
	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);
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值