当前位置: 首页 > article >正文

PWM子系统芯片驱动源码pwm-tegra.c分析

一、pwm硬件信息

pwm控制器(芯片手册提供)
在这里插入图片描述
由上图可知,pwm共有4个通道,每个通道对应一个32位的控制寄存器,且每个寄存器之间的地址偏移为16,如下图:(注意:第30:25位未被使用)
在这里插入图片描述

二、关键驱动源码分析

#define PWM_ENABLE	BIT(31) // 0x80000000 , 31位为1,表示使能PWM输出
#define PWM_DUTY_WIDTH	8   // 8位,表示占空比的位宽
#define PWM_DUTY_SHIFT	16  
#define PWM_SCALE_WIDTH	13 // 13位,表示频率分频的位宽
#define PWM_SCALE_SHIFT	0
struct tegra_pwm_chip { //定义设备类(面向对象)
     struct pwm_chip chip; //继承于 内核的pwm_chip 对象
     struct device *dev;  //继承于统一设备模型的device基类
 
     struct clk *clk;
     struct reset_control *rst;
 
     unsigned long clk_rate;
 
     void __iomem *regs; // PWM寄存器基地址
 
     const struct tegra_pwm_soc *soc; //pwm设备的附加数据
     unsigned long		max_clk_limit;
     int (*clk_enable)(struct clk *clk);
     void (*clk_disable)(struct clk *clk);
 };
//使用内核提供的container_of宏,可以由pwm_chip结构体指针获得tegra_pwm_chip结构体指针
 static inline struct tegra_pwm_chip *to_tegra_pwm_chip(struct pwm_chip *chip)
 {
     return container_of(chip, struct tegra_pwm_chip, chip);
 }
 
 /*从 Tegra PWM 控制器的寄存器中读取一个 32 位的值。具体来说,
  *它根据传入的 PWM 通道编号(num)计算寄存器的地址,并从该地址读取一个 32 位的值
  */
 static inline u32 pwm_readl(struct tegra_pwm_chip *chip, unsigned int num)
 {
     return readl(chip->regs + (num << 4));
 }
 
  /*向 Tegra PWM 控制器的寄存器中写一个 32 位的值。具体来说,
  *它根据传入的 PWM 通道编号(num)计算寄存器的地址,并从该地址写一个 32 位的值
  */
 static inline void pwm_writel(struct tegra_pwm_chip *chip, unsigned int num,
                   unsigned long val)
 {
     writel(val, chip->regs + (num << 4));
 }
/*这个函数里的形参保持和文件"pwm.h"里的`pwm_ops`结构体里的函数指针一致;
  *保持统一接口,方便上层调用;这里相当于将`pwm_ops`结构体里的函数实现了;
  *实现了 config 、enable 和 disable 三个函数;
  *对于tegra_pwm_config 函数,它的作用是配置 PWM 通道的占空比和频率,
  *具体来说,就是根据传入的占空比和周期,计算出相应的寄存器值,并写入 PWM 控制器的寄存器中;
  *细节不再追究。
  */ 
 static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
                 int duty_ns, int period_ns)
 {
     struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip);
     unsigned long long c = duty_ns, hz;
     unsigned long rate;
     u32 val = 0;
     int err;
     ...
 }
/*用于启用指定的PWM通道,先是启用与PWM通道相关的时钟;
  *再设置相应的寄存器位(第31位),使能PWM输出。 
  */
 static int tegra_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
 {
     struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip);
     int rc = 0;
     u32 val;
 
     rc = pc->clk_enable(pc->clk);
     if (rc < 0)
         return rc;
    /*这个函数在上面有定义,用于读取pc所指向的结构体中的reg成员;
     *pwm->hwpwm 为指定pwm通道;
     *返回值是 32位寄存器中的值;先读出来,再设置第31位为1,表示使能PWM输出,最后写回寄存器。
     */
     val = pwm_readl(pc, pwm->hwpwm);
     val |= PWM_ENABLE;
     pwm_writel(pc, pwm->hwpwm, val);
 
     return 0;
 }
 /*"填表":
  *把上面的函数实现填入这个结构体中,
  *这个结构体是“pwm.h”文件中所定义的pwm控制器操作集“pwm_ops”,
  *通过这种方法,将回调函数与pwm控制器操作集关联起来。
  */
 static const struct pwm_ops tegra_pwm_ops = {
     .config = tegra_pwm_config,
     .enable = tegra_pwm_enable,
     .disable = tegra_pwm_disable,
     .owner = THIS_MODULE,
 };
//初始化PWM控制器
 static int tegra_pwm_probe(struct platform_device *pdev)
 {
     struct tegra_pwm_chip *pwm;
     struct resource *r;
     bool no_clk_sleeping_in_ops;
     struct clk *parent_clk;
     struct clk *parent_slow;
     u32 pval;
     int ret;
    
     /*申请内存空间,大小为tegra_pwm_chip结构体大小;
      *并将申请到的内存空间清零,然后将申请到的内存空间赋值给pwm指针;
      *第一个参数是一个指向设备的指针,这个指针是由设备模型提供的,
      */
     pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL);
     if (!pwm)
         return -ENOMEM;
 
    /*从设备树中获取匹配的数据,即获取tegra_pwm_soc结构体的地址;
     *并将获取到的数据赋值给pwm->soc指针;
     */
     pwm->soc = of_device_get_match_data(&pdev->dev);
     pwm->dev = &pdev->dev;
 
    /*从设备树获取PWM控制器的基地址,即PWM控制器的寄存器基地址;
     *并将获取到的基地址 通过ioremap映射成内核中能用的虚地址,
     *然后赋给pwm->regs指针;
     */
     r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
     pwm->regs = devm_ioremap_resource(&pdev->dev, r);
     if (IS_ERR(pwm->regs))
         return PTR_ERR(pwm->regs);
    
     /*把pwm对象,设置为平台驱动的私有数据,
      *方便remove时通过 platform_get_drvdata得到,从而避免了使用全局变量
      */
     platform_set_drvdata(pdev, pwm);
 
     /*函数of_property_read_bool()返回值是bool类型,
      *第一个参数为设备树节点,第二个参数为属性名;
      *如果属性存在且值为true,则返回true,否则返回false;
      */
     no_clk_sleeping_in_ops = of_property_read_bool(pdev->dev.of_node,
                                "nvidia,no-clk-sleeping-in-ops");
     dev_info(&pdev->dev, "PWM clk can%s sleep in ops\n",
             no_clk_sleeping_in_ops ? "not" : "");
 
    /*从设备树中获取属性值,如果获取成功,则将属性值赋给pval;
     *否则,返回错误码;
     */
     ret = of_property_read_u32(pdev->dev.of_node,
                    "pwm-minimum-frequency-hz", &pval);
     if (!ret)
         pwm->max_clk_limit = pval * 256 * (1 << PWM_SCALE_WIDTH);
     else
         pwm->max_clk_limit = pwm->soc->max_clk_limit;
 
    //读取设备树中的名为"pwm" 的时钟属性的值
     pwm->clk = devm_clk_get(&pdev->dev, "pwm");
     if (IS_ERR(pwm->clk))
         return PTR_ERR(pwm->clk);
 
     ...
 
     //从设备树中获取名为"pwm"的复位控制器属性的值
     pwm->rst = devm_reset_control_get(&pdev->dev, "pwm");
     if (IS_ERR(pwm->rst)) {
         ret = PTR_ERR(pwm->rst);
         dev_err(&pdev->dev, "Reset control is not found: %d\n", ret);
         return ret;
     }
 
     reset_control_deassert(pwm->rst);
     
    /*继续补充初始化PWM控制器的基本信息(使用设备树中的信息,
     *和上面的初始化信息一起构成了PWM控制器的完整信息)
     */
     pwm->chip.dev = &pdev->dev;
     pwm->chip.ops = &tegra_pwm_ops;
     pwm->chip.base = -1;
     pwm->chip.npwm = pwm->soc->num_channels;
 
    /*注册PWM控制器
     *调用顺序:-> pwmchip_add_with_polarity -> pwmchip_sysfs_export -> device_create
     */
     ret = pwmchip_add(&pwm->chip);
     if (ret < 0) {
         dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
         reset_control_assert(pwm->rst);
         return ret;
     }
 
     return 0;
 }
/*释放PWM控制器的资源,包括时钟、复位控制器、PWM控制器的基地址等;
  */
 static int tegra_pwm_remove(struct platform_device *pdev)
 {
     struct tegra_pwm_chip *pc = platform_get_drvdata(pdev);
     unsigned int i;
     int err;
 
     if (WARN_ON(!pc))
         return -ENODEV;
 
     err = pc->clk_enable(pc->clk);
     if (err < 0)
         return err;
 
     for (i = 0; i < pc->chip.npwm; i++) {
         struct pwm_device *pwm = &pc->chip.pwms[i];
 
         if (!pwm_is_enabled(pwm))
             if (pc->clk_enable(pc->clk) < 0)
                 continue;
 
         pwm_writel(pc, i, 0);
 
         pc->clk_disable(pc->clk);
     }
 
     reset_control_assert(pc->rst);
     clk_disable_unprepare(pc->clk);
 
     return pwmchip_remove(&pc->chip);
 }
static const struct tegra_pwm_soc tegra20_pwm_soc = {
     .num_channels = 4, //通道数4 (指只有 pwm0 pwm1 pwm2 pwm3)
     .max_clk_limit = 48000000UL, /* 48 MHz  最大的时钟频率*/
 };
//用于匹配设备树
 static const struct of_device_id tegra_pwm_of_match[] = {
     { .compatible = "nvidia,tegra20-pwm", .data = &tegra20_pwm_soc },
     { .compatible = "nvidia,tegra186-pwm", .data = &tegra186_pwm_soc },
     { .compatible = "nvidia,tegra194-pwm", .data = &tegra194_pwm_soc },
     { }
 };
 MODULE_DEVICE_TABLE(of, tegra_pwm_of_match);
 
 static const struct dev_pm_ops tegra_pwm_pm_ops = {
     SET_SYSTEM_SLEEP_PM_OPS(tegra_pwm_suspend, tegra_pwm_resume)
 };
 
 static struct platform_driver tegra_pwm_driver = {
     .driver = {
         .name = "tegra-pwm",
         .of_match_table = tegra_pwm_of_match,
         .pm = &tegra_pwm_pm_ops,
     },
     .probe = tegra_pwm_probe,
     .remove = tegra_pwm_remove,
 };
 
 module_platform_driver(tegra_pwm_driver);

三、总结

本文结合芯片手册中提供的pwm控制器的硬件信息,分析了pwm控制器的驱动程序。源码主要包括以下几部分:1)定义pwm控制器寄存器相关信息;2)实现pwm_ops中的三个函数,分别为config,enabledisable;3) 初始化pwm控制器,完成结构体空间申请,解析设备树相关信息并赋给结构体中的部分成员,最后调用pwmchip_add(&pwm->chip),完成设备注册。4)最后按照平台驱动框架,使用前面的模块加载函数、卸载函数以及用于匹配设备树的结构体数组 这三个部分实例化平台驱动结构体tegra_pwm_driver,接着module_platform_driver(tegra_pwm_driver); 完成整个平台驱动。下一步将分析与此驱动对应的设备树。


http://www.kler.cn/a/574078.html

相关文章:

  • 变分扩散模型 ELBO 重构推导详解
  • 软件测试基础:功能测试知识总结
  • JmeterHttp请求头管理出现Unsupported Media Type问题解决
  • ubuntu局域网部署stable-diffusion-webui记录
  • MySQL 中,SELECT ... FOR UPDATE
  • Vue父子组件传递笔记
  • 向量数据库Chroma的介绍
  • 96.在 Vue 3 中使用 OpenLayers 探究加载 Point、Polygon 的极限
  • upload-labs详解(1-12)
  • 如何下载安装 PyCharm?
  • 备考六级:词汇量积累(day4)
  • 三参数水质在线分析仪:从源头保障饮用水安全
  • aardio - 虚表 —— 两个虚表之间互相拖动交换数据
  • 走进聚类的世界:用日常例子解释复杂的算法概念
  • npm install 报错 no such file or directory 的解决方法
  • GoLang的select是什么?在什么时候场景下用
  • Unity多Pass渲染与GPU Instancing深度优化指南
  • OpenCV计算摄影学(16)调整图像光照效果函数illuminationChange()
  • 浅谈Manus智能体与其他AI助手(如ChatGPT、Claude等)的优势
  • 【C++进阶学习】第一讲——继承(下)---深入挖掘继承的奥秘