PWM驱动
脉冲宽度调制(PWM)的工作原理就像一个开关,不断循环开和关。它是一种硬件功能,用于控制伺服电机,进行电压调节等。PWM最广为人知的应用有:
- 电机转速控制
- 亮度调节
- 电压调整
下面用一个简单的图表来介绍PWM:
上图展示了一个完整的PWM周期,介绍了一些术语,在深入了解核心PWM框架之前,我们需要说明:
- Ton: 信号为高的持续时间。
- Toff: 信号为低的持续时间。
- Period(周期): 这是一个完整的PWM周期的持续时间。它表示PWM信号的Ton和Toff之和。
- Duty cycle(占空比):表示在一个PWM信号周期内,信号保持为高的时间占整个周期的百分比。
不同的公式详细如下:
- PWM周期:
- 占空比:
您可以在https://en.wikipedia.org/wiki/Pulse-width_modulation上找到有关PWM的详细信息。
Linux PWM框架有两个接口:
- 控制器接口: 产生PWM输出的接口。它就是PWM芯片,也就是产生器。
- 消费者接口: 设备消费由控制器产生的PWM输出。这种设备的驱动程序使用 由控制器通过通用PWM框架导出的 辅助函数。
消费者或生产者接口都依赖于以下头文件:
#include <linux/pwm.h>
在这里,我们将讨论以下问题:
- PWM驱动器的架构和数据结构,控制器和消费者,以及一个虚拟驱动程序
- 在设备树中实例化PWM设备和控制器
- 请求和消费PWM设备
- 从用户空间使用PWM,通过sysfs接口
PWM控制器驱动程序
当编写GPIO-controller驱动程序时需要struct gpio_chip,编写IRQ-controller驱动程序时需要struct irq_chip, PWM控制器在内核中被表示为struct pwm_chip结构的实例:
struct pwm_chip { struct device *dev; const struct pwm_ops *ops; int base; unsigned int npwm; struct pwm_device *pwms; struct pwm_device * (*of_xlate)(struct pwm_chip *pc, const struct of_phandle_args *args); unsigned int of_pwm_n_cells; bool can_sleep; };
下面列出了结构中每个元素的含义:
- dev: 这表示与pwm chip相关联的设备。
- ops:这是一个提供回调函数的数据结构,向消费者驱动程序公开。
- base: 这个该pwm芯片控制的第一个PWM数字编号,如果chip->base < 0 ,然后内核将动态地分配一个基础编号。
- can_sleep: 这应该被芯片驱动程序设置为true,如果.config()、.enable()、.disable()等ops字段的操作可能处于休眠状态。
- npwm: 这是该芯片提供的PWM通道(设备)的数量。
- pwms: 这是该芯片的PWM设备的数组,由框架分配给消费者驱动程序。
- of_xlate: 这是一个可选的回调,用于请求PWM设备,给定DT PWM指示符。如果没有定义,它将被PWM核心设置为of_pwm_simple_xlate,这也将强制of_pwm_n_cells为2。
- of_pwm_n_cells: 这是DT中PWM指示符所需的cell的数量。
PWM控制器/芯片的添加和移除依赖于两个基本函数:pwmchip_add()和pwmchip_remove()。每个函数都应该被赋予一个填充的struct pwm_chip结构体作为参数。它们各自的原型如下:
int pwmchip_add(struct pwm_chip *chip) int pwmchip_remove(struct pwm_chip *chip)
与其他没有返回值的框架remove函数不同,pwmchip_remove()有返回值。如果成功,它将返回0,如果芯片仍在使用(仍然请求)PWM线路,则返回-EBUSY。
每个PWM驱动程序必须通过struct pwm_ops字段实现一些回调函数,PWM核心或消费者接口使用它来配置和充分利用其PWM通道。有些是可选的:
struct pwm_ops { int (*request)(struct pwm_chip *chip, struct pwm_device *pwm); void (*free)(struct pwm_chip *chip, struct pwm_device *pwm); int (*config)(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns); int (*set_polarity)(struct pwm_chip *chip, struct pwm_device *pwm, enum pwm_polarity polarity); int (*enable)(struct pwm_chip *chip,struct pwm_device *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); /* since kernel v4.7 */ struct module *owner; };
结构中每个元素的含义:
- request: 这是一个可选的回调,如果提供,将在PWM通道请求期间执行。
- free: 这和request一样,在PWM释放期间运行。
- config: 这是PMW配置回调,它为该PWM配置占空比和周期长度。
- set_polarity: 这个回调配置这个PWM的极性。
- enable: 使能PWM线路,开始输出切换。
- disable: 关闭PWM线路,停止输出切换
- apply: 自动应用新的PWM配置。state参数应该根据实际硬件配置进行调整。
- get_state: 返回当前的PWM状态。当PWM芯片注册时,这个函数在每个PWM器件中只被调用一次。
- owner: 这是拥有PWM芯片的模块,通常是THIS_MODULE。
在PWM控制器驱动的probe函数中,检索DT资源,初始化硬件,填充struct pwm_chip及其struct pwm_ops,然后用pwmchip_add函数添加PWM芯片是一个很好的做法。
驱动例子
现在,让我们通过为PWM控制器编写一个虚拟驱动程序来总结一下,它有三个通道:
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
struct fake_chip { struct pwm_chip chip; int foo; int bar; /* put the client structure here (SPI/I2C) */ };
static inline struct fake_chip *to_fake_chip(struct pwm_chip *chip) { return container_of(chip, struct fake_chip, chip); }
static int fake_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) { /* * One may need to do some initialization when a PWM channel * of the controller is requested. This should be done here. * * One may do something like * prepare_pwm_device(struct pwm_chip *chip, pwm->hwpwm); */ return 0; }
static int fake_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) { /* * In this function, one ne can do something like: * struct fake_chip *priv = to_fake_chip(chip); * * return send_command_to_set_config(priv, * duty_ns, period_ns); */ return 0; }
static int fake_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) { /* * In this function, one ne can do something like: * struct fake_chip *priv = to_fake_chip(chip); * * return foo_chip_set_pwm_enable(priv, pwm->hwpwm, true); */ pr_info("Somebody enabled PWM device number %d of this chip", pwm->hwpwm); return 0; }
static inline struct fake_chip *to_fake_chip(struct pwm_chip *chip) { return container_of(chip, struct fake_chip, chip); }
static int fake_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) {
/* * One may need to do some initialization when a PWM channel * of the controller is requested. This should be done here. * * One may do something like * prepare_pwm_device(struct pwm_chip *chip, pwm->hwpwm); */ return 0; }
static int fake_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) {
/* * In this function, one ne can do something like: * struct fake_chip *priv = to_fake_chip(chip); * * return send_command_to_set_config(priv, * duty_ns, period_ns); */ return 0; }
static int fake_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) { /* * In this function, one ne can do something like: * struct fake_chip *priv = to_fake_chip(chip); * * return foo_chip_set_pwm_enable(priv, pwm->hwpwm, true); */ pr_info("Somebody enabled PWM device number %d of this chip", pwm->hwpwm); return 0; }
static void fake_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) { /* * In this function, one ne can do something like: * struct fake_chip *priv = to_fake_chip(chip); * * return foo_chip_set_pwm_enable(priv, pwm->hwpwm, false); */ pr_info("Somebody disabled PWM device number %d of this chip", pwm->hwpwm); }
static const struct pwm_ops fake_pwm_ops = { .request = fake_pwm_request, .config = fake_pwm_config, .enable = fake_pwm_enable, .disable = fake_pwm_disable, .owner = THIS_MODULE, };
static int fake_pwm_probe(struct platform_device *pdev) { struct fake_chip *priv; priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM;
priv->chip.ops = &fake_pwm_ops; priv->chip.dev = &pdev->dev; priv->chip.base = -1; /* Dynamic base */ priv->chip.npwm = 3; /* 3 channel controller */ platform_set_drvdata(pdev, priv); return pwmchip_add(&priv->chip); }
static int fake_pwm_remove(struct platform_device *pdev) { struct fake_chip *priv = platform_get_drvdata(pdev); return pwmchip_remove(&priv->chip); }
static const struct of_device_id fake_pwm_dt_ids[] = { { .compatible = "packt,fake-pwm", }, { } };
MODULE_DEVICE_TABLE(of, fake_pwm_dt_ids);
static struct platform_driver fake_pwm_driver = { .driver = { .name = KBUILD_MODNAME, .owner = THIS_MODULE, .of_match_table = of_match_ptr(fake_pwm_dt_ids), }, .probe = fake_pwm_probe, .remove = fake_pwm_remove, };
module_platform_driver(fake_pwm_driver); MODULE_DESCRIPTION("Fake pwm driver"); MODULE_LICENSE("GPL");
PWM控制器绑定
当从DT内部绑定PWM控制器时,最重要的属性是#pwm-cells。它表示用于表示该控制器上PWM器件的单元数。在struct pwm_chip结构中,of_xlate钩子用于转换给定的PWM指示符。如果没有设置of_xlate回调,pwm-cells必须设置为2;否则,它应该设置为与of_pwm_n_cells相同的值。下面是一个i.MX6 SoC的DT中的PWM控制器节点示例:
pwm3: pwm@02088000 { #pwm-cells = <2>; compatible = "fsl,imx6q-pwm", "fsl,imx27-pwm"; reg = <0x02088000 0x4000>; interrupts = <0 85 IRQ_TYPE_LEVEL_HIGH>; clocks = <&clks IMX6QDL_CLK_IPG>, <&clks IMX6QDL_CLK_PWM3>; clock-names = "ipg", "per"; status = "disabled"; };
对应于我们的fake-pwm驱动程序的节点看起来像这样:
fake_pwm: pwm@0 { #pwm-cells = <2>; compatible = "packt,fake-pwm"; /* * Our driver does not use resource * neither mem, IRQ, nor Clock) */ };
PWM消费者接口
消费者是实际使用PWM通道的设备。PWM通道在内核中表示为struct pwm_device结构的实例:
struct pwm_device { const char *label; unsigned long flags; unsigned int hwpwm; unsigned int pwm; struct pwm_chip *chip; void *chip_data; unsigned int period; /* in nanoseconds */ unsigned int duty_cycle; /* in nanoseconds */ enum pwm_polarity polarity; };
- label: 这是PWM设备的名称
- flags: 表示与PWM设备相关的标志
- hwpw: 这是PWM设备在芯片本地的相关索引
- pwm: 这是pwm设备的系统全局索引
- chip: 这是一个PWM芯片,此控制器提供PWM设备
- chip_data: 这是与PWM设备相关的芯片私有数据
从内核v4.7开始,结构已经更改为:
struct pwm_device { const char *label; unsigned long flags; unsigned int hwpwm; unsigned int pwm; struct pwm_chip *chip; void *chip_data; struct pwm_args args; struct pwm_state state; };
- args: 这表示附加到该PWM设备的板级相关的PWM参数,这些参数通常从PWM查找表或设备树中检索。PWM参数表示用户希望在该PWM设备上使用的初始配置,而不是当前的PWM硬件状态。
struct pwm_args { unsigned int period; /* Device's initial period */ enum pwm_polarity polarity; };
- state: 表示当前PWM通道状态:
struct pwm_state { unsigned int period; /* PWM period (in nanoseconds) */ unsigned int duty_cycle; /* PWM duty cycle (in nanoseconds) */ enum pwm_polarity polarity; /* PWM polarity */ bool enabled; /* PWM enabled status */ }
在Linux的发展过程中,PWM框架面临着一些变化。这些变化与从消费者端请求PWM设备的方式有关。我们可以把消费者接口分成两部分,或者更准确地说,分成两个版本:
1. 旧版本,使用pwm_request()和pwm_free()来请求PWM设备和在使用后释放它。
2. 新且推荐的APIs,使用pwm_get()和pwm_put()函数。前者给出消费者设备和通道名称,作为请求PWM设备的参数,而后者给出PWM设备,作为释放的参数。这些函数的资源管理变体devm_pwm_get()和devm_pwm_put()也存在:
struct pwm_device *pwm_get(struct device *dev, const char *con_id) void pwm_put(struct pwm_device *pwm)
不能从原子上下文中调用pwm_request()/pwm_get()和pwm_free()/pwm_put(),因为PWM核心使用互斥对象,可能处于休眠状态。
请求后,必须配置PWM,使用以下命令:
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);
要启动/停止PWM输出,使用pwm_enable()/pwm_disable()。这两个函数都以指向struct pwm_device的指针作为参数,它们都是控制器通过pwm_chip.ops字段公开的回调函数:
int pwm_enable(struct pwm_device *pwm) void pwm_disable(struct pwm_device *pwm)
pwm_enable()成功时返回0,失败时返回负错误代码。PWM消费者驱动程序的一个很好的例子是内核源代码树中的drivers/leds/leds-pwm.c。下面是一个消费者代码的例子,驱动PWM LED:
static void pwm_led_drive(struct pwm_device *pwm, struct private_data *priv) { /* Configure the PWM, applying a period and duty cycle */ pwm_config(pwm, priv->duty, priv->pwm_period);
/* Start toggling */ pwm_enable(pchip->pwmd);
[...] /* Do some work */
/* And then stop toggling*/ pwm_disable(pchip->pwmd); }
PWM客户端绑定
PWM设备可以从以下位置分配给消费者:
- 设备树
- ACPI
- 静态查找表,在单板初始化文件中
这里只处理DT绑定,因为这是推荐的方法。当将PWM消费者(客户端)绑定到它的驱动程序时,您需要提供它所链接到的控制器的phandle。
建议将PWM属性命名为pwms;因为PWM设备是命名资源,你可以提供一个可选的属性,pwm-names,包含一个字符串列表,来命名pwms属性中列出的每个PWM设备。如果没有给出pwm-names属性,则用户节点的名称将用作备用。
对于使用多个PWM设备的设备,驱动程序可以使用pwm-names属性将pwm_get()调用请求的PWM设备的名称映射到pwms属性给出的列表中的索引。
下面的例子描述了一个基于PWM的背光设备,它摘自PWM设备绑定的内核文档(参见Documentation/devicetree/bindings/pwm/pwm.txt):
pwm: pwm { #pwm-cells = <2>; };
[...]
bl: backlight { pwms = <&pwm 0 5000000>; pwm-names = "backlight"; };
PWM-specifier通常编码相对于芯片的PWM号和以纳秒为单位的PWM周期,如下所示:
pwms = <&pwm 0 5000000>;
0对应PWM索引,相对于控制器。5000000表示周期,单位为纳秒。注意,在前面的示例中,指定pmm-names是多余的,因为无论如何,名称backlight都将用作备用。因此,驱动程序必须调用以下命令:
static int my_consummer_probe(struct platform_device *pdev) { struct pwm_device *pwm; pwm = pwm_get(&pdev->dev, "backlight"); if (IS_ERR(pwm)) { pr_info("unable to request PWM, trying legacy API\n"); /* Some drivers use the legacy API as fallback, in order * to request a PWM ID, global to the system * pwm = pwm_request(global_pwm_id, "pwm beeper"); */ } [...] return 0; }
PWM指示器通常编码相对于芯片的PWM号和以纳秒为单位的PWM周期。
使用带有sysfs接口的PWMs
PWM核心sysfs根路径为“/sys/class/pwm/”。它是管理PWM设备的用户空间方式。添加到系统中的每个PWM控制器/芯片都会在sysfs根路径下创建一个pwmchipN目录条目,其中N是PWM芯片的基础索引。目录中包含以下文件:
- npwm: 这是一个只读文件,表示芯片支持的PWM通道数量
- 导出: 这是一个只写的文件,允许你导出用于sysfs的PWM通道(此功能类似于GPIO sysfs接口)
- unexport: 从sysfs中取消导出PWM通道(只写)
PWM通道使用从0到pwm<n-1>的索引进行编号。这些编号是芯片的本地编号。每个PWM通道导出在pwmchipN中创建一个pwmX目录,该目录与包含使用的导出文件的目录相同。其中X为导出的通道号。每个通道目录包含以下文件:
- period: 这是一个可读/可写的文件,用于获取/设置PWM信号的总周期。单位为纳秒。
- duty_cycle: 这是一个可读/可写的文件,用于获取/设置PWM信号的占空比。它表示PWM信号的活动时间。该值以纳秒为单位,且必须小于周期。
- polarity: 这是一个可读/可写的文件,只有当该PWM器件的芯片支持极性反转时才使用。最好只在该PWM未启用时才更改极性。接受的值为字符串"normal"或者“inversed”。
- enable: 这是一个可读/可写的文件,用于启用(开始切换)或禁用(停止切换)PWM信号。接受的值如下:
- 0: disabled
- 1: enabled
下面是通过sysfs接口从用户空间使用PWM的示例:
- 使能PWM
# echo 1 > /sys/class/pwm/pwmchip<pwmchipnr>/pwm<pwmnr>/enable
- 设置PWM周期:
# echo <value in nanoseconds> > /sys/class/pwm/pwmchip<pwmchipnr>/pwm<pwmnr>/period
- 设置PWM占空比;占空比的值必须小于PWM周期的值:
# echo <value in nanoseconds> > /sys/class/pwm/pwmchip<pwmchipnr>/pwm<pwmnr>/duty_cycle
- 禁用PWM:
# echo 0 > /sys/class/pwm/pwmchip<pwmchipnr>/pwm<pwmnr>/enable
完整的PWM框架API和sysfs描述可在内核源代码树中的Documentation/pwm.txt文件中找到。
现在你应该已经准备好处理任何PWM控制器,无论是内存映射还是外部位于总线上。本章中描述的API足以编写和增强控制器驱动程序作为消费者设备驱动程序。如果您对PWM内核方面还不熟悉,可以完全使用用户空间sysfs接口。
本文来自博客园,作者:闹闹爸爸,转载请注明原文链接:https://www.cnblogs.com/wanglouxiaozi/p/17128050.html