Linux PWM子系统及高通平台PWM调试案例解析

基本概念

前后调试过两次高通平台pwm,个人觉得有必要来这样一篇帖子记录一下,有备无患。

首先明确基本概念,pwm全称为pulse width modulation,即脉冲宽度调制。脉冲即周期脉冲方波,宽度调制说的就是脉冲方波高低电平占比,就是我们常说的占空比,那调这玩意有啥用呢?

既然存在pwm就肯定有需要他的地方,到目前为止,我所接触到的显示屏都是TFT-LCD屏,也就是我们比较常用的薄膜晶体管液晶显示屏,特点是显示屏本身不能发光,需要依赖背光板才能显示,有关屏幕显示技术可自行查阅资料,此处不做过多展开。

所以要想点亮LED背光板,就需要用到背光芯片为背光板供电并调整其亮度;而我们调试的pwm就是给背光芯片使用,并通过占空比来调整背光亮度。

调试PWM功能总共分两块,PWM控制器 & 端子配置

PWM子系统框图

Linux PWM子系统框图如上

  • 最底层PWM硬件,一般soc跟mcu均具备输出pwm的能力,高通平台由pmic输出PWM,与soc之间通过spmi通信;
  • PWM HW driver,硬件驱动层,一般由芯片厂商提供,该驱动用于直接读写寄存器来控制PWM硬件;
  • PWM subsystem,HW driver备好之后,会向PWM子系统注册PWM设备,子系统这部分为Linux源码,目的是为了吸收不同芯片厂商带来的差异,并向上提供统一的接口用于控制PWM;
  • PWM consumer driver,PWM设备向系统注册注册完毕之后,PWM的使用者会通过PWM子系统提供的接口来获取PWM设备,再通过PWM ops来控制跟调整PWM参数;这个consumer driver推荐使用led driver,注册led设备后,led class会在sysfs里提供brightness/enable等接口,方便使用;
  • Application,有了led提供的接口,应用程序就可以通过接口来控制PWM了。

PWM HW driver

HW driver加载后,初始化PWM控制器并向系统注册PWM设备,查阅了Linux 4下面两个版本,发现其中并没有包含高通PWM driver,此处只能贴上节选,想要查看全部源码需要自行查阅资料。

高通私有qpnp pwm chip结构 & Linux标准pwm chip结构,二者属于嵌套结构,在Linux结构基础上做了再次封装,用于保存高通寄存器及其他私有变量;

/* Public facing structure */
struct qpnp_pwm_chip {
	struct	spmi_device	*spmi_dev;
	struct pwm_chip         chip;
	bool			enabled;
	struct _qpnp_pwm_config	pwm_config;
	struct	qpnp_lpg_config	lpg_config;
	spinlock_t		lpg_lock;
	enum qpnp_lpg_revision	revision;
	u8			sub_type;
	u32			flags;
	u8	qpnp_lpg_registers[QPNP_TOTAL_LPG_SPMI_REGISTERS];
	int			channel_id;
	const char		*channel_owner;
	u32			dtest_line;
	u32			dtest_output;
	bool			in_test_mode;
};

/**
 * struct pwm_chip - abstract a PWM controller
 * @dev: device providing the PWMs
 * @list: list node for internal use
 * @ops: callbacks for this PWM controller
 * @base: number of first PWM controlled by this chip
 * @npwm: number of PWMs controlled by this chip
 * @pwms: array of PWM devices allocated by the framework
 * @can_sleep: must be true if the .config(), .enable() or .disable()
 *             operations may sleep
 */
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;
};
static struct pwm_ops qpnp_pwm_ops = {
	.enable = qpnp_pwm_enable,
	.disable = qpnp_pwm_disable,
	.config = qpnp_pwm_config,
	.free = qpnp_pwm_free,
	.owner = THIS_MODULE,
};
static int qpnp_pwm_probe(struct spmi_device *spmi)
{
	struct qpnp_pwm_chip	*pwm_chip;
	int			rc;

	pwm_chip = kzalloc(sizeof(*pwm_chip), GFP_KERNEL);
	if (pwm_chip == NULL) {
		pr_err("kzalloc() failed.\n");
		return -ENOMEM;
	}

	spin_lock_init(&pwm_chip->lpg_lock);

	pwm_chip->spmi_dev = spmi;
	dev_set_drvdata(&spmi->dev, pwm_chip);

	rc = qpnp_parse_dt_config(spmi, pwm_chip);

	if (rc) {
		pr_err("Failed parsing DT parameters, rc=%d\n", rc);
		goto failed_config;
	}

	pwm_chip->chip.dev = &spmi->dev;
	pwm_chip->chip.ops = &qpnp_pwm_ops;
	pwm_chip->chip.base = -1;
	pwm_chip->chip.npwm = 1;

	rc = pwmchip_add(&pwm_chip->chip);
	if (rc < 0) {
		pr_err("pwmchip_add() failed: %d\n", rc);
		goto failed_insert;
	}
        ...

	return rc;
}

probe函数中分析dts参数,创建一个新的qpnp pwm chip设备,填充成员变量及ops,并通过函数pwmchip_add()向系统注册PWM资源(pwm chip),共consumer driver申请使用。

PWM subsystem

完成HW driver注册后,系统有了可申请的PWM设备(pwm chip),现在看PWM子系统,这部分源码隶属于Linux,/linux-4.4/drivers/pwm/core.c

/**
 * pwm_request() - request a PWM device
 * @pwm: global PWM device index
 * @label: PWM device label
 *
 * This function is deprecated, use pwm_get() instead.
 *
 * Returns: A pointer to a PWM device or an ERR_PTR()-encoded error code on
 * failure.
 */
struct pwm_device *pwm_request(int pwm, const char *label)
{
	struct pwm_device *dev;
	int err;

	if (pwm < 0 || pwm >= MAX_PWMS)
		return ERR_PTR(-EINVAL);

	mutex_lock(&pwm_lock);

	dev = pwm_to_device(pwm);
	if (!dev) {
		dev = ERR_PTR(-EPROBE_DEFER);
		goto out;
	}

	err = pwm_device_request(dev, label);
	if (err < 0)
		dev = ERR_PTR(err);

out:
	mutex_unlock(&pwm_lock);

	return dev;
}
EXPORT_SYMBOL_GPL(pwm_request);

此处以pwm_request为例,函数已导出,供consumer driver使用,通过该接口可获取pwm设备;

此外还有enable/disable/of_pwm_get/pwm_config等接口,用来操作pwm设备,内部基本使用了HW driver中提供的ops,也有调用HW driver中的全局函数。

PWM consumer driver

consumer driver作为PWM的使用者,以led driver为例,一方面负责调用接口调试PWM设备,另一方面通过sysfs提供接口,供应用程序使用,显而易见在这个过程中led driver起到了承上启下的作用;

源码中随便找了一个注册led的实例,/linux-4.4/drivers/leds/leds-lm3530.c

static int lm3530_probe(struct i2c_client *client,
			   const struct i2c_device_id *id)
{
	...
	drvdata->led_dev.name = LM3530_LED_DEV;
	drvdata->led_dev.brightness_set = lm3530_brightness_set;
	drvdata->led_dev.max_brightness = MAX_BRIGHTNESS;
	drvdata->led_dev.groups = lm3530_groups;

	...
	err = led_classdev_register(&client->dev, &drvdata->led_dev);
	if (err < 0) {
		dev_err(&client->dev, "Register led class failed: %d\n", err);
		return err;
	}

	return 0;
}

led_dev原型为,struct led_classdev,源码路径/linux-4.4/include/linux/leds.h;

struct led_classdev的成员里面我们比较关心brightness_set()/max_brightness/name;

  • brightness_set()用来调整亮度,此处我们填充PWM subsystem提供的接口pwm_config(),通过配置PWM来起到调整亮度的作用;
  • max_brightness最大亮度值,一般255;
  • name是注册的led设备名,这个名字最终会体现在/sys/class/leds/下;

将led_dev成员变量及方法填充完毕之后,通过led_classdev_register()向系统注册led设备,完成注册即可通过终端查看/sys/class/leds/下是否有你刚注册的设备。

Application

应用程序通过sysfs中led class提供的属性文件brightness/max_brightness,对其进行读写来调整亮度值,也有追加brightness_state的,用来控制背光使能,这个需要自行封装,一般都是通过背光使能端子来控制其使能;

读写brightness节点,进入内核后led调用brightness_set()->pwm_config()->HW driver,完成对PWM控制器的控制,进而达到调整亮度的目的。

端子配置

到目前位置,我们仅仅完成了对PWM控制器的配置,并向应用层提供接口;想要完成PWM输出,还有一个重要的部分就是端子配置,否则PWM控制器配置的再漂亮,没有输出端口也是没有意义的;

由于我们平台端子配置都是现成的,手里也没有高通PMIC原厂手册,所以此处只做定性分析;

端子配置的目的是将配置好的PWM通过某一端子输出,端子配置项包括function/strength/pull/mode等等,Documentation中有包括这部分dts的配置方法及实例,以下配置仅供参考。

qpnp: qcom,spmi@fc4c0000 {
	#address-cells = <1>;
	#size-cells = <0>;
	interrupt-controller;
	#interrupt-cells = <3>;

	qcom,pm8941@0 {
		spmi-slave-container;
		reg = <0x0>;
		#address-cells = <1>;
		#size-cells = <1>;

		pm8941_gpios: gpios {
			spmi-dev-container;
			compatible = "qcom,qpnp-pin";
			gpio-controller;
			#gpio-cells = <2>;
			#address-cells = <1>;
			#size-cells = <1>;

			gpio@c000 {
				reg = <0xc000 0x100>;
				qcom,pin-num = <62>;
			};

			gpio@c100 {
				reg = <0xc100 0x100>;
				qcom,pin-num = <20>;
				qcom,source_sel = <2>;
				qcom,pull = <5>;
			};
		};

		qcom,testgpio@1000 {
			compatible = "qcom,qpnp-testgpio";
			reg = <0x1000 0x1000>;
			gpios = <&pm8941_gpios 62 0x0 &pm8941_gpios 20 0x1>;
		};
	};
};

 

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux中设置GPIO的PWM输出,可以使用sysfs接口或者使用GPIO子系统pwm子系统的API。 1. 使用sysfs接口: 首先需要确认系统中是否支持PWM输出,可以查看/sys/class/pwm目录中是否有pwmchip0等类似的文件夹。然后就可以通过sysfs接口设置GPIO的PWM输出,具体步骤如下: a. 确认使用的GPIO引脚是否支持PWM输出,可以通过查看/sys/class/gpio/export文件中是否存在对应的GPIO编号来确认。 b. 在/sys/class/pwm目录下,创建对应的PWM设备文件夹: ``` echo 0 > /sys/class/pwm/pwmchip0/export ``` c. 设置PWM的周期和占空比: ``` echo 20000000 > /sys/class/pwm/pwmchip0/pwm0/period #设置周期为20ms echo 1000000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle #设置占空比为5% ``` d. 开启PWM输出: ``` echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable ``` 2. 使用GPIO子系统pwm子系统的API: a. 首先需要在设备树中添加PWM节点,配置对应的GPIO引脚和PWM控制器。 b. 在应用程序中,使用GPIO子系统pwm子系统的API来设置PWM输出,具体步骤如下: ``` #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <sys/ioctl.h> #include <linux/pwm.h> int main(int argc, char *argv[]) { int fd, ret; struct pwm_state state; fd = open("/dev/pwmchip0", 0); if (fd < 0) { printf("open pwmchip0 failed\n"); return -1; } memset(&state, 0, sizeof(state)); state.period = 20000000; //设置周期为20ms state.duty_cycle = 1000000; //设置占空比为5% state.enabled = 1; //开启PWM输出 ret = ioctl(fd, PWM_SET_STATE, &state); if (ret < 0) { printf("set pwm state failed, %s\n", strerror(errno)); close(fd); return -1; } close(fd); return 0; } ``` 以上是一些简单的操作,如果需要更加详细的设置可以参考Linux内核文档中关于PWM子系统的说明。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值