设备树下的 LED 驱动实验

1、修改设备树

在设备树下添加:

 alphaled {
 	#address-cells = <1>;
 	#size-cells = <1>;
 	compatible = "atkalpha-led";
 	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 */
 };

        添加到根节点目录下,其中alphaled为一级子节点。reg中的属性为实验程序中使用到的寄存器值。

        属性#address-cells #size-cells 都为 1 ,表示 reg 属性中起始地址占用一个字长 (cell),地址长度也占用一个字长 (cell)

        以0X020C406C 0X04这组为例,0X020C406C表示的是CCM_CCGR1_BASE寄存器的地址,4表示的是该寄存器的长度为32位,4个字节

2、实验程序

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/errno.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/mach/map.h>

#include <linux/cdev.h>
#include <linux/device.h>

#include <linux/of.h>
#include <linux/of_address.h>



#define DTSLED_CNT 1
#define DTSLED_NAME "dtsled"


/*物理地址映射后的地址指针*/
static void __iomem *IMX_CCM_CCGR1;
static void __iomem *IMX_MUX_GPIO1_IO03;
static void __iomem *IMX_PAD_GPIO1_IO03;
static void __iomem *IMX_GPIO1_DR;
static void __iomem *IMX_GPIO1_GDIR;


void led_switch(int sta)
{
    int val=0;
    val=readl(IMX_GPIO1_DR);
    val&=~(1<<3);//将val变为0
    if(sta ==0)
    {
        val&=~(1<<3);
        writel(val,IMX_GPIO1_DR);    
    }
    else
    {
        val|=(1<<3);
        writel(val,IMX_GPIO1_DR);
    }
}



struct dtsled_dev{
    int major;//主设备号
    int minor;//次设备号
    dev_t devide;//设备号
    struct cdev cdev;//cdev
    struct class *class;//类
    struct device *device;//设备
    struct device_node * bl_nd1;/*设备节点*/
    struct property *com;//获取到的属性
};

struct dtsled_dev dtsled;

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
 static int dtsled_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &dtsled; /* 设置私有数据 */
	printk("chrdevbase open!\r\n");
	return 0;
}

/*
 * @description		: 向设备写数据 
 * @param - filp 	: 设备文件,表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t dtsled_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    struct dtsled_dev *dev=(struct dtsled_dev * )filp->private_data;
    int retvalue;
    unsigned char databuf[1];
    unsigned char ledstat;

 	retvalue = copy_from_user(databuf, buf, cnt);
 		if(retvalue < 0) {
    	printk("kernel write failed!\r\n");
    	return -EFAULT;
 	}	
 	ledstat = databuf[0]; /* 获取状态值 */
 	if(ledstat == 0) { 
    	led_switch(0); /* 打开 LED 灯 */
 	} else if(ledstat == 1) {
    	led_switch(1); /* 关闭 LED 灯 */
 	}
	return 0;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int dtsled_release(struct inode *inode, struct file *filp)
{
	//printk("chrdevbase release!\r\n");
	return 0;
}

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

static int __init dtsled_init(void)
{
    int elem;
    u32 regdata[14];
    u8 i;
    int ret=0;
/*路径名获取设备节点*/
    dtsled.bl_nd1=of_find_node_by_path("/alphaled");
    if(dtsled.bl_nd1==NULL)
    {
        printk("of_find_node_by_path failed\r\n");
        return -EINVAL;
    }

    //读取reg属性中元素的数量
    elem=of_property_count_elems_of_size(dtsled.bl_nd1,"reg",sizeof(u32));
    printk("%d\n",elem);

     //读取reg属性中元素的内容
    ret=of_property_read_u32_array(dtsled.bl_nd1,"reg",regdata,elem);
    if(ret<0)
    {
        printk("of_property_read_u32_array\n");
    }else{
        for(i=0;i<elem;i++)
        {
            printk("reg[%d]=%#x\n",i,regdata[i]);
        }
    }
    /*将物理地址转化为虚拟地址*/
    int val=0;
    int result;
    /*IMX_CCM_CCGR1=ioremap(regdata[0],regdata[1]);
    IMX_MUX_GPIO1_IO03=ioremap(regdata[2],regdata[3]);
    IMX_PAD_GPIO1_IO03=ioremap(regdata[4],regdata[5]);
    IMX_GPIO1_DR=ioremap(regdata[6],regdata[7]);
    IMX_GPIO1_GDIR=ioremap(regdata[8],regdata[9]);*/


    IMX_CCM_CCGR1=of_iomap(dtsled.bl_nd1,0);
    IMX_MUX_GPIO1_IO03=of_iomap(dtsled.bl_nd1,1);
    IMX_PAD_GPIO1_IO03=of_iomap(dtsled.bl_nd1,2);
    IMX_GPIO1_DR=of_iomap(dtsled.bl_nd1,3);
    IMX_GPIO1_GDIR=of_iomap(dtsled.bl_nd1,4);

    /*开启时钟*/
    val=readl(IMX_CCM_CCGR1);
    val&=~(3<<26);
    val|=3<<26;
    writel(val,IMX_CCM_CCGR1);

    /*配置为GPIO1_IO03*/
    writel(5,IMX_MUX_GPIO1_IO03);

    /*配置电气属性*/
     writel(0x10b0,IMX_PAD_GPIO1_IO03);

    /*将GPIO1_IO03设置为输出*/
     val=readl(IMX_GPIO1_GDIR);
     val&=~(1<<3);
     val|=(1<<3);
     writel(val,IMX_GPIO1_GDIR);

     /*设置默认输出低电平*/
     val=readl(IMX_GPIO1_DR);
     val&=~(1<<3);
     writel(val,IMX_GPIO1_DR);
    
    /*注册字符设备*/
    dtsled.major=0;
    if(dtsled.major){
        dtsled.devide=MKDEV(dtsled.major,0);
        register_chrdev_region(dtsled.devide,DTSLED_CNT,DTSLED_NAME);

    }else{
        ret=alloc_chrdev_region(&dtsled.devide, 0, DTSLED_CNT, DTSLED_NAME);	/* 申请设备号 */
		if(ret<0)
            {
                goto fail_devide;
            }
        dtsled.major = MAJOR(dtsled.devide);	/* 获取分配号的主设备号 */
		dtsled.minor = MINOR(dtsled.devide);	/* 获取分配号的次设备号 */
        printk("major=%d\n",dtsled.major);
        printk("minor=%d\n",dtsled.minor);
    }
     /*初始化cdev*/
    dtsled.cdev.owner=THIS_MODULE;
    cdev_init(&dtsled.cdev, &dtsled_ops);
    
	/* 3、添加一个cdev */
	ret= cdev_add(&dtsled.cdev, dtsled.devide, DTSLED_CNT);
    if(ret<0)
        {
            goto fail_cdev;
        }

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

	/* 5、创建设备 */
	dtsled.device = device_create(dtsled.class, NULL, dtsled.devide, NULL, DTSLED_NAME);
	if (IS_ERR(dtsled.device)) {
		ret=PTR_ERR(dtsled.device);
        goto fail_device;
	}    
    return 0;
    fail_device:
        class_destroy(dtsled.class);
    fail_class:
        cdev_del(&dtsled.cdev);
    fail_cdev:
        unregister_chrdev_region(dtsled.devide, DTSLED_CNT);
    fail_devide:
        return ret;
}

static void __exit dtsled_exit(void)
{
    int val=0;
    val=readl(IMX_GPIO1_DR);
    val|=(1<<3);
    writel(val,IMX_GPIO1_DR);
    /*释放虚拟地址*/
    iounmap(IMX_CCM_CCGR1);
    iounmap(IMX_MUX_GPIO1_IO03);
    iounmap(IMX_PAD_GPIO1_IO03);
    iounmap(IMX_GPIO1_DR);
    iounmap(IMX_GPIO1_GDIR);
    /* 注销字符设备驱动 */
	device_destroy(dtsled.class, dtsled.devide);
	class_destroy(dtsled.class);

    cdev_del(&dtsled.cdev);/*  删除cdev */
    unregister_chrdev_region(dtsled.devide, DTSLED_CNT); /* 注销设备号 */
    
    printk("exit\n");
    printk("exit\n");
}

/*模块的加载与卸载*/
module_init(dtsled_init);
module_exit(dtsled_exit);


MODULE_LICENSE("GPL");
MODULE_AUTHOR("liuchuanqiang");

3、程序说明

  1. 使用设备树进行编写驱动时,首先要进行修改设备树。本实验将驱动LED点亮的所有寄存器都写到了设备树的alphaled节点下,其中alphaled节点位于设备树的根目录下。
  2. 使用ioremap 函数完成内存映射,将物理地址转化为虚拟地址。
  3. 在进行寄存器映射时,还可以使用of_iomap 函数。of_iomap一次进行的是一组reg属性寄存器的映射。

        IMX_CCM_CCGR1=of_iomap(dtsled.bl_nd1,0);第一个参数是设备节点,第二个参数是组数,是从0开始。

        4、程序驱动部分与其他字符驱动程序相同。

        5、#address-cells 和#size-cells 这两个属性可以用在任 何拥有子节点的设备中,用于描述子节点的地址信息。#address-cells 属性值决定了子节点 reg 属 性中地址信息所占用的字长(32 )#size-cells 属性值决定了子节点 reg 属性中长度信息所占的字长(32 )

        6、判断单片机是不是可以运行操作系统?

                MMU内存管理单元——完成虚拟空间到物理空间的映射。

         7、创建类、设备是为了自动创建设备节点。

         8、Linux设备树

                设备树提出的背景(为什么要使用设备树)?

        9、使用设备树点灯的编程思想?

                /proc/device-tree 设备树查看节点

        修改设备数,从设备树中读取寄存器内存地址,完成虚拟地址映射,使用of函数实现IO操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值