52.Linux PWM子系统
结构体pwm_chip表示PWM的核心

imx6ull_alientek共有八路pwm信号
pwm1-pwm4都在一组、pwm5-pwm8在一组
而 GPIO1_IO04 可以是 PWM3 的输出引脚
c
pwm3: pwm@02088000 {
compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm";
reg = <0x02088000 0x4000>;
interrupts = <GIC_SPI 85 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_PWM3>,
<&clks IMX6UL_CLK_PWM3>;
clock-names = "ipg", "per";
#pwm-cells = <2>;
};
compatible 属性值有两个"fsl,imx6ul-pwm"和"fsl,imx27-pwm",所以在整个 Linux源码里面搜索这两个字符窜即可找到 I.MX6ULL 的 PWM 驱动文件,这个文件就是drivers/pwm/pwm-imx.c。
include/linux/pwm.h中定义了pwm_chip结构体
c
struct pwm_chip {
struct device *dev;
struct list_head list;
const struct pwm_ops *ops; //pwm驱动的设备操作集合
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;
};
pwm_ops 结构体就是 PWM 外设的各种操作函数集合,编写 PWM 外设驱动的时候需要开发人员实现。
pwm_ops如下
c
struct pwm_ops {
int (*request)(struct pwm_chip *chip,struct pwm_device *pwm); //请求PWM
void (*free)(struct pwm_chip *chip,struct pwm_device *pwm); //释放PWM
int (*config)(struct pwm_chip *chip,struct pwm_device *pwm, int duty_ns, int period_ns); //配置PWM周期和占空比
int (*set_polarity)(struct pwm_chip *chip, struct pwm_device *pwm, enum pwm_polarity polarity); //配置PWM极性
int (*enable)(struct pwm_chip *chip, struct pwm_device *pwm); //使能PWM
void (*disable)(struct pwm_chip *chip,struct pwm_device *pwm); //关闭PWM
struct module *owner;
};
config\enable\disable这个三个肯定是需要实现的,否则打开/关闭 PWM,设置 PWM 的占空比这些就没操作了。
使用该结构体的思路
-
初始化pwm_chip结构体各成员变量
-
然后向内核注册初始化完成以后的pwm_chip,使用pwmchip_add 函数
此函数定义在 drivers/pwm/core.c 文件中,函数原型如下:
cint pwmchip_add(struct pwm_chip *chip) //返回值: 0 成功;负数 失败。 int pwmchip_remove(struct pwm_chip *chip) //返回值: 0 成功;负数 失败。
PWM驱动源码分析
im6ull的 PWM 驱动文件是drivers/pwm/pwm-imx.c。
c
static struct imx_pwm_data imx_pwm_data_v1 = {
.config = imx_pwm_config_v1,
.set_enable = imx_pwm_set_enable_v1,
};
static struct imx_pwm_data imx_pwm_data_v2 = {
.config = imx_pwm_config_v2,
.set_enable = imx_pwm_set_enable_v2,
};
static const struct of_device_id imx_pwm_dt_ids[] = {
{ .compatible = "fsl,imx1-pwm", .data = &imx_pwm_data_v1, },
{ .compatible = "fsl,imx27-pwm", .data = &imx_pwm_data_v2, },
{ /* sentinel */ }
};
static int imx_pwm_probe(struct platform_device *pdev)
{
}
static int imx_pwm_remove(struct platform_device *pdev)
{
}
static struct platform_driver imx_pwm_driver = {
.driver = {
.name = "imx-pwm",
.of_match_table = imx_pwm_dt_ids,
},
.probe = imx_pwm_probe,
.remove = imx_pwm_remove,
};
module_platform_driver(imx_pwm_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
是一个标准的platform平台设备驱动文件
当设备树 PWM 节点的 compatible 属性值为"fsl,imx27-pwm"的话就会匹配此驱动,注意后面的.data 为 imx_pwm_data_v2 型的结构体变量,内容如下:
imx_pwm_config_v2 函数就是最终操作 I.MX6ULL 的 PWM 外设寄存器,进行实际配置的函数。 imx_pwm_set_enable_v2 就是具体使能 PWM 的函数。

c
static void imx_pwm_set_enable_v2(struct pwm_chip *chip, bool enable)
{
struct imx_chip *imx = to_imx_chip(chip);
u32 val;
//读取PWMCR寄存器的值
val = readl(imx->mmio_base + MX3_PWMCR);
if (enable)
val |= MX3_PWMCR_EN; //如果为真使能PWM,将寄存器的bit0置1即可
else
val &= ~MX3_PWMCR_EN; //关闭PWM,将寄存器的bit0置0即可
writel(val, imx->mmio_base + MX3_PWMCR); //将新的 val 值写入到 PWMCR 寄存器中。
}
static int imx_pwm_config(struct pwm_chip *chip,
struct pwm_device *pwm, int duty_ns, int period_ns)
{
struct imx_chip *imx = to_imx_chip(chip);
int ret;
ret = clk_prepare_enable(imx->clk_ipg);
if (ret)
return ret;
ret = imx->config(chip, pwm, duty_ns, period_ns);
clk_disable_unprepare(imx->clk_ipg);
return ret;
}
static int imx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct imx_chip *imx = to_imx_chip(chip);
int ret;
ret = clk_prepare_enable(imx->clk_per);
if (ret)
return ret;
imx->set_enable(chip, true);
return 0;
}
static void imx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct imx_chip *imx = to_imx_chip(chip);
imx->set_enable(chip, false);
clk_disable_unprepare(imx->clk_per);
}
static struct pwm_ops imx_pwm_ops = {
.enable = imx_pwm_enable,
.disable = imx_pwm_disable,
.config = imx_pwm_config,
.owner = THIS_MODULE,
};
struct imx_pwm_data {
int (*config)(struct pwm_chip *chip,
struct pwm_device *pwm, int duty_ns, int period_ns);
void (*set_enable)(struct pwm_chip *chip, bool enable);
};
static struct imx_pwm_data imx_pwm_data_v1 = {
.config = imx_pwm_config_v1,
.set_enable = imx_pwm_set_enable_v1,
};
static struct imx_pwm_data imx_pwm_data_v2 = {
//这两个函数由于打开和关闭对应的PWM
.config = imx_pwm_config_v2,
.set_enable = imx_pwm_set_enable_v2,
};
static const struct of_device_id imx_pwm_dt_ids[] = {
{ .compatible = "fsl,imx1-pwm", .data = &imx_pwm_data_v1, },
{ .compatible = "fsl,imx27-pwm", .data = &imx_pwm_data_v2, },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_pwm_dt_ids);
static int imx_pwm_probe(struct platform_device *pdev)
{
const struct of_device_id *of_id =
of_match_device(imx_pwm_dt_ids, &pdev->dev);
const struct imx_pwm_data *data;
struct imx_chip *imx;
struct resource *r;
int ret = 0;
if (!of_id)
return -ENODEV;
//申请内存
imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL);
if (imx == NULL)
return -ENOMEM;
imx->clk_per = devm_clk_get(&pdev->dev, "per");
if (IS_ERR(imx->clk_per)) {
dev_err(&pdev->dev, "getting per clock failed with %ld\n",
PTR_ERR(imx->clk_per));
return PTR_ERR(imx->clk_per);
}
imx->clk_ipg = devm_clk_get(&pdev->dev, "ipg");
if (IS_ERR(imx->clk_ipg)) {
dev_err(&pdev->dev, "getting ipg clock failed with %ld\n",
PTR_ERR(imx->clk_ipg));
return PTR_ERR(imx->clk_ipg);
}
//初始化imx的chip(pwm_chip对象)
//设置pwm_ops的操作集合
imx->chip.ops = &imx_pwm_ops;
imx->chip.dev = &pdev->dev;
imx->chip.base = -1;
imx->chip.npwm = 1;
imx->chip.can_sleep = true;
//获取设备树中的PWM节点关于PWM控制器的地址信息
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
//地址映射
imx->mmio_base = devm_ioremap_resource(&pdev->dev, r);
if (IS_ERR(imx->mmio_base))
return PTR_ERR(imx->mmio_base);
//将imx_pwm_config_v2函数中的两个config和set_enable函数赋给imx的相同变量
data = of_id->data;
imx->config = data->config;
imx->set_enable = data->set_enable;
ret = pwmchip_add(&imx->chip);
if (ret < 0)
return ret;
platform_set_drvdata(pdev, imx);
return 0;
}
static int imx_pwm_remove(struct platform_device *pdev)
{
struct imx_chip *imx;
imx = platform_get_drvdata(pdev);
if (imx == NULL)
return -ENODEV;
return pwmchip_remove(&imx->chip);
}
实战演练
设备树修改
c
&pwm3 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pwm3>;
clocks = <&clks IMX6UL_CLK_PWM3>,
<&clks IMX6UL_CLK_PWM3>;
status = "okay";
};
IOMUXC{
pinctrl_pwm3: pwm3grp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO04__PWM3_OUT 0x110b0
>;
};
};
在imx6ull.dtsi中已经写好了pwm3的相关参数,需要添加其他的引脚信息和节点信息如上
内核使能支持PWM(默认已经使能)
c
-> Device Drivers
-> Pulse-Width Modulation (PWM) Support
-> <*> i.MX PWM support
最后将设备树、zImage拷贝到启动文件中
c
cd /sys/class/pwm
ls
pwmchip0 pwmchip2 pwmchip4 pwmchip6
pwmchip1 pwmchip3 pwmchip5 pwmchip7
pwmchip2就是刚添加的pwm3设备
导出该节点用于控制
c
echo 0 > /sys/class/pwm/pwmchip2/export
c
/pwmchip2 # ls
device npwm pwm0 uevent
export power subsystem unexport
pwm0就是导出的文件
使能 PWM3
c
echo 1 > /sys/class/pwm/pwmchip2/pwm0/enable
设置 PWM3 的频率
c
echo 50000 > /sys/class/pwm/pwmchip2/pwm0/period
设置 PWM3 的占空比 = period/duty_cycle = 10000/50000 = 20%
c
echo 10000 > /sys/class/pwm/pwmchip2/pwm0/duty_cycle
输出一个50HZ(20ms)的周期信号
设置周期:
priod : 20000000 ------> 20ms
设置占空比:
duty_cycle:
500000 ------> 0.5ms (2.5%) - 0度
1000000 ------> 1ms (5%) - 45度
1500000 ------> 1.5ms (5%) - 90度
不同占空比(2.5% - 12.5%,0.5ms ~ 2.5ms)控制舵机的转向角度为
红色线接电源(4.8V---7.2V), 棕色线接地, 橙色线接pwm
shell
echo 0 > /sys/class/pwm/pwmchip2/export
echo 1 > /sys/class/pwm/pwmchip2/pwm0/enable
echo 20000000 > /sys/class/pwm/pwmchip2/pwm0/period
//转到0度
echo 500000 > /sys/class/pwm/pwmchip2/pwm0/duty_cycle
//转到45度
echo 1000000 > /sys/class/pwm/pwmchip2/pwm0/duty_cycle
