Commit f051c466 authored by Thierry Reding's avatar Thierry Reding

pwm: Allow chips to support multiple PWMs

Many PWM controllers provide access to more than a single PWM output and
may even share some resource among them. Allowing a PWM chip to provide
multiple PWM devices enables better sharing of those resources. As a
side-effect this change allows easy integration with the device tree
where a given PWM can be looked up based on the PWM chip's phandle and a
corresponding index.

This commit modifies the PWM core to support multiple PWMs per struct
pwm_chip. It achieves this in a similar way to how gpiolib works, by
allowing PWM ranges to be requested dynamically (pwm_chip.base == -1) or
starting at a given offset (pwm_chip.base >= 0). A chip specifies how
many PWMs it controls using the npwm member. Each of the functions in
the pwm_ops structure gets an additional argument that specified the PWM
number (it can be converted to a per-chip index by subtracting the
chip's base).

The total maximum number of PWM devices is currently fixed to 1024 while
the data is actually stored in a radix tree, thus saving resources if
not all of them are used.
Reviewed-by: default avatarMark Brown <broonie@opensource.wolfsonmicro.com>
Reviewed-by: default avatarShawn Guo <shawn.guo@linaro.org>
[eric@eukrea.com: fix error handling in pwmchip_add]
Signed-off-by: default avatarEric Bénard <eric@eukrea.com>
Signed-off-by: default avatarThierry Reding <thierry.reding@avionic-design.de>
parent 0c2498f1
...@@ -33,9 +33,12 @@ there only has been the barebone API meaning that each driver has ...@@ -33,9 +33,12 @@ there only has been the barebone API meaning that each driver has
to implement the pwm_*() functions itself. This means that it's impossible to implement the pwm_*() functions itself. This means that it's impossible
to have multiple PWM drivers in the system. For this reason it's mandatory to have multiple PWM drivers in the system. For this reason it's mandatory
for new drivers to use the generic PWM framework. for new drivers to use the generic PWM framework.
A new PWM device can be added using pwmchip_add() and removed again with
pwmchip_remove(). pwmchip_add() takes a filled in struct pwm_chip as A new PWM controller/chip can be added using pwmchip_add() and removed
argument which provides the ops and the pwm id to the framework. again with pwmchip_remove(). pwmchip_add() takes a filled in struct
pwm_chip as argument which provides a description of the PWM chip, the
number of PWM devices provider by the chip and the chip-specific
implementation of the supported PWM operations to the framework.
Locking Locking
------- -------
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
* Generic pwmlib implementation * Generic pwmlib implementation
* *
* Copyright (C) 2011 Sascha Hauer <s.hauer@pengutronix.de> * Copyright (C) 2011 Sascha Hauer <s.hauer@pengutronix.de>
* Copyright (C) 2011-2012 Avionic Design GmbH
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
...@@ -20,66 +21,161 @@ ...@@ -20,66 +21,161 @@
#include <linux/module.h> #include <linux/module.h>
#include <linux/pwm.h> #include <linux/pwm.h>
#include <linux/radix-tree.h>
#include <linux/list.h> #include <linux/list.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/device.h> #include <linux/device.h>
struct pwm_device { #define MAX_PWMS 1024
struct pwm_chip *chip;
const char *label;
unsigned long flags;
#define FLAG_REQUESTED 0
#define FLAG_ENABLED 1
struct list_head node;
};
static LIST_HEAD(pwm_list);
static DEFINE_MUTEX(pwm_lock); static DEFINE_MUTEX(pwm_lock);
static LIST_HEAD(pwm_chips);
static DECLARE_BITMAP(allocated_pwms, MAX_PWMS);
static RADIX_TREE(pwm_tree, GFP_KERNEL);
static struct pwm_device *_find_pwm(int pwm_id) static struct pwm_device *pwm_to_device(unsigned int pwm)
{ {
struct pwm_device *pwm; return radix_tree_lookup(&pwm_tree, pwm);
}
list_for_each_entry(pwm, &pwm_list, node) { static int alloc_pwms(int pwm, unsigned int count)
if (pwm->chip->pwm_id == pwm_id) {
return pwm; unsigned int from = 0;
unsigned int start;
if (pwm >= MAX_PWMS)
return -EINVAL;
if (pwm >= 0)
from = pwm;
start = bitmap_find_next_zero_area(allocated_pwms, MAX_PWMS, from,
count, 0);
if (pwm >= 0 && start != pwm)
return -EEXIST;
if (start + count > MAX_PWMS)
return -ENOSPC;
return start;
}
static void free_pwms(struct pwm_chip *chip)
{
unsigned int i;
for (i = 0; i < chip->npwm; i++) {
struct pwm_device *pwm = &chip->pwms[i];
radix_tree_delete(&pwm_tree, pwm->pwm);
} }
return NULL; bitmap_clear(allocated_pwms, chip->base, chip->npwm);
kfree(chip->pwms);
chip->pwms = NULL;
}
static int pwm_device_request(struct pwm_device *pwm, const char *label)
{
int err;
if (test_bit(PWMF_REQUESTED, &pwm->flags))
return -EBUSY;
if (!try_module_get(pwm->chip->ops->owner))
return -ENODEV;
if (pwm->chip->ops->request) {
err = pwm->chip->ops->request(pwm->chip, pwm);
if (err) {
module_put(pwm->chip->ops->owner);
return err;
}
}
set_bit(PWMF_REQUESTED, &pwm->flags);
pwm->label = label;
return 0;
}
/**
* pwm_set_chip_data() - set private chip data for a PWM
* @pwm: PWM device
* @data: pointer to chip-specific data
*/
int pwm_set_chip_data(struct pwm_device *pwm, void *data)
{
if (!pwm)
return -EINVAL;
pwm->chip_data = data;
return 0;
}
/**
* pwm_get_chip_data() - get private chip data for a PWM
* @pwm: PWM device
*/
void *pwm_get_chip_data(struct pwm_device *pwm)
{
return pwm ? pwm->chip_data : NULL;
} }
/** /**
* pwmchip_add() - register a new PWM chip * pwmchip_add() - register a new PWM chip
* @chip: the PWM chip to add * @chip: the PWM chip to add
*
* Register a new PWM chip. If chip->base < 0 then a dynamically assigned base
* will be used.
*/ */
int pwmchip_add(struct pwm_chip *chip) int pwmchip_add(struct pwm_chip *chip)
{ {
struct pwm_device *pwm; struct pwm_device *pwm;
int ret = 0; unsigned int i;
int ret;
pwm = kzalloc(sizeof(*pwm), GFP_KERNEL);
if (!pwm)
return -ENOMEM;
pwm->chip = chip; if (!chip || !chip->dev || !chip->ops || !chip->ops->config ||
!chip->ops->enable || !chip->ops->disable)
return -EINVAL;
mutex_lock(&pwm_lock); mutex_lock(&pwm_lock);
if (chip->pwm_id >= 0 && _find_pwm(chip->pwm_id)) { ret = alloc_pwms(chip->base, chip->npwm);
ret = -EBUSY; if (ret < 0)
goto out;
chip->pwms = kzalloc(chip->npwm * sizeof(*pwm), GFP_KERNEL);
if (!chip->pwms) {
ret = -ENOMEM;
goto out; goto out;
} }
list_add_tail(&pwm->node, &pwm_list); chip->base = ret;
out:
mutex_unlock(&pwm_lock); 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);
if (ret) ret = 0;
kfree(pwm);
out:
mutex_unlock(&pwm_lock);
return ret; return ret;
} }
EXPORT_SYMBOL_GPL(pwmchip_add); EXPORT_SYMBOL_GPL(pwmchip_add);
...@@ -93,28 +189,25 @@ EXPORT_SYMBOL_GPL(pwmchip_add); ...@@ -93,28 +189,25 @@ EXPORT_SYMBOL_GPL(pwmchip_add);
*/ */
int pwmchip_remove(struct pwm_chip *chip) int pwmchip_remove(struct pwm_chip *chip)
{ {
struct pwm_device *pwm; unsigned int i;
int ret = 0; int ret = 0;
mutex_lock(&pwm_lock); mutex_lock(&pwm_lock);
pwm = _find_pwm(chip->pwm_id); for (i = 0; i < chip->npwm; i++) {
if (!pwm) { struct pwm_device *pwm = &chip->pwms[i];
ret = -ENOENT;
goto out;
}
if (test_bit(FLAG_REQUESTED, &pwm->flags)) { if (test_bit(PWMF_REQUESTED, &pwm->flags)) {
ret = -EBUSY; ret = -EBUSY;
goto out; goto out;
} }
}
list_del(&pwm->node); list_del_init(&chip->list);
free_pwms(chip);
kfree(pwm);
out: out:
mutex_unlock(&pwm_lock); mutex_unlock(&pwm_lock);
return ret; return ret;
} }
EXPORT_SYMBOL_GPL(pwmchip_remove); EXPORT_SYMBOL_GPL(pwmchip_remove);
...@@ -124,50 +217,64 @@ EXPORT_SYMBOL_GPL(pwmchip_remove); ...@@ -124,50 +217,64 @@ EXPORT_SYMBOL_GPL(pwmchip_remove);
* @pwm_id: global PWM device index * @pwm_id: global PWM device index
* @label: PWM device label * @label: PWM device label
*/ */
struct pwm_device *pwm_request(int pwm_id, const char *label) struct pwm_device *pwm_request(int pwm, const char *label)
{ {
struct pwm_device *pwm; struct pwm_device *dev;
int ret; int err;
if (pwm < 0 || pwm >= MAX_PWMS)
return ERR_PTR(-EINVAL);
mutex_lock(&pwm_lock); mutex_lock(&pwm_lock);
pwm = _find_pwm(pwm_id); dev = pwm_to_device(pwm);
if (!pwm) { if (!dev) {
pwm = ERR_PTR(-ENOENT); dev = ERR_PTR(-EPROBE_DEFER);
goto out; goto out;
} }
if (test_bit(FLAG_REQUESTED, &pwm->flags)) { err = pwm_device_request(dev, label);
pwm = ERR_PTR(-EBUSY); if (err < 0)
goto out; dev = ERR_PTR(err);
}
if (!try_module_get(pwm->chip->ops->owner)) { out:
pwm = ERR_PTR(-ENODEV); mutex_unlock(&pwm_lock);
goto out;
}
if (pwm->chip->ops->request) { return dev;
ret = pwm->chip->ops->request(pwm->chip); }
if (ret) { EXPORT_SYMBOL_GPL(pwm_request);
pwm = ERR_PTR(ret);
goto out_put;
}
}
pwm->label = label; /**
set_bit(FLAG_REQUESTED, &pwm->flags); * pwm_request_from_chip() - request a PWM device relative to a PWM chip
* @chip: PWM chip
* @index: per-chip index of the PWM to request
* @label: a literal description string of this PWM
*
* Returns the PWM at the given index of the given PWM chip. A negative error
* code is returned if the index is not valid for the specified PWM chip or
* if the PWM device cannot be requested.
*/
struct pwm_device *pwm_request_from_chip(struct pwm_chip *chip,
unsigned int index,
const char *label)
{
struct pwm_device *pwm;
int err;
goto out; if (!chip || index >= chip->npwm)
return ERR_PTR(-EINVAL);
out_put: mutex_lock(&pwm_lock);
module_put(pwm->chip->ops->owner); pwm = &chip->pwms[index];
out:
mutex_unlock(&pwm_lock);
err = pwm_device_request(pwm, label);
if (err < 0)
pwm = ERR_PTR(err);
mutex_unlock(&pwm_lock);
return pwm; return pwm;
} }
EXPORT_SYMBOL_GPL(pwm_request); EXPORT_SYMBOL_GPL(pwm_request_from_chip);
/** /**
* pwm_free() - free a PWM device * pwm_free() - free a PWM device
...@@ -177,11 +284,14 @@ void pwm_free(struct pwm_device *pwm) ...@@ -177,11 +284,14 @@ void pwm_free(struct pwm_device *pwm)
{ {
mutex_lock(&pwm_lock); mutex_lock(&pwm_lock);
if (!test_and_clear_bit(FLAG_REQUESTED, &pwm->flags)) { if (!test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) {
pr_warning("PWM device already freed\n"); pr_warning("PWM device already freed\n");
goto out; goto out;
} }
if (pwm->chip->ops->free)
pwm->chip->ops->free(pwm->chip, pwm);
pwm->label = NULL; pwm->label = NULL;
module_put(pwm->chip->ops->owner); module_put(pwm->chip->ops->owner);
...@@ -198,7 +308,10 @@ EXPORT_SYMBOL_GPL(pwm_free); ...@@ -198,7 +308,10 @@ EXPORT_SYMBOL_GPL(pwm_free);
*/ */
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
{ {
return pwm->chip->ops->config(pwm->chip, duty_ns, period_ns); if (!pwm || period_ns == 0 || duty_ns > period_ns)
return -EINVAL;
return pwm->chip->ops->config(pwm->chip, pwm, duty_ns, period_ns);
} }
EXPORT_SYMBOL_GPL(pwm_config); EXPORT_SYMBOL_GPL(pwm_config);
...@@ -208,10 +321,10 @@ EXPORT_SYMBOL_GPL(pwm_config); ...@@ -208,10 +321,10 @@ EXPORT_SYMBOL_GPL(pwm_config);
*/ */
int pwm_enable(struct pwm_device *pwm) int pwm_enable(struct pwm_device *pwm)
{ {
if (!test_and_set_bit(FLAG_ENABLED, &pwm->flags)) if (pwm && !test_and_set_bit(PWMF_ENABLED, &pwm->flags))
return pwm->chip->ops->enable(pwm->chip); return pwm->chip->ops->enable(pwm->chip, pwm);
return 0; return pwm ? 0 : -EINVAL;
} }
EXPORT_SYMBOL_GPL(pwm_enable); EXPORT_SYMBOL_GPL(pwm_enable);
...@@ -221,7 +334,7 @@ EXPORT_SYMBOL_GPL(pwm_enable); ...@@ -221,7 +334,7 @@ EXPORT_SYMBOL_GPL(pwm_enable);
*/ */
void pwm_disable(struct pwm_device *pwm) void pwm_disable(struct pwm_device *pwm)
{ {
if (test_and_clear_bit(FLAG_ENABLED, &pwm->flags)) if (pwm && test_and_clear_bit(PWMF_ENABLED, &pwm->flags))
pwm->chip->ops->disable(pwm->chip); pwm->chip->ops->disable(pwm->chip, pwm);
} }
EXPORT_SYMBOL_GPL(pwm_disable); EXPORT_SYMBOL_GPL(pwm_disable);
...@@ -31,6 +31,33 @@ void pwm_disable(struct pwm_device *pwm); ...@@ -31,6 +31,33 @@ void pwm_disable(struct pwm_device *pwm);
#ifdef CONFIG_PWM #ifdef CONFIG_PWM
struct pwm_chip; struct pwm_chip;
enum {
PWMF_REQUESTED = 1 << 0,
PWMF_ENABLED = 1 << 1,
};
struct pwm_device {
const char *label;
unsigned long flags;
unsigned int hwpwm;
unsigned int pwm;
struct pwm_chip *chip;
void *chip_data;
unsigned int period; /* in nanoseconds */
};
static inline void pwm_set_period(struct pwm_device *pwm, unsigned int period)
{
if (pwm)
pwm->period = period;
}
static inline unsigned int pwm_get_period(struct pwm_device *pwm)
{
return pwm ? pwm->period : 0;
}
/** /**
* struct pwm_ops - PWM controller operations * struct pwm_ops - PWM controller operations
* @request: optional hook for requesting a PWM * @request: optional hook for requesting a PWM
...@@ -41,29 +68,47 @@ struct pwm_chip; ...@@ -41,29 +68,47 @@ struct pwm_chip;
* @owner: helps prevent removal of modules exporting active PWMs * @owner: helps prevent removal of modules exporting active PWMs
*/ */
struct pwm_ops { struct pwm_ops {
int (*request)(struct pwm_chip *chip); int (*request)(struct pwm_chip *chip,
void (*free)(struct pwm_chip *chip); struct pwm_device *pwm);
int (*config)(struct pwm_chip *chip, int duty_ns, void (*free)(struct pwm_chip *chip,
int period_ns); struct pwm_device *pwm);
int (*enable)(struct pwm_chip *chip); int (*config)(struct pwm_chip *chip,
void (*disable)(struct pwm_chip *chip); struct pwm_device *pwm,
int duty_ns, int period_ns);
int (*enable)(struct pwm_chip *chip,
struct pwm_device *pwm);
void (*disable)(struct pwm_chip *chip,
struct pwm_device *pwm);
struct module *owner; struct module *owner;
}; };
/** /**
* struct pwm_chip - abstract a PWM * struct pwm_chip - abstract a PWM controller
* @pwm_id: global PWM device index * @dev: device providing the PWMs
* @label: PWM device label * @list: list node for internal use
* @ops: controller operations * @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
*/ */
struct pwm_chip { struct pwm_chip {
int pwm_id; struct device *dev;
const char *label; struct list_head list;
struct pwm_ops *ops; const struct pwm_ops *ops;
int base;
unsigned int npwm;
struct pwm_device *pwms;
}; };
int pwm_set_chip_data(struct pwm_device *pwm, void *data);
void *pwm_get_chip_data(struct pwm_device *pwm);
int pwmchip_add(struct pwm_chip *chip); int pwmchip_add(struct pwm_chip *chip);
int pwmchip_remove(struct pwm_chip *chip); int pwmchip_remove(struct pwm_chip *chip);
struct pwm_device *pwm_request_from_chip(struct pwm_chip *chip,
unsigned int index,
const char *label);
#endif #endif
#endif /* __LINUX_PWM_H */ #endif /* __LINUX_PWM_H */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment