框架分析:
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