Linux驱动开发|PWM驱动
PWM驱动
PWM驱动
一、PWM驱动介绍
PWM 原理可参考LCD背光调节实验一文的介绍,这里主要介绍 NXP 原厂提供的 Linux 内核自带的 PWM 驱动
1.1 设备树下的 PWM 控制器节点
I.MX6ULL 有8路 PWM 输出,因此对应8个 PWM 控制器,所以在设备树下有8个 PWM 控制器节点。这8路 PWM 都属于 I.MX6ULL 的 AIPS-1 域,8路 PWM 的设备树节点内容都是一样的,除了 reg 属性不同。本章实验使用 GPIO1_IO04 这个引脚来完成 PWM 实验,以 PWM3 为例,imx6ull.dtsi 文件中的 pwm3 节点信息如下:
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>;
};
1.2 PWM 子系统
内核提供了个 PWM 子系统框架,编写 PWM 驱动时一定要符合这个框架。 PWM 子系统的核心是 pwm_chip 结构体,定义在文件 include/linux/pwm.h 中
struct pwm_chip {
struct device *dev;
struct list_head list;
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;
};
其中 pwm_ops 结构体就是 PWM 外设的各种操作函数集合,编写驱动时需要开发人员实现, pwm_ops 结构体定义在 pwm.h 头文件中
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, //配置PWM周期和占空比
struct pwm_device *pwm,
int duty_ns, int period_ns);
int (*set_polarity)(struct pwm_chip *chip, //设置PWM极性
struct pwm_device *pwm,
enum pwm_polarity polarity);
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;
};
PWM 子系统驱动的核心初始化 pwm_chip 结构体,然后向内核注册初始化完成以后的pwm_chip
- 注册 pwm_chip :
int pwmchip_add(struct pwm_chip *chip)
//chip:要向内核注册的pwm_chip
//返回值: 0 成功;负数 失败
- 删除 pwm_chip :
int pwmchip_remove(struct pwm_chip *chip)
//chip:要移除的pwm_chip
//返回值:0 成功;负数 失败
1.3 PWM 驱动源码分析
下面简单分析一下 Linux 内核自带的 I.MX6ULL PWM 驱动,打开驱动文件 pwm-imx.c
static const struct of_device_id imx_pwm_dt_ids[] = {
{ .compatible = "fsl,imx1-pwm", .data = &imx_pwm_data_v1, },
//设备树PWM节点compatible属性值为“fsl,imx27-pwm”的话就会匹配此驱动
{ .compatible = "fsl,imx27-pwm", .data = &imx_pwm_data_v2, },
{ /* sentinel */ }
};
......
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);
/**************************************************/
//imx_pwm_data_v2是一个 imx_pwm_data 类型的结构体变量
static struct imx_pwm_data imx_pwm_data_v2 = {
.config = imx_pwm_config_v2,
.set_enable = imx_pwm_set_enable_v2,
};
当设备树节点和驱动匹配以后 imx_pwm_probe 函数就会执行
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_chip 类型的结构体指针变量申请内存
imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL);
if (imx == NULL)
return -ENOMEM;
......
//初始化imx的 chip 成员变量
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控制器的地址信息,
//然后再进行内存映射,就可得到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);
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;
}
pwm_chip 的 ops 操作集为 imx_pwm_ops, imx_pwm_ops 定义如下:
static struct pwm_ops imx_pwm_ops = {
.enable = imx_pwm_enable, //使能PWM
.disable = imx_pwm_disable, //关闭PWM
.config = imx_pwm_config, //配置PWM
.owner = THIS_MODULE,
};
整个 pwm-imx.c 文件里面,最终和 PWM 寄存器打交道的是 imx_pwm_config_v2 和 imx_pwm_set_enable_v2 这两个函数
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);
//如果enable为真,表示使能PWM,将PWMCR寄存器的bit0置1
if (enable)
val |= MX3_PWMCR_EN;
else //否则表示关闭PWM,将PWMCR寄存器的bit0清0
val &= ~MX3_PWMCR_EN;
//将新的val值写入到PWMCR寄存器中
writel(val, imx->mmio_base + MX3_PWMCR);
}
/********************************************************************/
static int imx_pwm_config_v2(struct pwm_chip *chip,
struct pwm_device *pwm, int duty_ns, int period_ns) {
struct imx_chip *imx = to_imx_chip(chip);
struct device *dev = chip->dev;
unsigned long long c;
unsigned long period_cycles, duty_cycles, prescale;
unsigned int period_ms;
bool enable = test_bit(PWMF_ENABLED, &pwm->flags);
int wait_count = 0, fifoav;
u32 cr, sr;
......
//根据参数duty_ns和period_ns来计算出应该写入到寄存器
//里面的值duty_cycles 和 period_cycles
c = clk_get_rate(imx->clk_per);
c = c * period_ns;
do_div(c, 1000000000);
period_cycles = c;
prescale = period_cycles / 0x10000 + 1;
period_cycles /= prescale;
c = (unsigned long long)period_cycles * duty_ns;
do_div(c, period_ns);
duty_cycles = c;
if (period_cycles > 2)
period_cycles -= 2;
else
period_cycles = 0;
//将计算得到的duty_cycles写入PWMSAR寄存器中,设置PWM的占空比
writel(duty_cycles, imx->mmio_base + MX3_PWMSAR);
//将计算得到的period_cycles写入PWMPR寄存器中,设置PWM的频率
writel(period_cycles, imx->mmio_base + MX3_PWMPR);
cr = MX3_PWMCR_PRESCALER(prescale) |
MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN |
MX3_PWMCR_DBGEN | MX3_PWMCR_CLKSRC_IPG_HIGH;
if (enable)
cr |= MX3_PWMCR_EN;
writel(cr, imx->mmio_base + MX3_PWMCR);
return 0;
}
二、PWM驱动编写
2.1 修改设备树
NXP 已经写好了 PWM 驱动,在实际使用时只需要修改设备树即可,本例中 GPIO1_IO04 可以作为 PWM3 的输出引脚,因此需要在设备树里面添加 GPIO1_IO04 引脚信息以及 PWM3 控制器对应的节点信息
- 添加 GPIO1_IO04 引脚信息:在设备树文件中 iomuxc 节点下添加引脚信息
pinctrl_pwm3: pwm3grp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO04__PWM3_OUT 0x110b0
>;
};
- 向 pwm3 节点追加信息:在设备树文件 pwm3 节点中添加如下信息
&pwm3 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pwm3>;
clocks = <&clks IMX6UL_CLK_PWM3>,
<&clks IMX6UL_CLK_PWM3>;
status = "okay";
};
- 屏蔽掉其他复用的 IO:检查是否有其他外设用到 GPIO1_IO04 和"gpio1 4",有的话屏蔽掉
设备树修改完成以后重新编译设备树,并使用新的设备树启动系统
2.2 使能 PWM 驱动
NXP 官方的 Linux 内核已经默认使能了 PWM 驱动
-> Device Drivers
-> Pulse-Width Modulation (PWM) Support
-> <*> i.MX PWM support
三、PWM驱动测试
3.1 PWM驱动测试
将开发板的 GPIO_4 (GPIO1_IO04) 引脚连接到示波器上,通过示波器来查看 PWM 波形图。可以直接在用户层来配置 PWM,进入目录 /sys/class/pwm 中
上图中 pwmchip0 ~ pwmchip7 对应 I.MX6ULL 的 PWM1 ~ PWM8,这里要用到 pwmchip2
- 调出 pwmchip2 的 pwm0 子目录
echo 0 > /sys/class/pwm/pwmchip2/export
#执行完后会在pwmchip2目录下生成一个名为“pwm0”的子目录
- 使能 PWM3
echo 1 > /sys/class/pwm/pwmchip2/pwm0/enable
- 设置 PWM3 的频率:设置的是周期值,单位为 ns,比如 20KHz 频率的周期就是 50000ns
echo 50000 > /sys/class/pwm/pwmchip2/pwm0/period
- 设置 PWM3 的占空比:设置的一个周期的 ON 时间,即高电平时间。比如 20KHz 频率下 20%占空比的 ON 时间就是 10000
echo 10000 > /sys/class/pwm/pwmchip2/pwm0/duty_cycle
设置完成使用示波器查看波形是否正确
3.2 PWM背光测试
有时需要在某个外设上添加 PWM 功能,比如:LCD 的背光控制就是 PWM 来完成的,下面以 PWM 背光控制为例,介绍如何在其他外设上添加 PWM 功能
首先是设备树描述,直接看内核里面关于 backlight(背光)的绑定文档,路径为
Documentation/devicetree/bindings/video/backlight/pwm-backlight.txt,此文档描述了如何创建 backlight 节点来使用 linux 内核自带的 pwm 背光驱动,必要的属性如下:
– compatible:为"pwm-backlight",可匹配到背光驱动 drivers/video/backlight/pwm_bl.c
– pwms: 指定背光使用哪一路PWM,及PWM相关的属性
– brightness-levels: 背光等级数组,范围 0 ~ 255,对应占空比为 0% ~ 100%
– default-brightness-level: 默认的背光等级,这里是数索引编号,不是具体的数值
– power-supply: 支持的电压,此属性可以不需要
以 ALPHA 开发板为例,看一下 PWM 背光节点是如何设置的,打开设备树文件,找到如下所示节点内容:
backlight {
compatible = "pwm-backlight"; //必须为“pwm-backlight”
pwms = <&pwm1 0 5000000>; //使用pwm1、通道0、周期为 5000000ns
//背光等级数组,一共8个等级,索引编号从0到7
brightness-levels = <0 4 8 16 32 64 128 255>;
default-brightness-level = <7>; //背光默认处于第7等级,即255,为100%占空比
status = "okay";
};
更多推荐
所有评论(0)