赞
踩
字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、 IIC、 SPI,LCD 等等都是字符设备,这些设备的驱动就叫做字符设备驱动。在用户空间控制一个字符设备的流程如图所示。
例如在用户空间中调用open,打开一个字符设备,执行流程如下:最终会执行chrdev中的ops对应的open函数。
字符设备相关的结构体:
- static struct char_device_struct {
- struct char_device_struct *next;
- unsigned int major;
- unsigned int baseminor;
- int minorct;
- char name[64];
- struct cdev *cdev; /* will die */
- } *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
-
- #define CHRDEV_MAJOR_HASH_SIZE 255
- struct cdev {
- struct kobject kobj;
- struct module *owner;
- const struct file_operations *ops;
- struct list_head list;
- dev_t dev;
- unsigned int count;
- };
其中chrdevs为一个全局数组,且:
#define CHRDEV_MAJOR_HASH_SIZE 255
旧版本的注册函数为:
- static inline int register_chrdev(unsigned int major, const char *name,
- const struct file_operations *fops)
-
- static inline int register_chrdev(unsigned int major, const char *name,
- const struct file_operations *fops)
- {
- return __register_chrdev(major, 0, 256, name, fops);
- }
通过调用__register_chrdev(major, 0, 256, name, fops)来进行注册:
- int __register_chrdev(unsigned int major, unsigned int baseminor,
- unsigned int count, const char *name,
- const struct file_operations *fops)
- {
- struct char_device_struct *cd;
- struct cdev *cdev;
- int err = -ENOMEM;
-
- cd = __register_chrdev_region(major, baseminor, count, name);
- if (IS_ERR(cd))
- return PTR_ERR(cd);
-
- cdev = cdev_alloc();
- if (!cdev)
- goto out2;
-
- cdev->owner = fops->owner;
- cdev->ops = fops;
- kobject_set_name(&cdev->kobj, "%s", name);
-
- err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
- if (err)
- goto out;
-
- cd->cdev = cdev;
-
- return major ? 0 : cd->major;
- out:
- kobject_put(&cdev->kobj);
- out2:
- kfree(__unregister_chrdev_region(cd->major, baseminor, count));
- return err;
- }
- static struct char_device_struct *
- __register_chrdev_region(unsigned int major, unsigned int baseminor,
- int minorct, const char *name)
- {
- struct char_device_struct *cd, **cp;
- int ret = 0;
- int i;
-
- cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
- if (cd == NULL)
- return ERR_PTR(-ENOMEM);
-
- mutex_lock(&chrdevs_lock);
-
- /* temporary */
- if (major == 0) { //如果没有指定主设备号
- for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
- if (chrdevs[i] == NULL)
- break;
- }
-
- if (i == 0) {
- ret = -EBUSY;
- goto out;
- }
- major = i;
- }
-
- cd->major = major;
- cd->baseminor = baseminor;
- cd->minorct = minorct;
- strlcpy(cd->name, name, sizeof(cd->name));
-
- i = major_to_index(major);
-
- for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) //寻找空的子设备位置
- if ((*cp)->major > major ||
- ((*cp)->major == major &&
- (((*cp)->baseminor >= baseminor) ||
- ((*cp)->baseminor + (*cp)->minorct > baseminor))))
- break;
-
- /* Check for overlapping minor ranges. */
- if (*cp && (*cp)->major == major) {
- int old_min = (*cp)->baseminor;
- int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
- int new_min = baseminor;
- int new_max = baseminor + minorct - 1;
-
- /* New driver overlaps from the left. */
- if (new_max >= old_min && new_max <= old_max) {
- ret = -EBUSY;
- goto out;
- }
-
- /* New driver overlaps from the right. */
- if (new_min <= old_max && new_min >= old_min) {
- ret = -EBUSY;
- goto out;
- }
- }
-
- cd->next = *cp;
- *cp = cd;
- mutex_unlock(&chrdevs_lock);
- return cd;
- out:
- mutex_unlock(&chrdevs_lock);
- kfree(cd);
- return ERR_PTR(ret);
- }
(1)提前指定了设备号:可以通过MKDEV(major)获取dev_t。
- int register_chrdev_region(dev_t from, unsigned count, const char *name)
- {
- struct char_device_struct *cd;
- dev_t to = from + count;
- dev_t n, next;
-
- for (n = from; n < to; n = next) {
- next = MKDEV(MAJOR(n)+1, 0);
- if (next > to)
- next = to;
- cd = __register_chrdev_region(MAJOR(n), MINOR(n),
- next - n, name);
- if (IS_ERR(cd))
- goto fail;
- }
- return 0;
- fail:
- to = n;
- for (n = from; n < to; n = next) {
- next = MKDEV(MAJOR(n)+1, 0);
- kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
- }
- return PTR_ERR(cd);
- }
(2)没有指定主设备号,由系统动态分配:
- int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
- const char *name)
- {
- struct char_device_struct *cd;
- cd = __register_chrdev_region(0, baseminor, count, name);
- if (IS_ERR(cd))
- return PTR_ERR(cd);
- *dev = MKDEV(cd->major, cd->baseminor);
- return 0;
- }
(3)注销字符设备
- void unregister_chrdev_region(dev_t from, unsigned count)
- {
- dev_t to = from + count;
- dev_t n, next;
-
- for (n = from; n < to; n = next) {
- next = MKDEV(MAJOR(n)+1, 0);
- if (next > to)
- next = to;
- kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
- }
- }
区别:
调用cdev_init()函数来把cdev与ops进行绑定:
- void cdev_init(struct cdev *cdev, const struct file_operations *fops)
- {
- memset(cdev, 0, sizeof *cdev);
- INIT_LIST_HEAD(&cdev->list);
- kobject_init(&cdev->kobj, &ktype_cdev_default);
- cdev->ops = fops;
- }
通过调用cdev_add将申请到的cdev添加到与之对应的设备号下:
- int cdev_add(struct cdev *p, dev_t dev, unsigned count)
- {
- int error;
-
- p->dev = dev;
- p->count = count;
-
- error = kobj_map(cdev_map, dev, count, NULL,
- exact_match, exact_lock, p);
- if (error)
- return error;
-
- kobject_get(p->kobj.parent);
-
- return 0;
- }
注销该设备的适合,需要调用cdev_del将其删掉:
- void cdev_del(struct cdev *p)
- {
- cdev_unmap(p->dev, p->count);
- kobject_put(&p->kobj);
- }
通过调用class_create()函数进行创建类,在sys/class/下创建一个文件,并且通过udev的uevent机制通知有设备添加进来。
- #define class_create(owner, name) \
- ({ \
- static struct lock_class_key __key; \
- __class_create(owner, name, &__key); \
- })
- struct class *__class_create(struct module *owner, const char *name,
- struct lock_class_key *key)
- {
- struct class *cls;
- int retval;
-
- cls = kzalloc(sizeof(*cls), GFP_KERNEL);
- if (!cls) {
- retval = -ENOMEM;
- goto error;
- }
-
- cls->name = name;
- cls->owner = owner;
- cls->class_release = class_create_release;
-
- retval = __class_register(cls, key);
- if (retval)
- goto error;
-
- return cls;
-
- error:
- kfree(cls);
- return ERR_PTR(retval);
- }
- int __class_register(struct class *cls, struct lock_class_key *key)
- {
- struct subsys_private *cp;
- int error;
-
- pr_debug("device class '%s': registering\n", cls->name);
-
- cp = kzalloc(sizeof(*cp), GFP_KERNEL);//类同bus,分配一个私有数据
- if (!cp)
- return -ENOMEM;
- klist_init(&cp->klist_devices, klist_class_dev_get, klist_class_dev_put);//初始化class 链表
- INIT_LIST_HEAD(&cp->interfaces);
- kset_init(&cp->glue_dirs);
- __mutex_init(&cp->mutex, "subsys mutex", key);
- error = kobject_set_name(&cp->subsys.kobj, "%s", cls->name);
- if (error) {
- kfree(cp);
- return error;
- }
-
- /* set the default /sys/dev directory for devices of this class */
- if (!cls->dev_kobj)
- cls->dev_kobj = sysfs_dev_char_kobj;
-
- #if defined(CONFIG_BLOCK)
- /* let the block class directory show up in the root of sysfs */
- if (!sysfs_deprecated || cls != &block_class)
- cp->subsys.kobj.kset = class_kset;
- #else
- cp->subsys.kobj.kset = class_kset;
- #endif
- cp->subsys.kobj.ktype = &class_ktype;
- cp->class = cls;
- cls->p = cp;
-
- //在/sys/class/下面创建一个目录,同时发送一个uenent时间给上层
- error = kset_register(&cp->subsys);
- if (error) {
- kfree(cp);
- return error;
- }
- error = add_class_attrs(class_get(cls));
- class_put(cls);
- return error;
- }
- int kset_register(struct kset *k)
- {
- int err;
-
- if (!k)
- return -EINVAL;
- //初始化kset
- kset_init(k);
- //在sysfs中建立目录
- err = kobject_add_internal(&k->kobj);
- if (err)
- return err;
- // 向用户空间发送 KOBJ_ADD事件。
- kobject_uevent(&k->kobj, KOBJ_ADD);
- return 0;
- }
当注销设备时,需要调用class_destory()函数对class进行摧毁:
- int kset_register(struct kset *k)
- {
- int err;
-
- if (!k)
- return -EINVAL;
- //初始化kset
- kset_init(k);
- //在sysfs中建立目录
- err = kobject_add_internal(&k->kobj);
- if (err)
- return err;
- // 向用户空间发送 KOBJ_ADD事件。
- kobject_uevent(&k->kobj, KOBJ_ADD);
- return 0;
- }
一旦创建好了这个class,再调用device_create(…)函数来在/dev目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev会自动响应device_create(…)函数,去/sysfs下寻找对应的类从而创建设备节点。
- /* 5、创建设备 */
- newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);
- struct device *device_create(struct class *class, struct device *parent,
- dev_t devt, void *drvdata, const char *fmt, ...)
- {
- va_list vargs;
- struct device *dev;
-
- va_start(vargs, fmt);
- dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
- va_end(vargs);
- return dev;
- }
- struct device *device_create_vargs(struct class *class, struct device *parent,
- dev_t devt, void *drvdata, const char *fmt,
- va_list args)
- {
- return device_create_groups_vargs(class, parent, devt, drvdata, NULL,
- fmt, args);
- }
- static struct device *
- device_create_groups_vargs(struct class *class, struct device *parent,
- dev_t devt, void *drvdata,
- const struct attribute_group **groups,
- const char *fmt, va_list args)
- {
- struct device *dev = NULL;
- int retval = -ENODEV;
-
- if (class == NULL || IS_ERR(class))
- goto error;
-
- dev = kzalloc(sizeof(*dev), GFP_KERNEL);
- if (!dev) {
- retval = -ENOMEM;
- goto error;
- }
-
- //初始化dev
- device_initialize(dev);
- dev->devt = devt;
- dev->class = class;
- dev->parent = parent;
- dev->groups = groups;
- dev->release = device_create_release;
- dev_set_drvdata(dev, drvdata);
-
- //设置kobject的名字
- retval = kobject_set_name_vargs(&dev->kobj, fmt, args);
- if (retval)
- goto error;
-
- retval = device_add(dev); //添加设备
- if (retval)
- goto error;
-
- return dev;
-
- error:
- put_device(dev);
- return ERR_PTR(retval);
- }
- int device_add(struct device *dev)
- {
- struct device *parent = NULL;
- struct kobject *kobj;
- struct class_interface *class_intf;
- int error = -EINVAL;
-
- dev = get_device(dev); //增加设备的引用计数dev->kobj->kref
- if (!dev)
- goto done;
-
- if (!dev->p) {
- //私有数据没有的话,申请并初始化:是连接bus,parent,对应驱动等重要连接点
- error = device_private_init(dev);
- if (error)
- goto done;
- }
-
- /*
- * for statically allocated devices, which should all be converted
- * some day, we need to initialize the name. We prevent reading back
- * the name, and force the use of dev_name()
- */
- if (dev->init_name) {
- //用dev的init_name初始化dev->kobject->name,实际是目录名
- dev_set_name(dev, "%s", dev->init_name);
- dev->init_name = NULL;
- }
-
- /* subsystems can specify simple device enumeration */
- //dev的init_name不存在且dev->kobject->name也不存在,则用bus的dev_name和dev_id来设置目录名
- if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
- dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);
-
- if (!dev_name(dev)) { //如果上面几个步骤都还没找到可设的目录名,则失败返回,设备必须要放在某个目录下
- error = -EINVAL;
- goto name_error;
- }
-
- pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
-
- parent = get_device(dev->parent); //父节点引用计数加1
- kobj = get_device_parent(dev, parent); //拿到父节点
- //拿到父节点赋值给本dev->kobj.parent,确定设备父子关系,也确定了sysfs中的目录关系
- if (kobj)
- dev->kobj.parent = kobj;
-
- /* use parent numa_node */
- //设置该设备节点为-1
- if (parent)
- set_dev_node(dev, dev_to_node(parent));
-
- /* first, register with generic layer. */
- /* we require the name to be set before, and pass NULL */
- //把内嵌的kobject注册到设备模型中,将设备加入到kobject模型中,创建sys项目目录,目录名字为kobj->name
- error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
- if (error)
- goto Error;
-
- /* notify platform of device entry */
- if (platform_notify)
- platform_notify(dev);
- /*创建sys目录下设备的uevent属性文件,通过它可以查看设备的uevent事件,主要是在/sys/devices/.../中添加dev的uevent属性文件*/
- error = device_create_file(dev, &dev_attr_uevent);
- if (error)
- goto attrError;
- /*实际创建的kobject都是在device下面,其他class,bus之类的里面的具体设备都是device目录下设备的符号链接,这里是在class下创建符号链接 */
- error = device_add_class_symlinks(dev);
- if (error)
- goto SymlinkError;
- error = device_add_attrs(dev);//创建sys目录下设备其他属性文件(添加设备属性文件)
- if (error)
- goto AttrsError;
- error = bus_add_device(dev);//添加设备的总线属性,将设备加入到管理它的bus总线的设备连表上,创建subsystem链接文件,链接class下的具体的子系统文件夹 将设备添加到其总线的设备列表中。
- if (error)
- goto BusError;
- error = dpm_sysfs_add(dev);//把设备增加到sysfs电源管理power目录(组)下,如果该设备设置电源管理相关的内容
- if (error)
- goto DPMError;
- device_pm_add(dev);//设备添加到电源管理相关的设备列表中
-
- //主设备号存在,则产生dev属性,在/dev目录下产生设备节点文件
- if (MAJOR(dev->devt)) {
- /*创建sys目录下设备的设备号属性,即major和minor /主要是在sys/devices/...中添加dev属性文件*/
- error = device_create_file(dev, &dev_attr_dev);
- if (error)
- goto DevAttrError;
- /*在/sys/dev/char/或者/sys/dev/block/创建devt的属性的连接文件,形如10:45,由主设备号和次设备号构成,指向/sys/devices/.../的具体设备目录*/
- error = device_create_sys_dev_entry(dev);//该链接文件只具备读属性,显示主设备号:次设备号,如10:45,用户空间udev响应uevent事件时,将根据设备号在/dev下创建节点文件
- if (error)
- goto SysEntryError;
-
- devtmpfs_create_node(dev);
- }
-
- /* Notify clients of device addition. This call must come
- * after dpm_sysfs_add() and before kobject_uevent().
- */
- if (dev->bus)//通知客户端,有新设备加入
- blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
- BUS_NOTIFY_ADD_DEVICE, dev);
- /*产生一个内核uevent事件(这里是有设备加入),可以是helper,也可是通过netlink机制和用户空间通信该事件可以被内核以及应用层捕获,属于linux设备模型中热插拔机制*/
- kobject_uevent(&dev->kobj, KOBJ_ADD);
- //给设备探测寻找相对应的驱动,在bus上找dev对应的drv,主要执行__device_attach,主要进行match,sys_add,执行probe函数和绑定等操作
- bus_probe_device(dev);
- if (parent)
- klist_add_tail(&dev->p->knode_parent,//添加新设备到父设备的子列表中
- &parent->p->klist_children);
-
- if (dev->class) {//如果改dev有所属类,则将dev的添加到类的设备列表里面
- mutex_lock(&dev->class->p->mutex);//要使用class的互斥锁
- /* tie the class to the device */
- klist_add_tail(&dev->knode_class,//dev添加到class的klist_device链表(对driver也有klist_driver链表)
- &dev->class->p->klist_devices);
-
- /* notify any interfaces that the device is here */
- /*通知有新设备加入,执行该dev的class_intf->add_dev(),好处是只有设备匹配注册成功了,才进行其它的注册工作(如字符设备的注册,生成/dev/***节点文件)以及部分初始化工作。*/
- list_for_each_entry(class_intf,
- &dev->class->p->interfaces, node)
- if (class_intf->add_dev)
- class_intf->add_dev(dev, class_intf);
- mutex_unlock(&dev->class->p->mutex);
- }
- done:
- put_device(dev);
- return error;
- SysEntryError:
- if (MAJOR(dev->devt))
- device_remove_file(dev, &dev_attr_dev);
- DevAttrError:
- device_pm_remove(dev);
- dpm_sysfs_remove(dev);
- DPMError:
- bus_remove_device(dev);
- BusError:
- device_remove_attrs(dev);
- AttrsError:
- device_remove_class_symlinks(dev);
- SymlinkError:
- device_remove_file(dev, &dev_attr_uevent);
- attrError:
- kobject_uevent(&dev->kobj, KOBJ_REMOVE);
- kobject_del(&dev->kobj);
- Error:
- cleanup_device_parent(dev);
- put_device(parent);
- name_error:
- kfree(dev->p);
- dev->p = NULL;
- goto done;
- }
同样地,在注销字符设备的适合,需要调用device_destroy()函数进行设备注销:
- void device_destroy(struct class *class, dev_t devt)
- {
- struct device *dev;
-
- dev = class_find_device(class, NULL, &devt, __match_devt);
- if (dev) {
- put_device(dev);
- device_unregister(dev);
- }
- }
- #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/errno.h>
- #include <linux/gpio.h>
- #include <linux/cdev.h>
- #include <linux/device.h>
-
- #include <asm/mach/map.h>
- #include <asm/uaccess.h>
- #include <asm/io.h>
-
-
- #define NEWCHRLED_CNT 1 /* 设备号个数 */
- #define NEWCHRLED_NAME "newchrled" /* 名字 */
- #define LEDOFF 0 /* 关灯 */
- #define LEDON 1 /* 开灯 */
-
- /* 寄存器物理地址 */
- #define CCM_CCGR1_BASE (0X020C406C)
- #define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
- #define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
- #define GPIO1_DR_BASE (0X0209C000)
- #define GPIO1_GDIR_BASE (0X0209C004)
-
- /* 映射后的寄存器虚拟地址指针 */
- 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;
-
- /* newchrled设备结构体 */
- struct newchrled_dev{
- dev_t devid; /* 设备号 */
- struct cdev cdev; /* cdev */
- struct class *class; /* 类 */
- struct device *device; /* 设备 */
- int major; /* 主设备号 */
- int minor; /* 次设备号 */
- };
-
- struct newchrled_dev newchrled; /* led设备 */
-
- /*
- * @description : LED打开/关闭
- * @param - sta : LEDON(0) 打开LED,LEDOFF(1) 关闭LED
- * @return : 无
- */
- 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);
- }
- }
-
- /*
- * @description : 打开设备
- * @param - inode : 传递给驱动的inode
- * @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
- * 一般在open的时候将private_data指向设备结构体。
- * @return : 0 成功;其他 失败
- */
- static int led_open(struct inode *inode, struct file *filp)
- {
- filp->private_data = &newchrled; /* 设置私有数据 */
- return 0;
- }
-
- /*
- * @description : 从设备读取数据
- * @param - filp : 要打开的设备文件(文件描述符)
- * @param - buf : 返回给用户空间的数据缓冲区
- * @param - cnt : 要读取的数据长度
- * @param - offt : 相对于文件首地址的偏移
- * @return : 读取的字节数,如果为负值,表示读取失败
- */
- static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
- {
- return 0;
- }
-
- /*
- * @description : 向设备写数据
- * @param - filp : 设备文件,表示打开的文件描述符
- * @param - buf : 要写给设备写入的数据
- * @param - cnt : 要写入的数据长度
- * @param - offt : 相对于文件首地址的偏移
- * @return : 写入的字节数,如果为负值,表示写入失败
- */
- static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
- {
- 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 == LEDON) {
- led_switch(LEDON); /* 打开LED灯 */
- } else if(ledstat == LEDOFF) {
- led_switch(LEDOFF); /* 关闭LED灯 */
- }
- return 0;
- }
-
- /*
- * @description : 关闭/释放设备
- * @param - filp : 要关闭的设备文件(文件描述符)
- * @return : 0 成功;其他 失败
- */
- static int led_release(struct inode *inode, struct file *filp)
- {
- return 0;
- }
-
- /* 设备操作函数 */
- static struct file_operations newchrled_fops = {
- .owner = THIS_MODULE,
- .open = led_open,
- .read = led_read,
- .write = led_write,
- .release = led_release,
- };
-
- /*
- * @description : 驱动出口函数
- * @param : 无
- * @return : 无
- */
- static int __init led_init(void)
- {
- u32 val = 0;
-
- /* 初始化LED */
- /* 1、寄存器地址映射 */
- IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
- SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
- SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
- GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
- GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);
-
- /* 2、使能GPIO1时钟 */
- val = readl(IMX6U_CCM_CCGR1);
- val &= ~(3 << 26); /* 清除以前的设置 */
- val |= (3 << 26); /* 设置新值 */
- writel(val, IMX6U_CCM_CCGR1);
-
- /* 3、设置GPIO1_IO03的复用功能,将其复用为
- * GPIO1_IO03,最后设置IO属性。
- */
- writel(5, SW_MUX_GPIO1_IO03);
-
- /*寄存器SW_PAD_GPIO1_IO03设置IO属性
- *bit 16:0 HYS关闭
- *bit [15:14]: 00 默认下拉
- *bit [13]: 0 kepper功能
- *bit [12]: 1 pull/keeper使能
- *bit [11]: 0 关闭开路输出
- *bit [7:6]: 10 速度100Mhz
- *bit [5:3]: 110 R0/6驱动能力
- *bit [0]: 0 低转换率
- */
- writel(0x10B0, SW_PAD_GPIO1_IO03);
-
- /* 4、设置GPIO1_IO03为输出功能 */
- val = readl(GPIO1_GDIR);
- val &= ~(1 << 3); /* 清除以前的设置 */
- val |= (1 << 3); /* 设置为输出 */
- writel(val, GPIO1_GDIR);
-
- /* 5、默认关闭LED */
- val = readl(GPIO1_DR);
- val |= (1 << 3);
- writel(val, GPIO1_DR);
-
- /* 注册字符设备驱动 */
- /* 1、创建设备号 */
- if (newchrled.major) { /* 定义了设备号 */
- newchrled.devid = MKDEV(newchrled.major, 0);
- register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);
- } else { /* 没有定义设备号 */
- alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME); /* 申请设备号 */
- newchrled.major = MAJOR(newchrled.devid); /* 获取分配号的主设备号 */
- newchrled.minor = MINOR(newchrled.devid); /* 获取分配号的次设备号 */
- }
- printk("newcheled major=%d,minor=%d\r\n",newchrled.major, newchrled.minor);
-
- /* 2、初始化cdev */
- newchrled.cdev.owner = THIS_MODULE;
- cdev_init(&newchrled.cdev, &newchrled_fops);
-
- /* 3、添加一个cdev */
- cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);
-
- /* 4、创建类 */
- newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
- if (IS_ERR(newchrled.class)) {
- return PTR_ERR(newchrled.class);
- }
-
- /* 5、创建设备 */
- newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);
- if (IS_ERR(newchrled.device)) {
- return PTR_ERR(newchrled.device);
- }
-
- return 0;
- }
-
- /*
- * @description : 驱动出口函数
- * @param : 无
- * @return : 无
- */
- static void __exit led_exit(void)
- {
- /* 取消映射 */
- iounmap(IMX6U_CCM_CCGR1);
- iounmap(SW_MUX_GPIO1_IO03);
- iounmap(SW_PAD_GPIO1_IO03);
- iounmap(GPIO1_DR);
- iounmap(GPIO1_GDIR);
-
- /* 注销字符设备驱动 */
- cdev_del(&newchrled.cdev);/* 删除cdev */
- unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); /* 注销设备号 */
-
- device_destroy(newchrled.class, newchrled.devid);
- class_destroy(newchrled.class);
- }
-
- module_init(led_init);
- module_exit(led_exit);
- MODULE_LICENSE("GPL");
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。