驱动开发之PWM输出控制(设备树详解版)

在做项目的过程中自己找资料摸索出了一套适合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);

驱动代码都有很详细的说明了,用户测试代码也很简单。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值