脉宽控制(PWM)操作像不断循环开关,这一功能常用于控制伺服电机、灯光控制、电压调节等。
- Ton:信号高电平的持续时间;
- Toff:信号低电平的持续时间;
- Period:完整的PWM周期的持续时间,它是Ton+Toff
- Duty cycle:Ton在Period的占空比;它的值为(Ton/Period)*100
linux的PWM框架有两个接口:
- 控制器接口:提供PWM线路。它是PWM芯片。
- 消费者接口:消费有PWM控制器提供的PWM线的设备。此类设备的驱动程序使用控制器通过PWM框架提供的辅助函数。
一、PWM控制器驱动程序
1. PWM控制器实例
PWM控制器在内核中表示为struct pwm_chip{}实例。
struct pwm_chip {
struct device *dev;
const struct pwm_ops *ops; // 公开给消费者程序的回调函数
int base; // 该芯片控制的第一个pwm的编号
uint npwm; // 该芯片控制的第一个pwm通道的数量
struct pwm_device *pwms; // 该芯片的pwm设备数组
struct pwm_device *(of_xlate)(struct pwm_chip *pc,
const struct of_phandle_args *args);
// 默认值为of_pwm_simple_xlate
uint of_pwm_n_cells; // PWM说明符的cell数
bool can_sleep;
};
// 控制器芯片的添加/删除
int pwmchip_add(struct pwm_chip *chip);
int pwmchip_remove(struct pwm_chip *chip);
// 每个pwm芯片会提供一些callback,这些callback供pwm内核或用户使用
struct pwm_ops {
int (*request)(struct pwm_chip *chip, struct pwm_device *pwm);
int (*free)(struct pwm_chip *chip, struct pwm_device *pwm);
int (*config)(struct pwm_chip *chip, struct pwm_device *pwm
int duty_ns, int period); // 配置函数
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);
int (*disable)(struct pwm_chip *chip, struct pwm_device *pwm);
int (*get_state)(struct pwm_chip *chip, struct pwm_device *pwm
struct pwm_state *state); // 返回pwm当前的状态
struct module *owner;
};
2. PWM芯片驱动程序实例
下面是一个具有3个通道的PWM控制器的驱动程序:
struct fake_chip {
struct pwm_chip chip;
int foo;
int bar;
/*将客户端结构放在这里,例如spi_device, i2c_client等*/
};
#define to_fake_chip(pwm_chip) container_of(pwm_chip, struct fake_chip, chip)
static int fake_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) {
return 0;
}
static int fake_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns) {
return 0;
}
static int fake_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) {
return 0;
}
static void fake_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) {}
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(sizeof(*Priv));
struct pwm_chip *chip = &priv->chip;
chip->ops = &fake_pwm_ops;
chip->dev = &pdev->dev;
chip->base = -1;
chip->npwm = 3;
platform_set_drvdat(pdev, priv);
return pwmchip_add(chip);
}
static void fake_pwm_remove(struct platform_device *pdev) {
struct fake_chip *priv = platform_get_drvdata(pdev);
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_MODULE, .owner=THIS_MODULE,
.of_match_table = fake_pwm_dt_ids,},
.probe = fake_pwm_probe,
.remove = fake_pwm_remove,
};
module_platform_driver(fake_pwm_driver);
3. PWM芯片与DT绑定
从DT内绑定PWM控制器时,最重要的属性是#pwm-cells。
下面是和fake_pwm驱动对应的节点:
fake_pwm: pwm@0 {
#pwm-cells=<2>;
compatible = "packt,fake-pwm"
};
下面是一个真实的示例,i.MX6 SoC中的PWM控制器示例如下:
pwm3: pwm@0208800 {
#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", "pwm3";
status = "disabled";
};
二、PWM框架消费者接口
消费者是实际使用PWM通道的设备,PWM通道在内核中表示为struct pwm_device{}结构体。
struct pwm_device {
const char *label;
ulong flags;
uint hwpwm; // pwm通道在硬件芯片内的相对索引
uint pwm; // Pwm通道的全局索引
struct pwm_chip *chip;
void *chip_data; // 与pwm芯片相关的专有数据
uint period;
uint duty_cycle;
enum pwm_polarity polarity;
};
// PWM框架提供的pwm设备使用函数由两个版本:旧版pwm_request/pwm_free,新版pwm_get/pwm_free;
struct pwm_device* pwm_get(struct device *dev, const char *con_id); // 其中参数dev是客户端设备
void pwm_put(struct pwm_device *pwm);
// 获取struct pwm_device{}后配置接口:
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);
// 启动、停止切换pwm输出
int pwm_enable(struct pwm_device *pwm);
int pwm_disable(struct pwm_device *pwm);
使用DT方式进行pwm消费者绑定:
// pwm控制器芯片
pwm0: pwm {
#pwm-cells = <2>;
};
// pwm消费者
bl: backligth {
pwms = <&pwm0 0 5000000>; // 其中0为pwm索引,5000000ns为period
pwm-names = "backligth"; // 名称对应函数pwm_get()中的con_id
};
三、通过sysfs接口使用pwm
pwm芯片对应的目录为/sys/class/pwm/pwmchip{chip-id}/,其中有三个文件:
- npwm:只读文件,芯片中pwm通道的数量;
- export:只写文件,echo {id} >export会导出对应对应的pwm通道,并生成对应通道目录/sys/class/pwm/pwmchip{chip-id}/pwm{pwmdevice-id}/
- unexport:只写文件,与export文件相反
pwm通道对应的目录为/sys/class/pwm/pwmchip{chip-id}/pwm{pwmdevice-id}/,其中PWM通道的索引为芯片的相对索引,该目录下有如下文件:
- period:用于设置/读取PWM通道的周期,ns单位
- duty_cycle:用于设置/读取PWM的Duty cycle
- polarity:用于设置PWM的极性
- enable:用户启动/停止PWM设备