linux驱动之pwm驱动

框架分析:

pwm框架与前面的i2c/spi/lcd框架有这相同之处也有不同之处:

同:

利用platform支持,这一点我几乎确认了,所以框架都要使用platform,因为厂家编写的驱动对应的硬件是会更新的,他们必须在platform下注册驱动。

异:

我们知道pwm要改变占空比,频率,等参数,而这些修改pwm框架支持了,让我们直接从/sys文件系统下更改属性文件就行,十分方便。前提我们的驱动必须支持对应的函数。(我们都知道框架只是给内核引出一个api利用这个api可以一直调用到我们实现的函数,所以最底层还是得我们自己来实现)

核心层文件:linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek\drivers\pwm\core.c

在这个文件中最主要是实现中间层的框架性函数,提供给上层/sys和下层驱动。

不像spi/i2c一样在这里会注册一个字符设备/总线。

上层sys: linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek\drivers\pwm\sysfs.c

在这个文件中注册了pwm的class,并且提供了一些属性文件的stroe与show函数,store函数会间接调用到我们驱动实现的函数。

注册class:

static int __init pwm_sysfs_init(void)
{
	return class_register(&pwm_class);
}

注册device:当驱动的platform被probe时会被调用

void pwmchip_sysfs_export(struct pwm_chip *chip)
{
	struct device *parent;

	/*
	 * If device_create() fails the pwm_chip is still usable by
	 * the kernel its just not exported.
	 */
	parent = device_create(&pwm_class, chip->dev, MKDEV(0, 0), chip,
			       "pwmchip%d", chip->base);
	if (IS_ERR(parent)) {
		dev_warn(chip->dev,
			 "device_create failed for pwm_chip sysfs export\n");
	}
}

提供一些属性的store函数:

static ssize_t pwm_duty_cycle_store(struct device *child,
				    struct device_attribute *attr,
				    const char *buf, size_t size)
{
	struct pwm_device *pwm = child_to_pwm_device(child);
	unsigned int val;
	int ret;

	ret = kstrtouint(buf, 0, &val);
	if (ret)
		return ret;

	ret = pwm_config(pwm, val, pwm->period);

	return ret ? : size;
}

pwm_config:core.c中定义

int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
{
	int err;

	if (!pwm || duty_ns < 0 || period_ns <= 0 || duty_ns > period_ns)
		return -EINVAL;

	err = pwm->chip->ops->config(pwm->chip, pwm, duty_ns, period_ns);//最终调用到ops的config,这是在驱动中定义的。
	if (err)
		return err;

	pwm->duty_cycle = duty_ns;
	pwm->period = period_ns;

	return 0;
}
驱动层:linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek\drivers\pwm\pwm-imx.c

所以我们驱动层大概要实现什么功能?

1.定义自己的pwm_chip结构体变量这个是贯穿三层的核心

struct pwm_chip {
	struct device		*dev;
	struct list_head	list;
	const struct pwm_ops	*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;
};

2.实现ops

struct pwm_ops {
	int			(*request)(struct pwm_chip *chip,
					   struct pwm_device *pwm);
	void			(*free)(struct pwm_chip *chip,
					struct pwm_device *pwm);
	int			(*config)(struct pwm_chip *chip,
					  struct pwm_device *pwm,
					  int duty_ns, int period_ns);
	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);
	void			(*disable)(struct pwm_chip *chip,
					   struct pwm_device *pwm);
#ifdef CONFIG_DEBUG_FS
	void			(*dbg_show)(struct pwm_chip *chip,
					    struct seq_file *s);
#endif
	struct module		*owner;
};
解释:
free: 释放 PWM 通道。该函数用于释放之前申请的 PWM 通道,以便将其归还给 PWM 控制器。该函数没有返回值。

config: 配置 PWM 通道的输出参数。该函数用于配置 PWM 通道的输出参数,例如占空比和周期。该函数返回 0 表示配置成功,返回负数表示失败。

set_polarity: 设置 PWM 通道的极性。该函数用于设置 PWM 通道的极性,即设置当 PWM 信号处于高电平时,输出的是 PWM 通道的最大占空比还是最小占空比。该函数返回 0 表示设置成功,返回负数表示失败。

enable: 启用 PWM 通道。该函数用于启用 PWM 通道,即开始输出 PWM 信号。该函数返回 0 表示启用成功,返回负数表示失败。

disable: 禁用 PWM 通道。该函数用于禁用 PWM 通道,即停止输出 PWM 信号。该函数没有返回值。

dbg_show: 在调试信息文件中显示 PWM 控制器的状态。该函数用于在调试信息文件中显示 PWM 控制器的状态,例如 PWM 通道的占空比和周期等信息。该函数只在开启了 CONFIG_DEBUG_FS 配置选项时才会被编译和调用。

我们主要实现4个就好:

static struct pwm_ops imx_pwm_ops = {
	.enable = imx_pwm_enable,
	.disable = imx_pwm_disable,
	.config = imx_pwm_config,
	.owner = THIS_MODULE,
};

3.注册pwm_chip

调用pwmchip_add (core.c中定义)

当然这一切都是在platform的probe中实现的:

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 = 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.ops = &imx_pwm_ops;
	imx->chip.dev = &pdev->dev;
	imx->chip.base = -1;
	imx->chip.npwm = 1;
	imx->chip.can_sleep = true;

	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;
}

修改设备树:

可以参考linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek\Documentation\devicetree\bindings\pwm\imx-pwm.txt

一般在dtsi下都会有对应的pwm节点,我们只需要在自己的设备树下再添加一些其它的信息即可(如:pinctrl)

实验:

我们配置GPIO1_IO4输出PWM3。imx6ull一共有8个pwm控制器,也就是有8路pwm。

在原理图上找不到,那我们就到imx6ul-pinfun.h中去找。

112 #define MX6UL_PAD_GPIO1_IO04__PWM3_OUT                            0x006C 0x0    2F8 0x0000 0x1 0x0

由于在imx6ull.dtsi中已经有了pwm3的节点。

 455             pwm3: pwm@02088000 {
 456                 compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm";
 457                 reg = <0x02088000 0x4000>;
 458                 interrupts = <GIC_SPI 85 IRQ_TYPE_LEVEL_HIGH>;
 459                 clocks = <&clks IMX6UL_CLK_PWM3>,
 460                      <&clks IMX6UL_CLK_PWM3>;
 461                 clock-names = "ipg", "per";
 462                 #pwm-cells = <2>;
 463             };

我们只需要在自己的设备树中加上pinctrl就可以了。

314         pinctrl_pwm3: pwm3grp {
315             fsl,pins = <
316                 MX6UL_PAD_GPIO1_IO04__PWM3_OUT 0x110b0
317             >;
318         };
624 &pwm3 {
625     pinctrl-names = "default";
626     pinctrl-0 = <&pinctrl_pwm3>;
627     status = "okay";
628 };

使用sysfs注意事项:

当我们的platform匹配成功后就会创建pwmchipxx的节点在/sys/class/pwm下

void pwmchip_sysfs_export(struct pwm_chip *chip)
{
	struct device *parent;

	/*
	 * If device_create() fails the pwm_chip is still usable by
	 * the kernel its just not exported.
	 */
	parent = device_create(&pwm_class, chip->dev, MKDEV(0, 0), chip,
			       "pwmchip%d", chip->base);
	if (IS_ERR(parent)) {
		dev_warn(chip->dev,
			 "device_create failed for pwm_chip sysfs export\n");
	}
}

那么属性文件呢?

我们通过阅读sysfs层的代码就能找到答案

static ssize_t pwm_export_store(struct device *parent,
				struct device_attribute *attr,
				const char *buf, size_t len)
{
	struct pwm_chip *chip = dev_get_drvdata(parent);
	struct pwm_device *pwm;
	unsigned int hwpwm;
	int ret;

	ret = kstrtouint(buf, 0, &hwpwm);
	if (ret < 0)
		return ret;

	if (hwpwm >= chip->npwm)
		return -ENODEV;

	pwm = pwm_request_from_chip(chip, hwpwm, "sysfs");
	if (IS_ERR(pwm))
		return PTR_ERR(pwm);

	ret = pwm_export_child(parent, pwm);
	if (ret < 0)
		pwm_put(pwm);

	return ret ? : len;
}

pwm_export_child:

static int pwm_export_child(struct device *parent, struct pwm_device *pwm)
{
	struct pwm_export *export;
	int ret;

	if (test_and_set_bit(PWMF_EXPORTED, &pwm->flags))
		return -EBUSY;

	export = kzalloc(sizeof(*export), GFP_KERNEL);
	if (!export) {
		clear_bit(PWMF_EXPORTED, &pwm->flags);
		return -ENOMEM;
	}

	export->pwm = pwm;

	export->child.release = pwm_export_release;
	export->child.parent = parent;
	export->child.devt = MKDEV(0, 0);
	export->child.groups = pwm_groups;
	dev_set_name(&export->child, "pwm%u", pwm->hwpwm);

	ret = device_register(&export->child);
	if (ret) {
		clear_bit(PWMF_EXPORTED, &pwm->flags);
		kfree(export);
		return ret;
	}

	return 0;
}

而修改pwm的属性文件的函数:

static ssize_t pwm_polarity_store(struct device *child,
				  struct device_attribute *attr,
				  const char *buf, size_t size)
{
	struct pwm_device *pwm = child_to_pwm_device(child);//可以看出他们都是在child的节点下创建的属性文件
	enum pwm_polarity polarity;
	int ret;

	if (sysfs_streq(buf, "normal"))
		polarity = PWM_POLARITY_NORMAL;
	else if (sysfs_streq(buf, "inversed"))
		polarity = PWM_POLARITY_INVERSED;
	else
		return -EINVAL;

	ret = pwm_set_polarity(pwm, polarity);

	return ret ? : size;
}

所以我们必须先在pwmchipx下创建一共pwm child,利用echo就行,因为会执行pwm_export_store从而执行到pwm_export_child,最终生成pwmx而在这个文件夹下就有若干个控制pwm的属性文件。(注意echo num 就会生成pwmnum)

例子:

echo 0 > /sys/class/pwm/pwmchip2/export 首先生成pwm0

在这里插入图片描述

然后使能:

echo 1 > /sys/class/pwm/pwmchip2/pwm0/enable

设置周期:单位ns

echo 50000 > /sys/class/pwm/pwmchip2/pwm0/period

设置占空比:(on的时间)

echo 10000 > /sys/class/pwm/pwmchip2/pwm0/duty_cycle


总结:

本文说的pwm驱动框架是针对单纯的pwm作为gpio的输出,而如果是一些外设上的pwm配置,它所用的框架则是不同的,如lcd上的pwm背光。

在imx中它的驱动代码在:drivers/video/backlight/pwm_bl.c。逻辑不同于\drivers\pwm\pwm-imx.c。

同时设备树的参考也不同:

它在

Documentation/devicetree/bindings/video/backlight/pwm-backlight.txt

wmchip2/pwm0/enable

设置周期:单位ns

echo 50000 > /sys/class/pwm/pwmchip2/pwm0/period

设置占空比:(on的时间)

echo 10000 > /sys/class/pwm/pwmchip2/pwm0/duty_cycle


总结:

本文说的pwm驱动框架是针对单纯的pwm作为gpio的输出,而如果是一些外设上的pwm配置,它所用的框架则是不同的,如lcd上的pwm背光。

在imx中它的驱动代码在:drivers/video/backlight/pwm_bl.c。逻辑不同于\drivers\pwm\pwm-imx.c。

同时设备树的参考也不同:

它在

Documentation/devicetree/bindings/video/backlight/pwm-backlight.txt

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值