目录
一、板载蜂鸣器驱动测试
1、驱动部分
kernel\drivers\char\buzzer\x210-buzzer.c
(1)九鼎科技为S5PV210开发板移植的内核已经提供了蜂鸣器驱动源码(该内核源码包在往期的文章中已提供下载地址)
(2)make menuconfig:查看是否开启相关配置项
(3)bug排查。修改Makefile中的宏名,使得与Kconfig中的名字可以匹配,最终可以
在系统中看到 /dev/buzzer:
CONFIG_x210_BUZZER_DRIVER x210_BUZZER_DRIVER
Makefile文件中:
obj-$(CONFIG_x210_BUZZER_DRIVER) += x210-buzzer.o
Kconfig文件中:
config x210_BUZZER_DRIVER
bool "x210 buzzer driver"
default y
help
compile for buzzer driver,y for kernel,m for module.
buzzer驱动一般放在kernel/drivers/misc/目录下,但是九鼎官方移植的内核驱动并未按照这个方式去处理,其将该驱动放在了kernel/drivers/char/buzzer/x210-buzzer.c
2、应用部分
(1)应用编写:打开文件+ioctl
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
ioctl 是设备驱动程序中设备控制接口函数,一个字符设备驱动通常会实现设备打开、关闭、读、写等功能,在一些需要细分的情境下,如果需要扩展新的功能,通常以增设 ioctl() 命令的方式实现。
(2)测试实践
驱动文件:kernel/drivers/char/buzzer/x210-buzzer.c
#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; //以Hz为单位的时钟频率
//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);//输出值为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");
通过查看驱动文件中的一些命令码得到该设备的一些操作命令,如PWM_IOCTL_SET_FREQ(让蜂鸣器响)。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define DEVNAME "/dev/buzzer"
#define PWM_IOCTL_SET_FREQ 1 //参考内核驱动源码而来,x210-buzzer.c
#define PWM_IOCTL_STOP 0 //同上
int main(int argc, char *argv[])
{
int fd = -1;
fd = open(DEVNAME, O_RDWR);
if (fd < 0)
{
perror("open /dev/buzzer failed");
return -1;
}
ioctl(fd, PWM_IOCTL_SET_FREQ, 10000);//开,1000表示频率
sleep(3);
ioctl(fd, PWM_IOCTL_STOP);//关
sleep(3);
ioctl(fd, PWM_IOCTL_SET_FREQ, 2000);
sleep(3);
ioctl(fd, PWM_IOCTL_STOP);
sleep(3);
close(fd);
return 0;
}
二、misc类设备介绍
1、何为misc
(1)中文名:杂项设备\杂散设备(将没有明确分类的设备放在这里,对应class这个概念)
(2)/sys/class/misc
若想udev自动创建设备节点,这个设备必须属于一个类才可以
(3)典型的字符设备
杂散类设备是一个典型的字符设备,其本身作为一个字符设备注册到内核中,主设备号是固定的,次设备号是不同的用于区分不同的杂散类设备
(4)有一套驱动框架,内核实现一部分(kernel/drivers/char/misc.c),驱动实现一部分(kernel/drivers/char/buzzer/x210-buzzer.c),类似led的驱动框架
(5)misc是对原始的字符设备注册接口的一个类层次的封装,很多典型字符设备都可以归类到misc类中,使用misc驱动框架来管理。
2、misc类设备驱动架构
(1)内核开发者实现部分(misc.c),关键点有2个:一个是类的创建,另一个是开放给驱动开发者的接口
(2)具体设备驱动工程师实现部分(x210-buzzer.c)
3、本部分学习方法
(1)蜂鸣器驱动源码已有,分析为主
(2)复习并验证前面讲的驱动框架的思维
(3)有余力的不妨开始注意一些细节
三、misc驱动框架源码分析1
kernel/drivers/char/misc.c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
/*
* Head entry for the doubly linked miscdevice list
*/
static LIST_HEAD(misc_list);
static DEFINE_MUTEX(misc_mtx);
/*
* Assigned numbers, used for dynamic minors
*/
#define DYNAMIC_MINORS 64 /* like dynamic majors */
static DECLARE_BITMAP(misc_minors, DYNAMIC_MINORS);
#ifdef CONFIG_PROC_FS
static void *misc_seq_start(struct seq_file *seq, loff_t *pos)
{
mutex_lock(&misc_mtx);
return seq_list_start(&misc_list, *pos);
}
static void *misc_seq_next(struct seq_file *seq, void *v, loff_t *pos)
{
return seq_list_next(v, &misc_list, pos);
}
static void misc_seq_stop(struct seq_file *seq, void *v)
{
mutex_unlock(&misc_mtx);
}
static int misc_seq_show(struct seq_file *seq, void *v)
{
const struct miscdevice *p = list_entry(v, struct miscdevice, list);
seq_printf(seq, "%3i %s\n", p->minor, p->name ? p->name : "");
return 0;
}
static const struct seq_operations misc_seq_ops = {
.start = misc_seq_start,
.next = misc_seq_next,
.stop = misc_seq_stop,
.show = misc_seq_show,
};
static int misc_seq_open(struct inode *inode, struct file *file)
{
return seq_open(file, &misc_seq_ops);
}
static const struct file_operations misc_proc_fops = {
.owner = THIS_MODULE,
.open = misc_seq_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
#endif
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);
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->f_op = new_fops;
if (file->f_op->open) {
file->private_data = c;
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;
}
static struct class *misc_class;
static const struct file_operations misc_fops = {
.owner = THIS_MODULE,
.open = misc_open,
};
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);
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;
}
EXPORT_SYMBOL(misc_register);
EXPORT_SYMBOL(misc_deregister);
static char *misc_devnode(struct device *dev, mode_t *mode)
{
struct miscdevice *c = dev_get_drvdata(dev);
if (mode && c->mode)
*mode = c->mode;
if (c->nodename)
return kstrdup(c->nodename, GFP_KERNEL);
return NULL;
}
static int __init misc_init(void)
{
int err;
#ifdef CONFIG_PROC_FS //proc文件系统
proc_create("misc", 0, NULL, &misc_proc_fops);
#endif
misc_class = class_create(THIS_MODULE, "misc");//类的创建
err = PTR_ERR(misc_class); //错误校验
if (IS_ERR(misc_class))
goto fail_remove;
err = -EIO;
if (register_chrdev(MISC_MAJOR,"misc",&misc_fops))
goto fail_printk;
misc_class->devnode = misc_devnode;
return 0;
fail_printk:
printk("unable to get major %d for misc devices\n", MISC_MAJOR);
class_destroy(misc_class);
fail_remove:
remove_proc_entry("misc", NULL);
return err;
}
subsys_initcall(misc_init);//该宏会将该函数放到指定的段中,在开机的较早阶段被执行
//详情请查看往期的《驱动框架入门之LED》文章,有关于该宏的讲解
1、misc源码框架基础
(1)misc源码框架本身也是一个模块,内核启动时自动加载,和led驱动框架类似
(2)源码框架的主要工作:注册misc类,使用老接口注册字符设备驱动(主设备号10),开放device注册的接口misc_register给驱动工程师
static int __init misc_init(void)
{
int err;
#ifdef CONFIG_PROC_FS //proc文件系统
proc_create("misc", 0, NULL, &misc_proc_fops);
#endif
misc_class = class_create(THIS_MODULE, "misc");//类的创建
err = PTR_ERR(misc_class); //错误校验
if (IS_ERR(misc_class))
goto fail_remove;
err = -EIO;
if (register_chrdev(MISC_MAJOR,"misc",&misc_fops))//MISC_MAJOR是10
goto fail_printk;
misc_class->devnode = misc_devnode;
return 0;
fail_printk:
printk("unable to get major %d for misc devices\n", MISC_MAJOR);
class_destroy(misc_class);
fail_remove:
remove_proc_entry("misc", NULL);
return err;
}
subsys_initcall(misc_init);//该宏会将该函数放到指定的段中,在开机的较早阶段被执行
//详情请查看往期的《驱动框架入门之LED》文章,有关于该宏的讲解
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;
};
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);
out:
mutex_unlock(&misc_mtx);
return err;
}
所有的misc类设备使用同一个主设备号,有着不同的次设备号
/proc与/sys是内核提供的查看、使用驱动的两个不同的路径
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) }
内核最多支持255个字符类设备。
(3)主设备号和次设备号的作用和区分
misc类设备共用一个主设备号,它们用不同的次设备号区分不同的设备.
(4)linux内核中的位图bitmap,用于表示相应的次设备号是否被使用,使用0/1表示该位的次设备号是否被使用,find_first_zero_bit()函数找到哪一个为0则表示其可以被使用。
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);
3、open函数分析
kernel/drivers/char/misc.c
static const struct file_operations misc_fops = {
.owner = THIS_MODULE,
.open = misc_open,
};
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);
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->f_op = new_fops;
if (file->f_op->open) {
file->private_data = c;
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;
}
上边的通过misc_open函数最终映射到:
kernel\drivers\char\buzzer\x210-buzzer.c
s tatic struct file_operations dev_fops = {
.owner = THIS_MODULE,
.open = x210_pwm_open,
.release = x210_pwm_close,
.ioctl = x210_pwm_ioctl,
};
4、内核互斥锁
(1)何为互斥锁
最常使用于线程同步的锁;标记用来保证在任一时刻,只能有一个线程访问该对象,同一线程多次加锁操作会造成死锁;临界区和互斥量都可用来实现此锁,通常情况下锁操作失败会将该线程睡眠等待锁释放时被唤醒
(2)定义:DEFINE_MUTEX()
static DEFINE_MUTEX(misc_mtx);
(3)上锁mutex_lock和解锁mutex_unlock
mutex_lock(&misc_mtx);
void __sched mutex_lock(struct mutex *lock)
{
might_sleep();
/*
* The locking fastpath is the 1->0 transition from
* 'unlocked' into 'locked' state.
*/
__mutex_fastpath_lock(&lock->count, __mutex_lock_slowpath);
mutex_set_owner(lock);
}
mutex_unlock(&misc_mtx);
void __sched mutex_unlock(struct mutex *lock)
{
/*
* The unlocking fastpath is the 0->1 transition from 'locked'
* into 'unlocked' state:
*/
#ifndef CONFIG_DEBUG_MUTEXES
/*
* When debugging is enabled we must not clear the owner before time,
* the slow path will always be taken, and that clears the owner field
* after verifying that it was indeed current.
*/
mutex_clear_owner(lock);
#endif
__mutex_fastpath_unlock(&lock->count, __mutex_unlock_slowpath);
}
(4)内核防止竞争状态的手段:原子访问、自旋锁、互斥锁、信号量
(5)原子访问主要用来做计数、互斥锁和信号量很相似(其实就是计数值为1的信号量),互斥锁的出现比信号量晚,实现上比信号量优秀,尽量使用互斥锁。
原子访问(Atomic Access) 在编程中,原子操作是指在一次发生中生效。一个原子操作不能在中途停止:它要么完整发生,要么不发生。
原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)
四、蜂鸣器驱动源码分析
kernel/drivers/char/buzzer/x210-buzzer.c
1、dev_init
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);//输出值为0
printk ("x210 "DEVICE_NAME" initialized\n");
return ret;
}
(1)信号量
kernel/include/linux/semaphore.h
kernel/kernel/semaphore.c
extern void down(struct semaphore *sem);//对信号量的操作,上锁
extern int __must_check down_interruptible(struct semaphore *sem);//在等待状态可被信号打断
extern int __must_check down_killable(struct semaphore *sem);//在等待状态可被杀死
extern int __must_check down_trylock(struct semaphore *sem);//以非阻塞的方式去获取锁
extern int __must_check down_timeout(struct semaphore *sem, long jiffies);//
extern void up(struct semaphore *sem);//对信号量的操作,解锁
(2)miscdevice
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &dev_fops,
};
(3)gpio_request
/* 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);//输出值为0
(4)printk
printk ("x210 "DEVICE_NAME" initialized\n");
2、ioctl(input output control,输入输出控制)
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
RETURN VALUE
Usually, on success zero is returned. A few ioctl() requests use
the return value as an output parameter and return a nonnegative value on
success. On error, -1 is returned, and errno is set appropriately.
3、硬件操作有关的代码
#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; //以Hz为单位的时钟频率
//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,
};
注:本资料大部分由朱老师物联网大讲堂课程笔记整理而来并且引用了部分他人博客的内容,如有侵权,联系删除!水平有限,如有错误,欢迎各位在评论区交流。