pwmIP

自定义PWM IP核linux驱动

AXI IP核简介

通过自定义IP核可以简化系统设计和缩短产品上市时间的目的。AXI4接口的IP核用于PS和PL端的数据通信,这里我们通过创建一个带有AXI4接口的IP核,该IP核通过AXI协议实现PS和PL端的数据通信。关于AXI协议后续在介绍。这里我们简单看一下系统框图

在这里插入图片描述

PL端的MIO连接到UART,用于打印信息。PS通过AXI接口为LED IP模块发送配置数据从而控制有PWM需求的外设。

IP核设计

这一部分一般是由FPGA工程师完成的,因此,我们只需要简单的阅读懂IP核模块干了什么事就好了,接下来就可以直接去用到IP核。这里简单介绍一下Breath LED IP核的一些寄存器配置。

控制寄存器(0x0)

AXI PWM IP模块提供了一个32位的控制寄存器,寄存器地址偏移量为0,向寄存器写入1表示使能PWM输出,写入0则表示禁止PWM输出,但目前实现的多路PWM输出同时生效的,如果需要单独控制每一路还需自己去修改IP的顶层代码。

状态寄存器(0x4)

一个32位的状态寄存器,寄存器地址偏移量为0x4

周期配置寄存器(0x8)

一个32位的PWM周期配置寄存器,寄存器地址偏移量为0x8。与控制寄存器一样,同样在配置多路PWM输出的情况下,该寄存器的配置对多路PWM输出都是同时生效的。

PWM输出的做小周期就是等于输入的时钟周期,如果该寄存器配置100,就是代表PWM输出周期为100个时钟周期

占空比配置寄存器(0x40~0x7C)

占空比配置寄存器一共有16个,寄存器地址偏移量为0x40-0x7c,对应16路输出。

Linux下PWM设备驱动框架

接下来才是我们关注的重点内容,linux内核开发者针对PWM设备设计了一套PWM设备驱动框架,那么接下来我们对PWM设备驱动框架做一个简单的了解

重要结构体

PWM设备驱动框架使用struct pwm_chip结构体来描述一个PWM设备,该设备定义在include/linux/pwm.h头文件中

struct pwm_chip{
    struct device *dev;					// 指向device对象
    struct list_head list;				// 链表节点
    const struct pwm_ops *ops;			// 指向pwm_ops对象
    int base;							// PWM通道的起始编号
    unsigned int npwm;					// PWM通道的数量
    
    struct pwm_device *pwms;			// 指向pwm_device对象数组
    struct pwm_device *(*of_xlate)(struct pwm_chip *pc,
                                  const struct of_phandle_args *args);
    unsigned int of_pwm_n_cells;
};

list是一个链表节点,当我们向PWM设备驱动框架核心层注册设备时,核心层维护了一个链表,所有注册的PWM设备都会链接到这个链表上,由核心层统一管理维护。

ops指针指向了一个struct pwm_ops类型的对象

struct pwm_ops{
    /* 申请PWM的方法 */
    int(*request)(struct pwm_chip *chip,struct pwm_device *pwm);
    /* 释放PWM的方法 */
    void (*free)(struct pwm_chip *chip,struct pwm_device *pwm);
    /* 配置PWM的方法 */
    int (*config)(struct pwm_chip *chip,struct pwm_device *pwm,
                 int duty_ns,int period_ns);
    /* 设置PWM极性的方法 */
    int (*set_polarity)(struct pwm_chip *chip,struct pwm_device *pwm,
                       enum pwm_polarity polarity);
    int (*capture)(struct pwm_chip *chip,struct pwm_device *pwm,
                  struct pwm_capture *result,unsigned long timeout);
    /* PWM输出使能的方法 */
    int (*enable)(struct pwm_chip *chip,struct pwm_device *pwm);
    /* PWM输出禁止的方法 */
    void (*disable)(struct pwm_chip *chip,struct pwm_device *pwm);
    int (*apply)(struct pwm_chip *chip,struct pwm_device *pwm,
                struct pwm_state *state);
    void (*get_state)(struct pwm_chip *chip,struct pwm_device *pwm,
                     struct pwm_state *state);
    
#define CONFIG_DEBUF_FS
	void (*dbg_show)(struct pwm_chip *chip,struct seq_file *s);    
#endif
    struct module *owner;
};

再来看一看struct pwm_device这个结构体的内容

struct pwm_device{
    const char *lable;		// 通道名字
    unsigned long flags;
    unsigned int hwpwm;		// PWM相对索引
    unsigned int pwm;		// PWM全局索引
    struct pwm_chip *chip;	// 指向PWM_chip对象
    void *chip_data;
    
    struct pwm_args args;
    struct pwm_state state;
};

chip指针指向PWM设备的实例化对象,当我们在编写驱动的时候,并不需要我们自己在驱动程序去手动实例化pwm_device对象,当调用框架提供的注册函数时,我们不需要自己在驱动程序区手动实例化pwm_device对象。

注册 卸载PWM设备
/* 设备注册接口函数 注册成功返回0  否则为负数 */
int pwmchip_add(struct pwm_chip *chip);
/* 设备卸载接口函数 成功返回0 否则为负数 */
int pwmchip_remove(struct pwm_chip *chip);

编写驱动

首先对于AXI PWM设备来说,不挂在任何总线上,所以PWM驱动程序基于虚拟的platform总线进行编写

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/io.h>
#include <linux/clk.h>


/*
* AXI_PWM寄存器定义
*/
#define PWM_AXI_CTRL_REG_OFFSET		0
#define PWM_AXI_STATUS_REG_OFFSET	4
#define PWM_AXI_PERIOD_REG_OFFSET	8
#define PWM_AXI_DUTY_REG_OFFSET		64

/* 自定义axi_pwm设备结构体 */
struct dglnt_pwm_dev{
    struct device *dev;			// device对象指针
    struct pwm_chip chip;		// 内置pwm_chip成员
    struct clk		*pwm_clk;	// axi_pwm IP输入时钟
    void __iomem	*base;		// axperiodi_pwm IP寄存器基地址
    unsigned int period_min_ns;	// pwm 输出最小周期
};

#define S_TO_NS		1000000000U	//秒换算成纳秒的量级单位

/* 写寄存器操作函数 */
static inline void dglnt_pwm_writel(struct dglnt_pwm_dev *dglnt_pwm,
                                   u32 reg,u32 val){
    writel(val,dglne_pwm->base + reg);
}

static inline struct dglnt_pwm_dev *to_dglnt_pwm_dev(struct pwm_chip *chip){
    return container_of(chip,struct dglnt_pwm_dev,chip);
}

/* pwm 配置
*  duty_ns 		  	   : PWM占空比ns
*  period_ns	       : PWM周期ns
*  @return   		   : 成功返回0 失败返回负数
*/
static int dglnt_pwm_config(struct pwm_chip*chip,struct pwm_device *pwm,
                           int duty_ns,int period_ns){
    int duty,period;
    struct dglnt_pwm_dev *dglnt_pwm = to_dglnt_pwm_dev(chip);
    
    if(dglnt_pwm->period_min_ns > period_ns){
        period_ns = dglnt_pwm->period_min_ns;
    }
    
    period = period_ns / dglnt_pwm->period_min_ns;
    duty = duty_ns / dglnt_pwm->period_min_ms;
    
    printk(KERN_INFO"period=%d duty=%d\n",period,duty);
    
    dglnt_pwm_writel(dglnt_pwm,PWM_AXI_PERIOD_REG_OFFSET,period);
    dglnt_pwm_writel(dglnt_pwm,PWM_AXI_DUTY_REG_OFFSET + (4 * pwm->hwpwm),duty);
    
    return 0;
}

/* axi_pwm 使能 */
static int dglnt_pwm_enable(struct pwm_chip *chip,struct pwm_device *pwm){
    struct dglnt_pwm_dev *dglnt_pwm = to_dglnt_pwm_dev(chip);
    dglnt_pwm_writel(dglnt_pwm,PWM_AXI_CTRL_REG_OFFSET,1);
    return 0;
}

/* axi_pwm 禁用 */
static void dglnt_pwm_disable(struct pwm_chip *chip,struct pwm_device *pwm){
    struct dglnt_pwm_dev *dglnt_pwm = to_dglnt_pwm_dev(chip);
    dglnt_pwm_writel(dglnt_pwm,PWM_AXI_CTRL_REG_OFFSET,0);
}

/* PWM操作函数集 struct pwm_ops结构体对象 */
static const struct pwm_ops dglnt_pwm_ops = {
    .config = dglnt_pwm_config,
    .enable = dglnt_pwm_enable,
    .disable = dglnt_pwm_disable,
    .owner = THIS_MODULE,
};

static int dglnt_pwm_probe(struct platform_device *pdev){
    struct dglnt_pwm_dev *dglnt_pwm;
    unsigned long clk_rate;
    struct resource *res;
    int ret;
    
    /* 实例化一个dglnt_pwm_dev对象 */
    dglnt_pwm = devm_kzalloc(&pdev->dev,sizeof(*dglnt_pwm),GFP_KERNEL);
    if(!dglnt_pwm)
        return -ENOMEM;
    
    dglnt_pwm->dev = &pdev->dev;
    
    /* 获取平台资源,得到寄存器地址 */
    /*Platform_get_resource()用于驱动程序的__init函数中,以获取设备资源的结构信息,如起始地址和结束地址,以便找到资源内存大小,以便在内存中映射它*/
    res = platform_get_resource(pdev,IORESOURCE_MEM,0);
    dglnt_pwm->base = devm_ioremap_resource(&pdev->dev,res);
    if(IS_ERR(dglnt_pwm->base))
        return PTR_ERR(dglnt_pwm->base);
    
    /* 获取时钟,得到时钟大小 */
    dglnt_pwm->pwm_clk = devm_clk_get(&pdev->dev,"pwm");
    if(IS_ERR(dglnt_pwm->pwm_clk)){
        dev_err(&pdev->dev,"failed to get pwm clock\n");
        return PTR_ERR(dglnt_pwm->pwm_clk);
    }
    
    clk_rate = clk_get_rate(dglnt_pwm->pwm_clk);
    
    /* 计算PWM的最小周期 */
    dglnt_pwm->period_min_ns = S_TO_NS /clk_rate;
    printk(KERN_INFO"pwm_clk=%1d period_min_ns=%d\n",clk_rate,dglnt_pwm->period_min_ns);
    
    /* 注册PWM设备 */
    dglnt_pwm->chip.dev = &pdev->dev;
    dglnt_pwm->chip.ops = &dglnt_pwm_ops;
    dglnt_pwm->chip.base = 0;
    ret = of_property_read_u32(pdev->dev.of_node,"npwm",&dglnt_pwm->chip.npwm);
    if(ret){
        dev_err(&pdev->dev,"failed to read npwm\n");
        return ret;
    }
    
    ret = pwmchip_add(&dglnt_pwm->chip);
    if(0 > ret){
        dev_err(&pdev->dev,"pwmchip_add failed: %d \n",ret);
        return ret;
    }
    
    platform_set_drvdate(pdev,dglnt_pwm);
    return 0;
}


static int dglnt_pwm_remove(struct platform_device *pdev){
    struct dglnt_pwm_dev *dglnt_pwm = platform_get_drvdata(pdev);
    unsigned int i;
    
    /* 先禁止PWM输出 */
    for(i=0;i<dglnt_pwm->chip.npwm;i++){
        dglnt_pwm_writel(dglnt_pwm,PWM_AXI_CTRL_REG_OFFSET,0);
    }
    
    /* 卸载PWM设备 */
    return pwmchip_remove(&dglnt_pwm->chip);
}

static const struct of_device_id dglnt_pwm_of_match[] = {
    {.compatible = "digilent,axi-pwm",},
    {},
};
MODULE_DEVICE_TABLE(of,dglnt_pwm_of_match);

static struct platform_driver dglnt_pwm_driver = {
    .driver = {
        .name = "dglnt-pwm",
        .of_match_table = dglnt_pwm_of_match,
    },
    .probe = dglnt_pwm_probe,
    .remove = dglnt_pwm_remove,
};

module_platform_driver(dglnt_pwm_driver);

MODULE_AUTHOR("XXX");
MODULE_DESCRIPTION("Digilent AXI_PWM IP Core");
MODULE_LICENSE("GPL v2");

总结

老样子,我们又来到了总结时刻,总结有利于我们分析代码的架构,理清里面的逻辑关系。

很明显,pwm是基于platform框架构建的。必然要分析platform框架这一套,两个结构体,两个函数

  1. static const struct of_device_id dglnt_pwm_of_match[] = {
        {.compatible = "digilent,axi-pwm",},
        {},
    };
    static struct platform_driver dglnt_pwm_driver = {
        .driver = {
            .name = "dglnt-pwm",
            .of_match_table = dglnt_pwm_of_match,
        },
        .probe = dglnt_pwm_probe,
        .remove = dglnt_pwm_remove,
    };
    
  2. static int dglnt_pwm_probe(struct platform_device *pdev);
    static int dglnt_pwm_remove(struct platform_device *pdev);
    
    module_platform_driver(dglnt_pwm_driver);
    

关于probe 函数上面已经展开,主要就是申请一个PWM设备,然后分配资源。对pwm进行初始化等等。

remove函数就是禁止pwm输出并卸载PWM设备。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值