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