本次PWM内核驱动主要设计思路:
(1)硬件控制(相关寄存器读写、IO映射)、模块函数(加载、卸载、ioctl)分开
(2)相关声明都放在头文件中
以下是beep_drv.c代码,里面存放的都是模块函数
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/kdev_t.h> //设备号相关操作头文件
#include <linux/device.h> //class相关头文件
#include "fs4412_beep.h"
#include "beep_drv.h"
//定义beep蜂鸣器主次设备号
#define BEEP_MAJOR 251 //主设备号
#define BEEP_MINOR 0 //次设备号
//定义操作命令,这里注意命令只是和用户调用程序协定的,方便大家在应用层和底层保持一致
#define MAGIC_NUM 'k' //设备类型
#define BEEP_ON _IO(MAGIC_NUM, 0) //打开蜂鸣器指令
#define BEEP_OFF _IO(MAGIC_NUM, 1) //关闭蜂鸣器指令
#define BEEP_FREQ _IO(MAGIC_NUM, 2) //设置蜂鸣器频率指令
//定义beep相关属性对象
struct beep_cdev my_beep;
//定义beep操作方法集
static long beep_ioctl (struct file *filep, unsigned int cmd, unsigned long arg);
static int beep_open(struct inode *inode, struct file *filep);
static int beep_release(struct inode *inode, struct file *filep);
static ssize_t beep_read(struct file *, char __user *, size_t, loff_t *);
static struct file_operations beep_ops={
.open = beep_open,
.read = beep_read,
.release = beep_release,
.unlocked_ioctl = beep_ioctl,
};
//beep的ioctl控制函数
static long beep_ioctl (struct file *filep, unsigned int cmd, unsigned long arg)
{
switch(cmd)
{
case BEEP_ON:
fs4412_beep_open();
break;
case BEEP_OFF:
fs4412_beep_close();
break;
case BEEP_FREQ:
fs4412_beep_freq(arg);
break;
default:
fs4412_beep_close();
break;
}
return 0;//返回结果
}
//beep蜂鸣器打开函数
static int beep_open(struct inode *inode, struct file *filep)
{
//返回文件操作操作符
printk("beep open success!\n");
return 0;
}
//beep蜂鸣器打开函数
static ssize_t beep_read (struct file *filep, char __user *buf, size_t size, loff_t * off_set)
{
//返回文件操作操作符
return 0;
}
//beep蜂鸣器注销函数
static int beep_release(struct inode *inode, struct file *filep)
{
fs4412_beep_close();
return 0;
}
static int __init beep_drv_init(void)
{
int ret;
printk("beep init!\n");
//1、申请设备号和cdev对象
my_beep.dev_num = MKDEV(BEEP_MAJOR, BEEP_MINOR); //组设备号
//2、注册设备号和cdev初始化
ret = register_chrdev(BEEP_MAJOR, "beep number", &beep_ops);
if(ret) //注册失败
{
printk(KERN_ERR"register_chrdev err!\n");
goto reg_err;
}
//3、创建设备节点
my_beep.cls = class_create(THIS_MODULE, "beep class");
if(IS_ERR(my_beep.cls)) //分配失败
{
printk(KERN_ERR"class create err!\n");
goto class_create_err;
}
my_beep.test_device = device_create(my_beep.cls, NULL, my_beep.dev_num, NULL, "fs4412_beep");
if(IS_ERR(my_beep.test_device))//device创建失败
{
printk(KERN_ERR"device create err!\n");
goto device_create_err;
}
//4、beep蜂鸣器硬件初始化
fs4412_beep_init();
return 0;
device_create_err:
//销毁class
class_destroy(my_beep.cls);
class_create_err:
//注销设备号和销毁cdev
unregister_chrdev(my_beep.dev_num, "beep number");
return -EINVAL;
reg_err:
return ret;
}
static void __exit beep_drv_exit(void)
{
//1、解除映射
fs4412_beep_unmap();
//2、销毁节点
device_destroy(my_beep.cls, my_beep.dev_num);
class_destroy(my_beep.cls);
//3、注销设备号和回收cdev
unregister_chrdev(BEEP_MAJOR, "beep number" );
printk("beep exit!\n");
}
module_init(beep_drv_init);
module_exit(beep_drv_exit);
MODULE_LICENSE("GPL");
以下是beep_drv.h头文件:
#include <linux/kdev_t.h>
#include <linux/fs.h>
#define BEEP_IN_FREQ 100000//蜂鸣器定时器的频率
struct beep_cdev{
dev_t dev_num;
struct class *cls; //用来在udev自动创建节点号的时候使用
struct device *test_device; //用来创建设备节点
unsigned int *gpd0con;//用来存放关于引脚相关的寄存器虚拟地址,这里直接指定类型是因为就一个寄存器长度肯定是4字节
void *timer_base;//指向定时器相关寄存器的第一个寄存器基地址
};
extern struct beep_cdev my_beep;
然后是fs4412_beep.c关于寄存器和IO的内容:
#include <asm/io.h>
#include "beep_drv.h"
#define TIMER_REGISTER_CNT 5
#define REGISTER_SIZE 4
//寄存器基地址
#define GPD0CON 0x114000a0 //引脚GPD0的控制寄存器
#define TIMER_BASE 0x139D0000 //控制PWM的定时器基地址
//寄存器地址相对偏移
#define TCFG0 0x0000 //寄存器TCFG0针对TIMER_BASE的偏移量,该寄存器用来设置定时器
#define TCFG1 0x0004 //寄存器TCFG1针对TIMER_BASE的偏移量
#define TCON 0x0008 //寄存器TCON针对TIMER_BASE的偏移量 ,该寄存器用来控制定时器
#define TCNTB0 0x000C //寄存器TCNTB0针对TIMER_BASE的偏移量 ,该寄存器用来控制周期
#define TCMPB0 0x0010 //寄存器TCMPB0针对TIMER_BASE的偏移量,该寄存器用来控制占空比
/*
蜂鸣器初始化函数:fs4412_beep_init
*/
void fs4412_beep_init(void)
{
//1、IO虚拟地址映射
my_beep.gpd0con = ioremap(GPD0CON, 4);
my_beep.timer_base = ioremap(TIMER_BASE, TIMER_REGISTER_CNT*REGISTER_SIZE);
//2、配置IO引脚GPD0_0引脚
writel((readl(my_beep.gpd0con) & ~(0xf<<0))|(2<<0), my_beep.gpd0con);
//3、设置一级分频(249)和二级分频(4)
writel((readl(my_beep.timer_base + TCFG0) & ~(0xff<<0)) | (249<<0), my_beep.timer_base + TCFG0); //一级分频249
writel((readl(my_beep.timer_base + TCFG1) & ~(0xf<<0)) | (2<<0), my_beep.timer_base + TCFG1);//二级分频4
//4、设置周期和占空比
writel(100, my_beep.timer_base + TCNTB0);//周期为100
writel(50, my_beep.timer_base + TCMPB0);//占空比为50%
//5、打开手动
writel((readl(my_beep.timer_base + TCON) | (1<<1)), my_beep.timer_base + TCON);//手动装载周期和占空比
//6、关闭手动开启自动装载
writel((readl(my_beep.timer_base + TCON) & ~(1<<1)) | (1<<3), my_beep.timer_base + TCON);
//7、默认关闭定时器
writel(readl(my_beep.timer_base + TCON) & ~(1<<0), my_beep.timer_base + TCON);
}
//蜂鸣器开启接口
void fs4412_beep_open(void)
{
printk("beep_open!\n");
//打开定时器
writel(readl(my_beep.timer_base + TCON) | (1<<0), my_beep.timer_base + TCON);
}
//关闭蜂鸣器
void fs4412_beep_close(void)
{
printk("beep_close!\n");
//关闭定时器
writel(readl(my_beep.timer_base + TCON) & ~(1<<0), my_beep.timer_base + TCON);
}
//关闭蜂鸣器
void fs4412_beep_change(void)
{
//关闭定时器
writel(readl(my_beep.timer_base + TCON) ^ (1<<0), my_beep.timer_base + TCON);
}
//设置蜂鸣器频率
void fs4412_beep_freq(unsigned long arg)
{
//蜂鸣器设置周期,占空比默认是50%
writel(BEEP_IN_FREQ/arg, my_beep.timer_base + TCNTB0);//设置周期
writel(BEEP_IN_FREQ/(arg*2), my_beep.timer_base + TCMPB0); //50%占空比
}
//解除IO映射
void fs4412_beep_unmap(void)
{
//解除IO引脚寄存器
iounmap(my_beep.gpd0con);
//解除定时器寄存器映射
iounmap(my_beep.timer_base);
}
fs4412_beep.h头文件代码如下:
void fs4412_beep_init(void);
void fs4412_beep_open(void);
void fs4412_beep_close(void);
void fs4412_beep_freq(unsigned long arg);
void fs4412_beep_unmap(void);
void fs4412_beep_change(void);
最后是Makefile的内容:
APP_NAME = app_beep
APP_OBJ = app_beep.c
MODULE_NAME = beep
PWD = $(shell pwd)
CC = arm-none-linux-gnueabi-gcc
APP_INSTALL_DIR = /source/rootfs/my_modules
MODULE_INSTALL_DIR = /source/rootfs/my_modules
EXTRA_CFLAGS += -I$(src)/include
ifeq ($(KERNELRELEASE),)
KER_DIR = /home/linux/kernel/linux-3.14-fs4412
modules:
make -C $(KER_DIR) M=$(PWD) modules
$(CC) app_beep.c -o $(APP_NAME)
clean:
make -C $(KER_DIR) M=$(PWD) clean
rm -rf *.ko $(APP_NAME) *.o *.mod.* modules.* Module.*
rm $(APP_INSTALL_DIR)/$(APP_NAME)
rm $(MODULE_INSTALL_DIR)/$(MODULE_NAME).ko
install:
cp $(MODULE_NAME).ko $(MODULE_INSTALL_DIR)
cp $(APP_NAME) $(APP_INSTALL_DIR)
else
obj-m += $(MODULE_NAME).o
$(MODULE_NAME)-y := beep_drv.o fs4412_beep.o
endif
根据以上代码编写的简单测试代码:
#include <stdio.h>
#include <fcntl.h> //和文件操作相关的
#include <sys/ioctl.h>//用来组件命令字的
//组件操作beep的,命令字,这里一定要和驱动中的命令字一致
#define MAGIC_NUM 'k'
#define BEEP_ON _IO(MAGIC_NUM, 0)
#define BEEP_OFF _IO(MAGIC_NUM, 1)
#define BEEP_FREQ _IO(MAGIC_NUM, 2)
int main(int argc, const char *argv[])
{
int fd;
//1、打开beep
fd = open("/dev/fs4412_beep", O_RDWR);
if(fd<0)
{
perror("open fail!\n");
return 2;
}
//2、打开蜂鸣器
ioctl(fd, BEEP_ON);
sleep(6);
//3、关闭蜂鸣器
ioctl(fd, BEEP_OFF);
sleep(6);
//关闭beep
close(fd);
return 0;
}