在做项目的过程中自己找资料摸索出了一套适合PWM驱动的编写方法,下面让我来带大家从头开始编写PWM驱动吧!
首先附上设备树
beep@139D0000{
compatible= "fs4412,beep";
reg=<0x139D0000 0x24>;
clocks = <&clock 336>;
clock-names = "timers";
pinctrl-0 = <&pwm0_out>;
pinctrl-names = "default";
};
我用PWM控制蜂鸣器,139D0000是PWM寄存器的基地址,如下图:
想必到这里beep@139D0000大家应该知道是什么意思了。
接下来就是compatible属性,这个是在写驱动代码的时候匹配要用到的,到时候看下面的驱动代码就会知道。
reg属性就是寄存器的其实地址,加上所有寄存器的长度0x24就是36个字节也就是9个寄存器 clocks属性是PWM要用到的时钟编号就是336,具体可以在linux源码目录下的Documentation/devicetree/bindings/clock/exynos4-clock.txt可以找到(我用的芯片是exynos4,具体芯片看你自己的)如下图:
接下来的clock-names属性的值是"timers",这个值在驱动代码中获取时钟频率的时候也需要用到,要记住了!!
pinctrl-0的属性定义后就可以在驱动代码中使用pinctrl子系统的API(Pinctrl(Pin Control)子系统是一个用于管理和控制硬件引脚的框架),需要注意的是这个值直接填默认就行。
到此为止,相信大家已经对设备树有一个比较好的理解了。
接下来就是驱动代码的分析。
先附上代码。代码都有详细的注释。看驱动代码前先补充一点。
设备树的机制其实也是总线型的 BUS/Dev/Drv 模型,只是编写 Dev 的方式变了。即编写 设备树文件 .dts。dst 文件会被编译成 dtb 文件。dtb文件会传给内核, 内核会解析dtb文件, 构造出一系列的 device_node 结构体,
device_node 结构体会转换为 platform_device 结构体。
如果 “of_node 中的 compatible” 跟 “of_match_table 中的 compatible” 一致, 就表示匹配成功, 则调用 platform_driver中的probe函数; 在probe函数中, 可以继续从of_node中获得各种属性来确定硬件资源。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/io.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/clk.h>
#include <linux/pinctrl/consumer.h>
#include <linux/atomic.h>
#include "beep.h"
int major=11;
int minjor=0;
#define GPD0CON 0x114000a0
static unsigned int *gpd0con;
struct my_dev
{
struct cdev mydev;
struct class * myclass;
struct device * mydevice;
unsigned int __iomem *tfg0;//寄存器需要用在这个关键字修饰
unsigned int __iomem *tfg1;
unsigned int __iomem *tcon;
unsigned int __iomem *tcntb0;
unsigned int __iomem *tcmpb0;
struct clk*clk;//时钟子系统需要用到的指针
struct pinctrl * pctrl;//pinctrl需要用到的指针
unsigned long freq;//用于获取时钟频率
atomic_t available;
};
struct my_dev * pmydev=NULL;
int beep_open(struct inode*pinode, struct file * pfile )
{
printk("open\n");
pfile->private_data=(void *)(container_of(pinode->i_cdev,struct my_dev,mydev));
/*
pinode->i_cdev其实就是stru cdev结构体对象的地址(其实就是指向stru cdev结构体)
而我们以及定义了struct cdev mydev; struct my_dev,就可以用container_of这个宏来获取到struct my_dev这个结构体的地址
然后用pfile->private_data这个(void *)类型的成员指向它,注意在后面的write、read、ioctl等函数都可以用它了
*/
return 0;
}
int beep_close(struct inode * pinode,struct file * pfile)
{
return 0;
}
long beep_ioctl(struct file * pfile,unsigned int cmd,unsigned long arg)
{
struct my_dev *pmdev=(struct my_dev*)pfile->private_data;//!!注意这里就用到了!!!!!!container_of宏得到的东西
unsigned int div=50;
switch(cmd)
{
case BEEP_OPEN:
{
writel((readl(pmdev->tcon)|0x1),pmdev->tcon);
break;
}
case BEEP_CLOSE:
{
writel((readl(pmdev->tcon)&(~0x1)),pmdev->tcon);
break;
}
case BEEP_BIG:
{
writel(div,pmydev->tcntb0);
writel(div/2,pmydev->tcmpb0);
break;
}
default:
break;
}
return 0;
}
struct file_operations ops =
{
.owner=THIS_MODULE,//这个一定要写,表示属于当前模块
.open=beep_open,
.release=beep_close,
.unlocked_ioctl=beep_ioctl,
};
static int driver_probe(struct platform_device *pdev)
{
int ret;
dev_t dev=MKDEV(major,minjor); //将主设备号和次设备号合成dev这个设备号(32位)
struct resource *res;
unsigned int per0;
ret=register_chrdev_region(dev,1,"beep");//手动注册设备号
if(ret<0)
{
ret=alloc_chrdev_region(&dev,0,0,"beep");//自动注册设备号
if(ret<0)
{
major=MAJOR(dev);
return -1;
}
major=MAJOR(dev);//如果错误就分离,别遗漏
}
pmydev=(struct my_dev*)kmalloc(sizeof(struct my_dev),GFP_KERNEL);//使用slab算法分配的内存,地址是连续的,操作比较快
if(pmydev==NULL)
{
goto mem_err;
printk("register error\n");
return -1;
}
memset(pmydev,0,sizeof(struct my_dev));
pmydev->myclass=class_create(THIS_MODULE,"my_beep");//自动mknod的第一步,在/sys/class目录下创建my_beep
if(IS_ERR(pmydev->myclass)!=0)
{
goto class_err;
printk("%ld\n",PTR_ERR(pmydev->myclass));
return -1;
}
pmydev->mydevice=device_create(pmydev->myclass,NULL,dev,NULL,"beep");
//自动mknod的最后一步,在在/sys/class目录下的my_beep下载创建beep,
//同时在/dev下mknod beep
if(pmydev->mydevice==NULL)
{
printk("device_create error\n");
goto device_err;
}
platform_set_drvdata(pdev,pmydev);//用于将动态分配的pmydev和设备平台相关联,以后可以用到
cdev_init(&pmydev->mydev,&ops);//将函数操作集绑定
if(cdev_add(&pmydev->mydev,dev,1))//将cdev对象假如内核中(以散列表加入)
{
goto add_err;
}
res=platform_get_resource(pdev,IORESOURCE_MEM,0);//用户获取平台资源
/*
struct resource
{
resource_size_t start; //资源起始位置
resource_size_t end; //资源结束位置
const char *name;
unsigned long flags; //区分资源是什么类型的
};
flags为IORESOURCE_MEM 时,start 、end
分别表示该platform_device占据的内存的开始地址和结束值
*/
if(!res)
{
printk("resource error");
goto res_err;
return -1;
}
pmydev->tfg0=ioremap(res->start,resource_size(res));//计算了资源的地址,资源的结束地址减去资源的起始地址,并加上 1。
if(!pmydev->tfg0)
{
goto mem_err;
}
pmydev->tfg1=pmydev->tfg0+1;//加一就是移动了4个字节的大小,因为tfg0是unsigned int *,
//寄存器从小到大分别是tfg0、tgf1等,所以按顺序1加即可
pmydev->tcon=pmydev->tfg0+2;
pmydev->tcntb0=pmydev->tfg0+3;
pmydev->tcmpb0=pmydev->tfg0+4;
/*
struct platform_device pdev
{
const char *name; //匹配用的名字
Int id;//设备id,用于在该总线上同名的设备进行编号,如果只有一个设备,则为-1
struct device dev; //设备模块必须包含该结构体
struct resource *resource;//资源结构体 指向资源数组
u32 num_resources;//资源的数量 资源数组的元素个数
const struct platform_device_id *id_entry;//设备八字
};
struct device dev
{
struct bus_type *bus; //总线类型
dev_t devt; //设备号
struct device_driver *driver; //设备驱动
struct device_node *of_node;//设备树中的节点,重要
void (*release)(struct device *dev);//删除设备,重要
//.......
};
*/
pmydev->clk=clk_get(&pdev->dev,"timers");//设备树中的时钟名字就是timers,获取名字为id的时钟
if(IS_ERR(pmydev->clk))
{
goto get_clk_err;
printk("clk error");
}
ret=clk_prepare_enable(pmydev->clk);//使能时钟(pwm用的是pclk时钟)
if(ret<0)
{
goto enable_clk_err;
}
pmydev->freq=clk_get_rate(pmydev->clk);//获取clk时钟频率
pmydev->pctrl=devm_pinctrl_get_select_default(&pdev->dev);//将对应的引脚空设置为PWM0输出
writel((readl(pmydev->tfg0)& (~0xFF))| 0xF9,pmydev->tfg0);//获得预分频值,设置倍数为100倍
per0=readl(pmydev->tfg0)&0xFF;
writel(((readl(pmydev->tfg1)& ~0xF)|0x2),pmydev->tfg1);//二级分频倍数为1。则递减计数器递减频率为 PLCK/(99+1)/1=1MHZ;
pmydev->freq/=(per0+1)*16;
writel(100,pmydev->tcntb0);//计数器的值
writel(50,pmydev->tcmpb0);//计数器比较的值,也就是说占空比是50
writel((readl(pmydev->tcon))|(0x1<<3),pmydev->tcon);//将计数器变为自动重装载
writel((readl(pmydev->tcon)| 1<<1),pmydev->tcon);//手动装载
writel((readl(pmydev->tcon) & (~(1<<1))),pmydev->tcon);//手动清除装载,!!writel((readl(pmydev->tcon) | (~(1<<1))),pmydev->tcon)
writel((readl(pmydev->tcon)|0x1),pmydev->tcon);//启动pwm
return 0;
enable_clk_err:
clk_put(pmydev->clk);
get_clk_err:
iounmap(pmydev->tfg0);
map_err:
device_err:
class_destroy(pmydev->myclass);
class_err:
device_destroy(pmydev->myclass,dev);
res_err:
cdev_del(&pmydev->mydev);
add_err:
kfree(pmydev);
pmydev=NULL;
mem_err:
unregister_chrdev_region(dev,0);
reg_err:
return ret;
}
static int driver_remove(struct platform_device *pdev)
{
dev_t dev;
struct my_dev *pmydev;
dev=MKDEV(major,minjor);
pmydev=(struct my_dev*)platform_get_drvdata(pdev);
clk_disable_unprepare(pmydev->clk);
clk_put(pmydev->clk);
iounmap(pmydev->tfg0);
device_destroy(pmydev->myclass,dev);
class_destroy(pmydev->myclass);
cdev_del(&pmydev->mydev);
unregister_chrdev_region(dev,1);
kfree(pmydev);
pmydev=NULL;
return 0;
}
struct of_device_id id_table[]={ //设备树匹配必须要用这个
[0]={.compatible="fs4412,beep"},
[1]={.compatible="qwe,qwe"},
[2]={},
};
struct platform_driver beep_driver={
.probe=driver_probe,
.remove=driver_remove,
.driver={
.name="fs4412ex",//设备树匹配也要用名字,虽然用不到它。但是也要写
.of_match_table=id_table, //设备树匹配
},
};
module_platform_driver(beep_driver);//一个宏代替了函数入口函数和函数出口函数所需的操作
MODULE_LICENSE("GPL");
//module_init(beep_init);
//module_exit(beep_exit);
驱动代码都有很详细的说明了,用户测试代码也很简单。