1.板载蜂鸣器驱动测试
1.1、驱动部分
(1)移植内核已经提供了蜂鸣器驱动源码
(2)make menuconfig
(3)bug排查。修改Makefile中的宏名,最终可以在系统中看到 /dev/buzzer
1.2、应用部分
(1)应用编写:打开文件+ioctl
(2)测试实践
2.misc类设备介绍
由于Linux驱动倾向于分层设计,所以各个具体的设备都可以找到它归属的类型,从而套到它相应的架构里面去,并且只需要实现最底层的那一部分。但是,也有部分类似buzzer的字符设备,确实不知道它属于什么类型,一般采用miscdevice框架结构。
2.1、何为misc
(1)中文名:杂项设备\杂散设备
(2)/sys/class/misc
(3)典型的字符设备
(4)有一套驱动框架,内核实现一部分(misc.c),驱动实现一部分(x210-buzzer.c)。
(5)misc是对原始的字符设备注册接口的一个类层次的封装,很多典型字符设备都可以归类到misc类中,使用misc驱动框架来管理。
2.2、misc类设备驱动架构
(1)内核开发者实现部分,关键点有2个:一个是类的创建,另一个是开放给驱动开发者的接口
(2)具体设备驱动工程师实现部分
3 . misc类设备原理
3.1、misc源码框架基础
(1)misc源码框架本身也是一个模块,内核启动时自动加载
(2)源码框架的主要工作:注册misc类,使用老接口注册字符设备驱动(主设备号10),开放device注册的接口misc_register给驱动工程师
3.2、misc类设备的注册
(1)驱动工程师需要借助misc来加载自己的驱动时,只需要调用misc_register接口注册自己的设备即可,其余均不用管。
(2)misc_list链表的作用。内核定义了一个misc_list链表用来记录所有内核中注册了的杂散类设备。当我们向内核注册一个misc类设备时,内核就会向misc_list链表中insert一个节点。
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)原式子:static LIST_HEAD(misc_list);
展开后:static struct list_head misc_list = { &(misc_list), &(misc_list) }
3.3 主设备号和次设备号的作用和区分
miscdevice本质上也是字符设备
只是在miscdevice核心层的misc_init()函数中,通过register_chrdev(MISC_MAJOR, "misc",&misc_fops)注册了字符设备
而具体miscdevice实例调用misc_register()的时候又自动完成了device_create()、获取动态次设备号的动作
每个misc设备的主设备号都是MISC_MAJOR = 10
misc设备结构体
struct miscdevice {
int minor;
const char *name;
const struct file_operations *fops;
struct list_head list;
struct device *parent;
struct device *this_device;
const char *nodename;
mode_t mode;
};
如果minor为MISC_DYNAMIC_MINOR, miscdevice核心层会自动找一个空闲的次设备号,否则用minor指定的次设备号
miscdevice驱动的注册和注销分别用下面两个API:
int misc_register(struct miscdevice * misc);
int misc_deregister(struct miscdevice *misc);
因此miscdevice驱动的一般结构形如:
static const struct file_operations xxx_fops = {
.unlocked_ioctl = xxx_ioctl,
.open= xxx_open,
};
static struct miscdevice xxx_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "xxx",
.fops = &xxx_fops
};
static int __init xxx_init(void)
{
printk("ARC Hostlink driver mmap at 0x%p\n", __HOSTLINK__);
return misc_register(&xxx_dev);//注册
}
static void __exit xxx_exit(void)
{
misc_deregister(&xxx_dev);//注销
}
4. 注册和注销接口代码解析
注册:
int misc_register(struct miscdevice * misc)
{
struct miscdevice *c;//作临时指针
dev_t dev;//设备号变量
int err = 0;
INIT_LIST_HEAD(&misc->list);//初始化设备链表节点
mutex_lock(&misc_mtx);//上锁
list_for_each_entry(c, &misc_list, list) {//遍历链表的每个节点
if (c->minor == misc->minor) {//查找是否已经存在次设备号相等的设备
mutex_unlock(&misc_mtx);//若存在,返回错误
return -EBUSY;
}
}
if (misc->minor == MISC_DYNAMIC_MINOR) {//若定义为自动分配
int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);//查找为零的那一位
if (i >= DYNAMIC_MINORS) {//若没有可以的次设备号
mutex_unlock(&misc_mtx);
return -EBUSY;
}
misc->minor = DYNAMIC_MINORS - i - 1;
set_bit(i, misc_minors);//把该位设置位不为空
}
dev = MKDEV(MISC_MAJOR, misc->minor);//得到设备号
misc->this_device = device_create(misc_class, misc->parent, dev,
misc, "%s", misc->name);//创建设备文件
if (IS_ERR(misc->this_device)) {
int i = DYNAMIC_MINORS - misc->minor - 1;
if (i < DYNAMIC_MINORS && i >= 0)
clear_bit(i, misc_minors);
err = PTR_ERR(misc->this_device);
goto out;
}
/*
* Add it to the front, so that later devices can "override"
* earlier defaults
*/
list_add(&misc->list, &misc_list);//把该设备挂到misc设备链表上
out:
mutex_unlock(&misc_mtx);
return err;
}
注销
int misc_deregister(struct miscdevice *misc)
{
int i = DYNAMIC_MINORS - misc->minor - 1;//得到次设备号
if (list_empty(&misc->list))//得到的设备节点为空
return -EINVAL;
mutex_lock(&misc_mtx);
list_del(&misc->list);//从链表中删除这个次设备号
device_destroy(misc_class, MKDEV(MISC_MAJOR, misc->minor));//删除设备文件
if (i < DYNAMIC_MINORS && i >= 0)
clear_bit(i, misc_minors);//清除位图的该位
mutex_unlock(&misc_mtx);
return 0;
}
重要的函数和结构体:
static LIST_HEAD(misc_list);//链表初始化
static DEFINE_MUTEX(misc_mtx);//定义互斥锁
#define DYNAMIC_MINORS 64 /* like dynamic majors */
static DECLARE_BITMAP(misc_minors, DYNAMIC_MINORS);//定义位图
// Misc设备打开函数
static int misc_open(struct inode * inode, struct file * file)
// misc设备类
static struct class *misc_class;
// misc的操作结构体
static const struct file_operations misc_fops = {
.owner = THIS_MODULE,
.open = misc_open,
};
// misc注册函数
int misc_register(struct miscdevice * misc)
// misc注销函数
int misc_deregister(struct miscdevice *misc)
// 导出
EXPORT_SYMBOL(misc_register);
EXPORT_SYMBOL(misc_deregister);
static char *misc_devnode(struct device *dev, mode_t *mode)//检验读写权限
static int __init misc_init(void)//初始化
subsys_initcall(misc_init);
5. struct file的好处
可以利用struct file的private_data挂接驱动的结构体,这就可以通过struct file找到驱动的结构体
每一个文件都有一个对应的结构体,这个结构体非常重要
struct file { //有些变量我没有写上
const struct file_operations *f_op; //用于挂接设备驱动的file_operations
spinlock_t f_lock; /* f_ep_links, f_flags, no IRQ */
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode;
loff_t f_pos;
struct fown_struct f_owner;
const struct cred *f_cred;
struct file_ra_state f_ra;
u64 f_version;
void *private_data; //用作私有数据,可以挂接设备结构体
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
unsigned long f_mnt_write_state;
};
在调用misc_register(&xxx_dev)时,该函数内部会自动调用device_create(),而device_create()会以xxx_dev作为drvdata参数。其次,在miscdevice核心层misc_open()函数的帮助下,在file_operations的成员函数中, xxx_dev会自动成为file的private_data(misc_open会完成file->private_data的赋值操作)。
static int misc_open(struct inode * inode, struct file * file)
{
int minor = iminor(inode);//得到次设备号
struct miscdevice *c;
int err = -ENODEV;
const struct file_operations *old_fops, *new_fops = NULL;
mutex_lock(&misc_mtx);//上互斥锁
list_for_each_entry(c, &misc_list, list) {
if (c->minor == minor) {//查找次设备号相同的设备
new_fops = fops_get(c->fops); //得到file_operations
break;
}
}
if (!new_fops) {//如果为空
mutex_unlock(&misc_mtx);
request_module("char-major-%d-%d", MISC_MAJOR, minor);
mutex_lock(&misc_mtx);
list_for_each_entry(c, &misc_list, list) {
if (c->minor == minor) {
new_fops = fops_get(c->fops);
break;
}
}
if (!new_fops)
goto fail;
}
err = 0;
old_fops = file->f_op;//得到老的文件的file_operations
file->f_op = new_fops;//变为自己定义的file_operations
if (file->f_op->open) {//如果file_operations的open变量不为空
file->private_data = c;//把设备miscdevice 挂接到文件的私有数据上
err=file->f_op->open(inode,file);//判断能不能打开文件
if (err) {//不能打开
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
}
fops_put(old_fops);
fail:
mutex_unlock(&misc_mtx);
return err;
}
如果我们用面向对象的封装思想把一个设备的属性、自旋锁、互斥体、等待队列、 miscdevice等封装在一个结构体里面:
struct xxx_dev {
unsigned int version;
unsigned int size;
spinlock_t lock;
//...
struct miscdevice miscdev;
};
在file_operations的成员函数中,就可以通过container_of()和file->private_data反推出xxx_dev的实例
static long xxx_ioctl(struct file *file, unsigned int cmd, unsigned long i)
{
struct xxx_dev *xxx = container_of(file->private_data,struct xxx_dev, miscdev);
//...
}
蜂鸣器驱动:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <mach/hardware.h>
#include <plat/regs-timer.h>
#include <mach/regs-irq.h>
#include <asm/mach/time.h>
#include <linux/clk.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/miscdevice.h>
#include <linux/gpio.h>
#include <plat/gpio-cfg.h>
//#include <plat/regs-clock.h>
//#include <plat/regs-gpio.h>
//#include <plat/gpio-bank-e.h>
//#include <plat/gpio-bank-f.h>
//#include <plat/gpio-bank-k.h>
#define DEVICE_NAME "buzzer"
#define PWM_IOCTL_SET_FREQ 1
#define PWM_IOCTL_STOP 0
static struct semaphore lock;
// TCFG0在Uboot中设置,这里不再重复设置
// Timer0输入频率Finput=pclk/(prescaler1+1)/MUX1
// =66M/16/16
// TCFG0 = tcnt = (pclk/16/16)/freq;
// PWM0输出频率Foutput =Finput/TCFG0= freq
static void PWM_Set_Freq( unsigned long freq )
{
unsigned long tcon;
unsigned long tcnt;
unsigned long tcfg1;
struct clk *clk_p;
unsigned long pclk;
//unsigned tmp;
//设置GPD0_2为PWM输出
s3c_gpio_cfgpin(S5PV210_GPD0(2), S3C_GPIO_SFN(2));
tcon = __raw_readl(S3C2410_TCON);
tcfg1 = __raw_readl(S3C2410_TCFG1);
//mux = 1/16
tcfg1 &= ~(0xf<<8);
tcfg1 |= (0x4<<8);
__raw_writel(tcfg1, S3C2410_TCFG1);
clk_p = clk_get(NULL, "pclk");
pclk = clk_get_rate(clk_p);
tcnt = (pclk/16/16)/freq;
__raw_writel(tcnt, S3C2410_TCNTB(2));
__raw_writel(tcnt/2, S3C2410_TCMPB(2));//占空比为50%
tcon &= ~(0xf<<12);
tcon |= (0xb<<12); //disable deadzone, auto-reload, inv-off, update TCNTB0&TCMPB0, start timer 0
__raw_writel(tcon, S3C2410_TCON);
tcon &= ~(2<<12); //clear manual update bit
__raw_writel(tcon, S3C2410_TCON);
}
void PWM_Stop( void )
{
//将GPD0_2设置为input
s3c_gpio_cfgpin(S5PV210_GPD0(2), S3C_GPIO_SFN(0));
}
static int x210_pwm_open(struct inode *inode, struct file *file)
{
if (!down_trylock(&lock))
return 0;
else
return -EBUSY;
}
static int x210_pwm_close(struct inode *inode, struct file *file)
{
up(&lock);
return 0;
}
// PWM:GPF14->PWM0
static int x210_pwm_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
switch (cmd)
{
case PWM_IOCTL_SET_FREQ:
printk("PWM_IOCTL_SET_FREQ:\r\n");
if (arg == 0)
return -EINVAL;
PWM_Set_Freq(arg);
break;
case PWM_IOCTL_STOP:
default:
printk("PWM_IOCTL_STOP:\r\n");
PWM_Stop();
break;
}
return 0;
}
static struct file_operations dev_fops = {
.owner = THIS_MODULE,
.open = x210_pwm_open,
.release = x210_pwm_close,
.ioctl = x210_pwm_ioctl,
};
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &dev_fops,
};
static int __init dev_init(void)
{
int ret;
init_MUTEX(&lock);
ret = misc_register(&misc);
/* GPD0_2 (PWMTOUT2) */
ret = gpio_request(S5PV210_GPD0(2), "GPD0");
if(ret)
printk("buzzer-x210: request gpio GPD0(2) fail");
s3c_gpio_setpull(S5PV210_GPD0(2), S3C_GPIO_PULL_UP);
s3c_gpio_cfgpin(S5PV210_GPD0(2), S3C_GPIO_SFN(1));
gpio_set_value(S5PV210_GPD0(2), 0);
printk ("x210 "DEVICE_NAME" initialized\n");
return ret;
}
static void __exit dev_exit(void)
{
misc_deregister(&misc);
}
module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("www.9tripod.com");
MODULE_DESCRIPTION("x210 PWM Driver");