本文章参考正点原子相关教程,仅学习使用
考虑到老版本使用register_chrdev需要事先确定设备号,驱动加载需要手动创建节点(mknod).新版本linux驱动采用动态分配设备号,可自动创建节点.
1. 新字符设备注册方法
// 没有指定设备号,申请设备号
// dev : 设备号
// baseminor : 次设备号 , 通常为0
// count : 设备申请数量
// name : 设备名
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
// 指定设备号,申请设备号
// from : 给定的设备号
// count : 申请设备数量
// name : 设备名称
int register_chrdev_region(dev_t from, unsigned count, const char *name)
// 注销设备
void unregister_chrdev_region(dev_t from, unsigned count)
// 字符设备结构体
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
// 初始化字符设备
void cdev_init(struct cdev *, const struct file_operations *);
// 添加字符设备
int cdev_add(struct cdev *, dev_t, unsigned);
// 删除字符设备
void cdev_del(struct cdev *);
2.自动创建设备节点
linux通过udev机制自动创建节点,udev通过访问硬件状态,根据硬件状态申请节点.
自动创建设备节点通过创建类和设备两部组成.
// 创建类
// owner : THIS_MODULE
// name : class name
extern struct class * __must_check __class_create(struct module *owner,
const char *name,
struct lock_class_key *key);
// 销毁类
extern void class_destroy(struct class *cls);
// 创建设备
struct device *device_create(struct class *cls, struct device *parent,
dev_t devt, void *drvdata,
const char *fmt, ...);
// 销毁设备
extern void device_destroy(struct class *cls, dev_t devt);
3.自定义设备结构体
通常设备包含很多属性,如设备号,类,设备等等,采用结构体定义代替变量定义.一般地,在open函数里面将结构作为私有设备添加到设备中.
struct newchrled_dev
{
dev_t devid; /*device number */
struct cdev cdev; /*cdev */
struct class *class; /*class */
struct device *device; /*device */
int major; /*major device number*/
int minor; /*minor device number */
};
4. 完整例子
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define LED_NAME 'led'
#define LED_CNT 1
#define LEDOFF 0
#define LEDON 1
// physical address
#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)
// virtual address
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;
struct newchrled_dev
{
dev_t devid; /*device number */
struct cdev cdev; /*cdev */
struct class *class; /*class */
struct device *device; /*device */
int major; /*major device number*/
int minor; /*minor device number */
};
struct newchrled_dev newchrled;
// open or close led
void led_switch(uint8_t sta){
uint32_t 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);
}
}
// open device
static int led_open(struct inode *inode , struct file *flip){
// set private data
flip->private_data = &newchrled;
return 0;
}
// release device
static int led_release(struct inode *inode, struct file *filp){
return 0;
}
// read data
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt){
return 0;
}
// write data
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt){
int ret;
unsigned char databuf[1];
unsigned char ledstat;
ret = copy_from_user(databuf,buf,cnt);
if (ret < 0 ){
printk("kernel write failed!\n");
return -1;
}
ledstat = databuf[0];
if (ledstat == LEDON){
led_switch(LEDON);
}else if(ledstat == LEDOFF){
led_switch(LEDOFF);
}
return 0;
}
// device operation
static struct file_operation led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_read,
};
// device init
static int __init led_init(void){
int ret = 0;
uint32_t val = 0;
// address mapping
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);
// init gpio1 time
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26);
val |= (3 << 26);
writel(val, IMX6U_CCM_CCGR1);
// set reuse
writel(5, SW_MUX_GPIO1_IO03);
// set io
writel(0x10B0,SW_PAD_GPIO1_IO03);
// set gpio1 3 as output
val = readl(GPIO1_GDIR);
val &= ~(1 << 3);
val |= (1 << 3);
writel(val, GPIO1_GDIR);
// close led
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val, GPIO1_DR);
// register device
if(newchrled.major) /*if defined major */
{
newchrled.devid = MKDEV(newchrled.major,0); /* */
register_chrdev_region(newchrled.devid, LED_CNT, LED_NAME);
}else
{
alloc_chrdev_region(&newchrled.devid, LED_CNT, LED_NAME);
newchrled.major = MAJOR(newchrled.devid);
newchrled.minor = MAJOR(newchrled.devid);
}
printk("newchrled major = %d, minor = %d.\n",newchrled.major, newchrled.minor);
// cdev
newchrled.cdev.owner = THIS_MODULE;
cdev_init(&newchrled.cdev, &led_fops);
cdev_add(&newchrled.cdev, newchrled.devid, LED_CNT);
// class
newchrled.class = class_creat(THIS_MODULE, LED_NAME);
if(IS_ERR(newchrled.class)){
return PTR_ERR(newchrled.class);
}
// device
newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, LED_NAME);
if(IS_ERR(newchrled.device)){
return PTR_ERR(newchrled.device);
}
return 0;
}
// device exit
static void __exit led_exit(void){
// cancel mapping
iounmap(CCM_CCGR1_BASE);
iounmap(SW_MUX_GPIO1_IO03_BASE);
iounmap(SW_PAD_GPIO1_IO03_BASE);
iounmap(GPIO1_DR_BASE);
iounmap(GPIO1_GDIR_BASE);
cdev_del(&newchrled.cdev);
unregister_chrdev_region(newchrled.devid, LED_CNT);
device_destory(newchrled.device, LED_CNT);
class_destory(newchrled.class);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("TAN");