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

Linux下pwm开发与框架源码分析

目录

1 文档概述

2 pwm子系统框架

3 pwm的使用

3.1 驱动的使用

3.2 应用层使用sysfs配置

4 硬件驱动添加一个pwm_chip

5 pwm子系统源码过程分析

5.1 上层获取一个pwm_device的过程

5.2 添加一个pwm_chip过程

5.3 pwm配置接口


1 文档概述

        本文档介绍了Linux下,pwm子系统框架及源码、驱动使用pwm子系统过程、硬件驱动适配pwm子系统过程和应用过程。

2 pwm子系统框架

        如下图,大致描述了pwm子系统在整个Linux系统中的关系。pwm子系统向上提供了驱动可访问的接口,同时通过sysfs提供了应用层能直接配置的方式,向下提供了增加pwm设备的方法,为平台适配提供方法,支持多pwm同时注册等。

3 pwm的使用

        pwm子系统可以在驱动中直接使用pwm接口,也可以通过pwm子系统通过sysfs提供给用户的文件节点来配置

3.1 驱动的使用

        设备树中定义对pwm的引用

  backlight: backlight {
        compatible = "pwm-backlight";
        pwms = <&pwm1 0 25000 0>;//三个参数含义参考后面章节分析
        power-supply = <&vcc3v3_lcd>;
    };

在驱动中,使用pwm子系统提供的接口

pb->pwm = devm_pwm_get(&pdev->dev, NULL);//获取pwm_device实例
pwm_init_state(pb->pwm, &state);//初始化
ret = pwm_apply_state(pb->pwm, &state);//配置当前值,生效
pwm_get_state(pb->pwm, &state);//获取当前pwm的状态值

3.2 应用层使用sysfs配置

        应用层路径/sys/class/pwm/存在多个pwm实例

tree /sys/class/pwm/
/sys/class/pwm/
|-- pwmchip0 -> ../../devices/platform/soc/ff200000.timer/ff2000000.timer:pwm/pwm/pwmchip0
`-- pwmchip1 -> ../../devices/platform/soc/ff200010.timer/ff200010.timer:pwm/pwm/pwmchip1
....

        使用实例

        导出pwmchip0

echo 0 > /sys/class/pwm/pwmchip0/export

        此时在/sys/class/pwm/pwmchip0/路径下生成了一个pwm0子目录

echo 1 > /sys/class/pwm/pwmchip2/pwm0/enable #使能pwm0
echo 50000 > /sys/class/pwm/pwmchip2/pwm0/period #设置周期值,单位为 ns,比如 20KHz 频率的周期就是 50000ns
echo 10000 > /sys/class/pwm/pwmchip2/pwm0/duty_cycle #20%占空比

        取消导出片chip通道0设备文件

echo 0 >/sys/class/pwm/pwmchip0/unexport

4 硬件驱动添加一个pwm_chip

        以rockchip的pwm硬件驱动为例:

        设备树配置的pwm节点

    pwm0: pwm@ff200000 {
        compatible = "rockchip,px30-pwm", "rockchip,rk3328-pwm";
       ...
        pinctrl-0 = <&pwm0_pin>;
        #pwm-cells = <3>;
    };
    pwm1: pwm@ff200010 {
        compatible = "rockchip,px30-pwm", "rockchip,rk3328-pwm";
        ..
        pinctrl-0 = <&pwm1_pin>;
        #pwm-cells = <3>;
    };
    pwm3: pwm@ff200030 {
       ...
    };
    pwm4: pwm@ff208000 {
        ...
    };
    pwm5: pwm@ff208010 {
       ...
    };
    pwm6: pwm@ff208020 {
        ...
    };
    pwm7: pwm@ff208030 {
        ...
    };

        硬件驱动的实现与使用:

static struct platform_driver rockchip_pwm_driver = {
    .driver = {
        .name = "rockchip-pwm",
        .of_match_table = rockchip_pwm_dt_ids,
    },
    .probe = rockchip_pwm_probe,
    .remove_new = rockchip_pwm_remove,
};
module_platform_driver(rockchip_pwm_driver);
static int rockchip_pwm_probe(struct platform_device *pdev)
{
    const struct of_device_id *id;
    struct rockchip_pwm_chip *pc;
    u32 enable_conf, ctrl;
    bool enabled;
    ...
    pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
    if (!pc)
        return -ENOMEM;
    ...
    pc->data = id->data;
    pc->chip.dev = &pdev->dev;
    pc->chip.ops = &rockchip_pwm_ops;//硬件回调操作集
    pc->chip.npwm = 1;//当前pwm_chip只支持一个pwm(实际设备树有多个pwm节点,将添加多个pwm_chip)
    enable_conf = pc->data->enable_conf;
    ctrl = readl_relaxed(pc->base + pc->data->regs.ctrl);
    enabled = (ctrl & enable_conf) == enable_conf;
    ret = pwmchip_add(&pc->chip);//添加一个pwm_chip
  	...
    /* Keep the PWM clk enabled if the PWM appears to be up and running. */
    if (!enabled)
        clk_disable(pc->clk);
    clk_disable(pc->pclk);
    return 0;
}
static const struct pwm_ops rockchip_pwm_ops = {
    .get_state = rockchip_pwm_get_state,//获取当前pwm状态数据
    .apply = rockchip_pwm_apply,//配置硬件pwm
    .owner = THIS_MODULE,
};

        get_state和apply,其中apply是必须实现的回调。

5 pwm子系统源码过程分析

        pwm子系统源码实现在Linux内核源码路径drivers/pwm下。

5.1 上层获取一个pwm_device的过程


struct pwm_device *devm_pwm_get(struct device *dev, const char *con_id)
{
struct pwm_device *pwm;
pwm = pwm_get(dev, con_id);//获取返回一个pwm_device
...
return pwm;
}
struct pwm_device *pwm_get(struct device *dev, const char *con_id)
{
const struct fwnode_handle *fwnode = dev ? dev_fwnode(dev) : NULL;
...
    /* look up via DT first */
    if (is_of_node(fwnode))
        return of_pwm_get(dev, to_of_node(fwnode), con_id);//从设备树中找device对应的pwm_device
//从pwm_lookup_list里找,该链表是由用户调用pwm_add_table接口,将pwm_device与device的关系设置好后,配置到该链表中,实际项目比较少用到这种方式
list_for_each_entry(p, &pwm_lookup_list, list) {
...
}
...
return pwm;
}

static struct pwm_device *of_pwm_get(struct device *dev, struct device_node *np,
                     const char *con_id)
{
    struct pwm_device *pwm = NULL;
    struct of_phandle_args args;
    struct device_link *dl;
    struct pwm_chip *chip;
int index = 0;
...
/*
*下面函数从设备树节点np中的pwms里,找出index(这里是0)这个引用的device,然后根据device里#pwm-cells标签里值,解析pwms引用的device后面的参数,放到args里。例如背光设备树定义如下:
backlight: backlight {
        compatible = "pwm-backlight";
        pwms = <&pwm1 0 25000 0>;
        power-supply = <&vcc3v3_lcd>;
    };
上面pwms = <&pwm1 0 25000 0>;中,pwm1后面的三个参数将被解析到args中,分别代表pwm_device偏移量、频率、是否反相
*/
err = of_parse_phandle_with_args(np, "pwms", "#pwm-cells", index,
                     &args);
//根据得到的pwm节点,获取struct pwm_chip,该结构体维护者pwm_device,和dev绑定,这些在硬件驱动层用户添加一个pwm_chip的时候做的初始化,后面分析
chip = fwnode_to_pwmchip(of_fwnode_handle(args.np));
//根据chip和args获取到pwm_device,该回调在用户添加pwm_chip的时候初始化,pwmchip_add->of_pwmchip_add->chip->of_xlate = of_pwm_xlate_with_flags
pwm = chip->of_xlate(chip, &args);
//建立引用节点与当前pwm节点的联系,实际上dev只想device_pwm
dl = pwm_device_link_add(dev, pwm);
...
return pwm;
}

struct pwm_device *
of_pwm_xlate_with_flags(struct pwm_chip *chip, const struct of_phandle_args *args)
{
    struct pwm_device *pwm;
    if (chip->of_pwm_n_cells < 2)//从设备树#pwm-cells解析的值,pwm配置不能小于2
        return ERR_PTR(-EINVAL);
    /* flags in the third cell are optional */
    if (args->args_count < 2)//同上
        return ERR_PTR(-EINVAL);
    if (args->args[0] >= chip->npwm)//#pwm-cells配置第一个偏移量不能超过所有注册的pwm chip
        return ERR_PTR(-EINVAL);
//根据第一个偏移量,该偏移量表示pwm在chip中的偏移值
pwm = pwm_request_from_chip(chip, args->args[0], NULL);
    pwm->args.period = args->args[1];//第二个参数值,频率
    pwm->args.polarity = PWM_POLARITY_NORMAL;
    if (chip->of_pwm_n_cells >= 3) {
        if (args->args_count > 2 && args->args[2] & PWM_POLARITY_INVERTED)//第三个参数值
            pwm->args.polarity = PWM_POLARITY_INVERSED;
    }
    return pwm;
}
struct pwm_device *pwm_request_from_chip(struct pwm_chip *chip,unsigned int index,const char *label)
{
    ...
    mutex_lock(&pwm_lock);
    pwm = &chip->pwms[index];//根据index获取维护在chip的pwm对象
    err = pwm_device_request(pwm, label);//如果实现了硬件request接口
 ...
    mutex_unlock(&pwm_lock);
    return pwm;
}

5.2 添加一个pwm_chip过程


int pwmchip_add(struct pwm_chip *chip) {

//根据用户配置的当前pwm_chip配置的pwm数量,申请对应pwm_device 个数
chip->pwms = kcalloc(chip->npwm, sizeof(*pwm), GFP_KERNEL);
//分别对每个pwm赋值
for (i = 0; i < chip->npwm; i++) {
        pwm = &chip->pwms[i];
        pwm->chip = chip;
        pwm->pwm = chip->base + i;
        pwm->hwpwm = i;
}
//将当前pwm_chip加入全局链表维护
list_add(&chip->list, &pwm_chips);
if (IS_ENABLED(CONFIG_OF))
        of_pwmchip_add(chip);//支持设备树情况下,绑定chip与pwm关系
pwmchip_sysfs_export(chip);//通过sysfs导出应用层,提供用户操作
}
static void of_pwmchip_add(struct pwm_chip *chip)
{
    if (!chip->dev || !chip->dev->of_node)
        return;
    if (!chip->of_xlate) {//用户没实现该接口情况下
        u32 pwm_cells;
        if (of_property_read_u32(chip->dev->of_node, "#pwm-cells", &pwm_cells))//和前面分析获取pwm参数一致
            pwm_cells = 2;
        chip->of_xlate = of_pwm_xlate_with_flags;//赋默认的回到值
        chip->of_pwm_n_cells = pwm_cells;
    }
    of_node_get(chip->dev->of_node);
}

        需要注意的是,如果硬件驱动层将每个pwm(最后解析成一个pwm_device)分开,向pwm子系统添加的一个pwm_chip时只用一个pwm(最后解析成一个pwm_device),则of_xlate一般交给pwm子系统实现,该回调处理了pwm在chip的维护顺序,默认偏移值是0。如果将多个pwm(最后解析成一个pwm_device)一起维护成一个pwm_chip,这里的回调用户可以选择实现来维护pwm_device在pwm_chip中的关系。

5.3 pwm配置接口

        前面在获取到了pwm_device后,利用pwm_device可以调用pwm子系统提供的接口进行pwm配置操作

=====> pwm_init_state 接口

static inline void pwm_init_state(const struct pwm_device *pwm,
                  struct pwm_state *state)
{
    struct pwm_args args;
    /* First get the current state. */
    pwm_get_state(pwm, state); //返回pwm->state
    /* Then fill it with the reference config */
    pwm_get_args(pwm, &args);//返回pwm->args
    state->period = args.period;//周期
    state->polarity = args.polarity;//极性配置
    state->duty_cycle = 0;
    state->usage_power = false;
}

=====> pwm_apply_state接口

int pwm_apply_state(struct pwm_device *pwm, const struct pwm_state *state)
{
    struct pwm_chip *chip;
    int err;
  ...
    chip = pwm->chip;
    if (state->period == pwm->state.period &&
        state->duty_cycle == pwm->state.duty_cycle &&
        state->polarity == pwm->state.polarity &&
        state->enabled == pwm->state.enabled &&
        state->usage_power == pwm->state.usage_power)
        return 0;
    err = chip->ops->apply(chip, pwm, state);//调用硬件回调接口进行配置
    ...
    return 0;
}

=====> pwm_get_stat接口

static inline void pwm_get_state(const struct pwm_device *pwm,
                 struct pwm_state *state)
{
    *state = pwm->state;
}


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

相关文章:

  • 嵌入式中利用QT实现服务器与客户端方法
  • SQL注入靶场演练
  • 【算法】计算程序执行时间(C/C++)
  • 136.flask内置jinja2模版使用
  • django从入门到精通(六)——auth认证及自定义用户
  • Win11 22H2/23H2系统11月可选更新KB5046732发布!
  • javaScript之箭头函数
  • 阿里推理模型来了!Marco-o1 发布即开源
  • 飞凌嵌入式T113-i开发板RISC-V核的实时应用方案
  • 记录一个奇怪的前端布局现象
  • 后台通用tag面包屑
  • Figma入门-文字、样式、链接、动作
  • 多联机空调节能集中控制系统
  • 机器学习——数据标注
  • 微搭低代码入门09对象
  • 基于 MONAI 的 3D 图像分割任务2(Brain Tumour 和 SwinUNETR 训练)
  • 低速接口项目之串口Uart开发(七)——如何在FPGA项目中实现自适应波特率串口功能
  • leetcode-24-两两交换链表中的节点
  • 表的增删改查(MySQL)
  • [论文阅读]Can GNN be Good Adapter for LLMs?
  • 如何在Word文件中设置水印以及如何禁止修改水印
  • 【深度学习|onnx】往onnx中写入训练的超参或者类别等信息,并在推理时读取
  • HTML的自动定义倒计时,这个配色存一下
  • 谈学生公寓安全用电系统的涉及方案
  • 乐理的学习(和弦)
  • MongoDB比较查询操作符中英对照表及实例详解