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;
}