Linux内核提供看PWM的驱动框架,让驱动开发人员可以更好地进行PWM驱动的编写。
- 1、PWM结构体
struct pwm_chip {
struct device *dev;
struct list_head list; // 链表
const struct pwm_ops *ops; // PWM的操作函数
int base; // 索引号
unsigned int npwm; // 一个PWM控制器下有几路PWM
struct pwm_device *pwms; // PWM设备
struct pwm_device * (*of_xlate)(struct pwm_chip *pc,
const struct of_phandle_args *args);
unsigned int of_pwm_n_cells; // 参数
bool can_sleep; // 是否可以睡眠
};
Linux内核把一个PWM控制器的一些基本属性都抽象出来,组成了pwm_chip 结构体,下面就来简单看一下每个属性的一些作用。
dev: PWM设备
list: PWM控制器链表
ops : PWM操作函数
base:PWM控制器的下标索引,通常设置为-1。
npwm : 一个PWM控制器下有多少路PWM
pwms : 指向具体的PWM设备
其他的属性可以不必关注。
- 2、PWM操作函数
接下来看一下重点的PWM操作函数
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;
};
request : 申请PWM设备
free : 释放PWM设备
config : PWM设备配置函数
set_polarity : 设置PWM输出极性
enable : PWM开启
disable : PWM关闭
dbg_show : 开启DEBUG_FS的时候用到。
其中我们重点需要实现config、enable 和disable 函数,其他函数可以不需要实现。
- 3、注册PWM设备
pwmchip_add函数的具体作用是用来注册PWM设备
int pwmchip_add(struct pwm_chip *chip)
{
struct pwm_device *pwm;
unsigned int i;
int ret;
// 一定需要的参数
if (!chip || !chip->dev || !chip->ops || !chip->ops->config ||
!chip->ops->enable || !chip->ops->disable || !chip->npwm)
return -EINVAL;
mutex_lock(&pwm_lock);
ret = alloc_pwms(chip->base, chip->npwm); // 分配内存空间
if (ret < 0)
goto out;
chip->pwms = kzalloc(chip->npwm * sizeof(*pwm), GFP_KERNEL);
if (!chip->pwms) {
ret = -ENOMEM;
goto out;
}
chip->base = ret;
for (i = 0; i < chip->npwm; i++) {
pwm = &chip->pwms[i];
pwm->chip = chip;
pwm->pwm = chip->base + i;
pwm->hwpwm = i;
radix_tree_insert(&pwm_tree, pwm->pwm, pwm);
}
bitmap_set(allocated_pwms, chip->base, chip->npwm);
INIT_LIST_HEAD(&chip->list);
list_add(&chip->list, &pwm_chips);
ret = 0;
if (IS_ENABLED(CONFIG_OF))
of_pwmchip_add(chip);
pwmchip_sysfs_export(chip);
out:
mutex_unlock(&pwm_lock);
return ret;
}
- 4、具体例子
接下来以pwm-rockchip.c为例子简单讲一下PWM框架的具体使用。
static int rockchip_pwm_probe(struct platform_device *pdev)
{
const struct of_device_id *id;
struct rockchip_pwm_chip *pc;
struct resource *r;
int ret;
id = of_match_device(rockchip_pwm_dt_ids, &pdev->dev);
if (!id)
return -EINVAL;
pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
if (!pc)
return -ENOMEM;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
pc->base = devm_ioremap_resource(&pdev->dev, r);
if (IS_ERR(pc->base))
return PTR_ERR(pc->base);
pc->clk = devm_clk_get(&pdev->dev, NULL); // 获取PWM时钟
if (IS_ERR(pc->clk))
return PTR_ERR(pc->clk);
ret = clk_prepare(pc->clk);
if (ret)
return ret;
platform_set_drvdata(pdev, pc);
pc->data = id->data;
pc->chip.dev = &pdev->dev;
pc->chip.ops = pc->data->ops; // PWM操作函数
pc->chip.base = -1; // PWM控制器下标
pc->chip.npwm = 1; // PWM通道
if (pc->data->ops->set_polarity) {
pc->chip.of_xlate = of_pwm_xlate_with_flags;
pc->chip.of_pwm_n_cells = 3;
}
ret = pwmchip_add(&pc->chip); // 注册PWM
if (ret < 0) {
clk_unprepare(pc->clk);
dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
}
return ret;
}
上面的PWM控制器注册函数十分简单,主要是先获取PWM控制器的时钟,然后初始化pwm_chip结构体,包括PWM操作函数,PWM下标索引和PWM通道数量,最后调用pwmchip_add把PWM控制器注册进内核。接下来看一下具体的PWM操作函数。
- 4.1、config配置函数
static int rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
unsigned long period, duty;
u64 clk_rate, div;
int ret;
clk_rate = clk_get_rate(pc->clk);
/*
* Since period and duty cycle registers have a width of 32
* bits, every possible input period can be obtained using the
* default prescaler value for all practical clock rate values.
*/
div = clk_rate * period_ns;
do_div(div, pc->data->prescaler * NSEC_PER_SEC);
period = div;
div = clk_rate * duty_ns;
do_div(div, pc->data->prescaler * NSEC_PER_SEC);
duty = div;
ret = clk_enable(pc->clk);
if (ret)
return ret;
writel(period, pc->base + pc->data->regs.period);
writel(duty, pc->base + pc->data->regs.duty);
writel(0, pc->base + pc->data->regs.cntr);
clk_disable(pc->clk);
return 0;
}
config配置函数有3个参数
chip : PWM控制器
pwm :PWM设备
duty_ns : 高电平的周期
period_ns : PWM周期
PWM的配置函数主要的作用就是将PWM的周期和PWM的高电平时间写入寄存器。
4.2、PWM开启和关闭函数
static int rockchip_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
int ret;
ret = clk_enable(pc->clk);
if (ret)
return ret;
pc->data->set_enable(chip, pwm, true);
return 0;
}
static void rockchip_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
pc->data->set_enable(chip, pwm, false);
clk_disable(pc->clk);
}
开启和关闭函数的实现就很简单了,就是让寄存器里面写1或0开启或关闭PWM。