Linux驱动开发|PWM驱动

本文详细介绍了NXP i.MX6ULL处理器中PWM驱动的工作原理,包括设备树配置、PWM子系统、驱动源码分析。通过修改设备树启用PWM功能,并展示了如何在用户空间配置和测试PWM,同时给出了PWM背光控制的应用示例。
摘要由CSDN通过智能技术生成

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";
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

安迪西嵌入式

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值