Linux利用platform_driver和设备树实现PWM驱动

本文详细介绍了如何在Linux环境下利用platform_driver和设备树实现PWM驱动,涉及PWM工作原理、电路结构、设备树配置及关键代码示例,包括头文件、设备注册、驱动函数和操作蜂鸣器的方法。


字符设备PWM驱动

Linux环境下利用platform_driver和设备树实现PWM驱动,此文以开发板FS4412为例。


一、PWM驱动的硬件资源

PWM,脉宽调制器,顾名思义就是一个输出脉冲宽度可以调整的硬件器件,它不仅脉冲宽度可调,频率也可以调整,它的核心部件是一个硬件定时器。

1.PWM工作原理

PWM管脚默认输出高电平,在图1中的时刻1将计数值设为 109,比较值设为109,在时刻2启动定时器,PWM立即输出低电平,在时钟的作用下,计数器开始做减法计数,当计数值减到和比较值一致时(时刻3),输出翻转,之后一直输出高电平。当计数到达0后(时刻4),再完成一次计数,在时刻5重新从109开始计数,输出再次变成低电平,如此周而复始就形成一个矩形波。波形的周期由计数值决定,占空比由比较值决定。在图1中,占空比为110/160,如果用于计数的时钟频率为freq,那么波形的频率就为freq/160。
图1

2.PWM电路原理

通过查找原理图,FS4412使用了其中一路PWM输出(PWM0,对应管脚为GPD0.0)接蜂鸣器,如图2
图2

3.PWM内部结构

PWM内部结构图如图3,PWM的输入时钟是PCLK,经讨8位的预分频后再经过第二次分频的时钟最终给到PWM0所对应的计数器0.TCNTB0是计数值寄存器,用于控制PWM输出波形的频率,
TCMPB0是比较寄存器,用于控制PWM输出波形的占空比。
图3

二、具体代码

1.设备树

  1. 修改设备树,与驱动程序进行匹配 {.compatible = “fs,mybee”},然后驱动程序,才能通过设备树获取硬件资源
  2. 设备树修改后需要重新编译,并下载到开发板运行,通过ls /proc/device-tree/ 可以在系统中查看获取的设备树信息

/linux-3.14/arch/arm/boot/dts下对应的设备树文件添加之后重新编译

mybee@11000ca0{
        compatible ="fs,mybee";
        reg = <0x114000A0 0x4>,<0x139d0000 0x14>;
    }; 

2.应用程序代码

代码如下(示例):

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#define BUZZER_ON  _IO('B',1)
#define BUZZER_OFF _IO('B',2)
int main(void)
{
    int fd;
	int ret;
    fd = open("/dev/buzzer",O_RDWR);
    if(fd==-1){
        perror("open");
        return -1;
    }
    //printf("open device success %d\n",fd);
    while(1){
        ioctl(fd,BUZZER_ON);
        sleep(1);
        ioctl(fd,BUZZER_OFF);
        sleep(1);
    } 
    close(fd);
    return 0;
}

3.驱动程序代码

头文件和命令

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/err.h>
#include <asm/io.h>
#define BUZZER_ON  _IO('B',1)
#define BUZZER_OFF _IO('B',2)//需要与应用程序一致

提前准备好硬件相关的寄存器

#define GPD0CON    0x114000A0 
#define TCFG0      0x139D0000 
#define TCFG1      0x139D0004 
#define TCON       0x139D0008
#define TCNTB0     0x139D000C
#define TCMPB0     0x139D0010
unsigned int *gpd0con;
unsigned int *tcfg0;
unsigned int *tcfg1;
unsigned int *tcon;
unsigned int *tcntb0;
unsigned int *tcmpb0;

定义相关的函数和需要用到的变量

//实现platform_driver
int my_probe(struct platform_device *pdev);
int my_remove(struct platform_device *pdev);
long my_ioctl(struct file *pf, unsigned int cmd, unsigned long args);

static struct resource *rescon;
static struct resource *resdata;//设备资源
static dev_t devnum;//申请设备号时使用
static char *name ="mybuzzer";//设备名
static struct cdev mycdev ;//字符设备
static struct class * myclass;
static struct device * mydevice;//设备节点
static struct file_operations myops={
    .unlocked_ioctl = my_ioctl,
};
int buzzer_init(void);//操作函数
int buzzer_on(void);
int buzzer_off(void);
//定义platform_driver对象
struct of_device_id of_matches[]={
    {.compatible="fs,mybee"},           //修改匹配规则,从设备树  
    {},                                 //获取buzzer相关硬件信息
};
static struct platform_driver mydriver ={
    .probe = my_probe,
    .remove = my_remove,
    .driver = {
        .name = "mytest",               
        .of_match_table =  of_matches,  //通过设备树匹配
    },    
};  

对蜂鸣器相关的操作函数

int buzzer_init(void)//注册
{
    u32 tmp;
    tmp = readl(gpd0con);
    tmp &= ~0xf;
    tmp |= 0x2;
    writel(tmp,gpd0con);
   
    tmp = readl(tcfg0);
    tmp |= 0xff;
    writel(tmp,tcfg0);

    tmp = readl(tcfg1);
    tmp &= ~0xf;
    tmp |= 0x3;
    writel(tmp,tcfg1);

    writel(110,tcntb0);
    writel(110/2,tcmpb0);

    tmp = readl(tcon);
    tmp |= 0x1<<3;
    tmp |= 0x1<<1;
    writel(tmp,tcon);

    tmp = readl(tcon);
    tmp &= ~(0x1<<1);
    writel(tmp,tcon);
    return 0;
}
int buzzer_on(void)//打开
{
    u32 tmp;
    printk("buzzer_on\n");
    tmp = readl(tcon);
    tmp |= 0x1;
    writel(tmp,tcon);
    return 0;
}
int buzzer_off(void)//关闭
{
    u32 tmp;
    printk("buzzer_off\n");
    tmp = readl(tcon);
    tmp &= ~0x1;
    writel(tmp,tcon);
    return 0;
}

平台驱动相关函数

int my_probe(struct platform_device *pdev)//与设备树匹配成功执行
{
    int ret;
    //通过设备树获取 硬件资源
    printk("match\n");

    rescon = platform_get_resource(pdev,IORESOURCE_MEM,0);
    if(rescon==NULL){
        return -1;
    }
    printk("%#x\n",rescon->start);
    gpd0con = ioremap(rescon->start,4);

    resdata = platform_get_resource(pdev,IORESOURCE_MEM,1);
    if(resdata==NULL){
        return -1;
    }
    printk("%#x\n",resdata->start);
    tcfg0 = ioremap(resdata->start,4);

    //gpd0con = ioremap(GPD0CON,4);
    //tcfg0 = ioremap(TCFG0,4);
    tcfg1 = ioremap(resdata->start+4,4);
    tcon = ioremap(resdata->start+8,4);
    tcntb0 = ioremap(resdata->start+12,4);
    tcmpb0 = ioremap(resdata->start+16,4);
    //字符设备注册
    ret =  alloc_chrdev_region(&devnum,0,1,name);//1.申请设备号
    if(ret!=0){
        goto failed_alloc;
    }
    cdev_init(&mycdev,&myops); //2.cdev初始化
    ret = cdev_add(&mycdev,devnum,1); //3.cdev添加到内核
    if(ret!=0){
        goto failed_add;
    }
    printk("register success %d,%d\n",MAJOR(devnum),MINOR(devnum));
    myclass = class_create(THIS_MODULE,"myclass");
    if(IS_ERR(myclass)){
        goto failed_class;
    }
    mydevice = device_create(myclass,NULL,devnum,NULL,"buzzer");
    if(IS_ERR(mydevice)){
        goto failed_device;
    }
    //硬件操作
    buzzer_init();
    buzzer_off();
    return 0;
failed_device://失败之后的对应操作
    class_destroy(myclass);
failed_class:
    cdev_del(&mycdev);
failed_add:
    unregister_chrdev_region(devnum,1);
failed_alloc:
    return -1;
}
int my_remove(struct platform_device *pdev)//卸载时执行
{
    printk("driver remove\n");
    iounmap(gpd0con);//取消映射
    iounmap(tcfg0);
    iounmap(tcfg1);
    iounmap(tcon);
    iounmap(tcntb0);
    iounmap(tcmpb0);

    device_destroy(myclass,devnum);
    class_destroy(myclass);
    cdev_del(&mycdev);
    unregister_chrdev_region(devnum,1);
    return 0;
}
long my_ioctl(struct file *pf, unsigned int cmd, unsigned long args)//当应用层调用IO操作时就会调用到此函数
{
    switch (cmd)
    {
        case BUZZER_ON:  buzzer_on(); break;
        case BUZZER_OFF: buzzer_off();break;
        default:  return -1;
    }
    return 0;
}
static int mod_init(void)//模块加载函数
{
    return  platform_driver_register(&mydriver);  //平台驱动注册
}
static void mod_exit(void)//模块卸载函数
{
    platform_driver_unregister(&mydriver);        //平台驱动注销
}
module_init(mod_init);//注册到内核
module_exit(mod_exit);//卸载到内核
MODULE_LICENSE("GPL");//模块信息
<think>嗯,用户想了解Linux设备树驱动之间的精确匹配关系。这个问题涉及到Linux内核的设备模型,我得先回忆一下设备树驱动是如何协同工作的。根据引用[1]引用[2],设备树通过描述硬件信息,而驱动则通过compatible属性来匹配设备树中的节点。 首先,设备树里的每个设备节点都有一个compatible属性,这个属性可能包含多个字符串。驱动在注册时会提供自己的兼容性列表,内核会按照优先级进行匹配。比如,如果设备节点的compatible是"vendor,device-specific",驱动中的compatible列表如果有完全相同的字符串,就会优先匹配,这就是精确匹配。 然后,用户可能还想知道具体的匹配流程。比如,设备树的信息如何传递到驱动驱动又是如何解析这些信息的。根据引用[3],驱动在初始化时会调用of_match_table,里面存放了支持的compatible字符串。当设备节点驱动的compatible匹配时,驱动的probe函数就会被调用,从而完成设备的初始化操作。 另外,引用[4]中的例子也展示了如何在设备树中配置PWM控制器,并在驱动中通过compatible属性进行匹配。这个例子具体说明了设备树节点驱动之间的实际应用,比如设置引脚复用、PWM参数等。 用户可能还关心如果多个驱动有相似的compatible怎么办,这时候内核会根据优先级选择最匹配的那个。比如,精确匹配的优先级高于模糊匹配,这样可以确保设备使用最合适的驱动。 最后,我需要确认这些信息是否准确,并且是否覆盖了用户的问题。可能还需要补充一些示例或者实际代码片段,帮助用户更好地理解匹配机制。比如,设备树中的节点结构,驱动中的of_match_table结构体,以及probe函数的作用。</think>### Linux设备树驱动精确匹配关系详解 #### 1. 匹配的核心机制 在Linux设备驱动模型中,设备树驱动的匹配主要依赖`compatible`属性。该属性在设备树节点中定义,格式为: ```dts compatible = "厂商,设备型号", "通用兼容性标识"; ``` 驱动代码中会声明`of_device_id`结构体数组,包含支持的设备标识符: ```c static const struct of_device_id pwm_fan_match[] = { { .compatible = "wyc,pwm_fan" }, //精确匹配厂商定义 { .compatible = "generic-pwm-fan" }, //通用匹配 {} }; ``` **匹配优先级**: - **精确匹配**:优先匹配`厂商,设备型号`格式的标识符(如引用[4]中的`wyc,pwm_fan`) - **模糊匹配**:次优匹配通用标识符(如`pwm-fan`) - **总线类型匹配**:最后根据总线类型匹配 #### 2. 匹配流程示意图 ``` 启动时内核扫描设备树 ↓ 解析设备节点的compatible属性 ↓ 遍历驱动注册的of_device_id列表 ↓ 找到第一个匹配的compatible字符串 ↓ 触发驱动的.probe()初始化函数 ``` 如引用[3]中提到的,驱动通过`of_iomap`等接口解析设备树中的寄存器地址等硬件参数[^3]。 #### 3. 关键实现细节 1. **设备树配置示例**(引用[4]节选): ```dts pwm_fan { compatible = "wyc,pwm_fan"; // 驱动匹配的关键标识 pwms = <&pwm1 0 100 0>; // PWM参数 pinctrl-0 = <&pwm1_pins_a>; // 引脚复用配置 }; ``` 2. **驱动注册示例**: ```c static struct platform_driver pwm_fan_driver = { .probe = pwm_fan_probe, .driver = { .name = "pwm-fan", .of_match_table = pwm_fan_match, // 匹配表 }, }; module_platform_driver(pwm_fan_driver); ``` #### 4. 典型应用场景 1. **多硬件兼容**:同一驱动支持不同厂商的相似设备 ```dts // 设备树节点 compatible = "ti,ads1115", "ads1115"; ``` 2. **版本演进**: ```dts compatible = "fsl,imx8mm-gpio", "fsl,imx35-gpio"; ``` 新版硬件兼容旧版驱动 #### 5. 调试技巧 - 查看已加载匹配关系: ```bash ls /sys/bus/platform/devices/设备名/driver ``` - 内核打印匹配过程: ```bash dmesg | grep -i "match\|probing" ``` 相关问题
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值