Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f051c466 authored by Thierry Reding's avatar Thierry Reding
Browse files

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
Loading
Loading
Loading
Loading
+6 −3
Original line number Diff line number Diff line
@@ -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 have multiple PWM drivers in the system. For this reason it's mandatory
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
argument which provides the ops and the pwm id to the framework.

A new PWM controller/chip can be added using pwmchip_add() and removed
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
-------
+190 −77
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@
 * Generic pwmlib implementation
 *
 * 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
 *  it under the terms of the GNU General Public License as published by
@@ -20,66 +21,161 @@

#include <linux/module.h>
#include <linux/pwm.h>
#include <linux/radix-tree.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/device.h>

struct pwm_device {
	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);
#define MAX_PWMS 1024

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) {
		if (pwm->chip->pwm_id == pwm_id)
			return pwm;
static int alloc_pwms(int pwm, unsigned int count)
{
	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);
	}

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

	return NULL;
	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
 * @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)
{
	struct pwm_device *pwm;
	int ret = 0;

	pwm = kzalloc(sizeof(*pwm), GFP_KERNEL);
	if (!pwm)
		return -ENOMEM;
	unsigned int i;
	int ret;

	pwm->chip = chip;
	if (!chip || !chip->dev || !chip->ops || !chip->ops->config ||
	    !chip->ops->enable || !chip->ops->disable)
		return -EINVAL;

	mutex_lock(&pwm_lock);

	if (chip->pwm_id >= 0 && _find_pwm(chip->pwm_id)) {
		ret = -EBUSY;
	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;
	}

	list_add_tail(&pwm->node, &pwm_list);
out:
	mutex_unlock(&pwm_lock);
	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);

	if (ret)
		kfree(pwm);
	INIT_LIST_HEAD(&chip->list);
	list_add(&chip->list, &pwm_chips);

	ret = 0;

out:
	mutex_unlock(&pwm_lock);
	return ret;
}
EXPORT_SYMBOL_GPL(pwmchip_add);
@@ -93,28 +189,25 @@ EXPORT_SYMBOL_GPL(pwmchip_add);
 */
int pwmchip_remove(struct pwm_chip *chip)
{
	struct pwm_device *pwm;
	unsigned int i;
	int ret = 0;

	mutex_lock(&pwm_lock);

	pwm = _find_pwm(chip->pwm_id);
	if (!pwm) {
		ret = -ENOENT;
		goto out;
	}
	for (i = 0; i < chip->npwm; i++) {
		struct pwm_device *pwm = &chip->pwms[i];

	if (test_bit(FLAG_REQUESTED, &pwm->flags)) {
		if (test_bit(PWMF_REQUESTED, &pwm->flags)) {
			ret = -EBUSY;
			goto out;
		}
	}

	list_del(&pwm->node);
	list_del_init(&chip->list);
	free_pwms(chip);

	kfree(pwm);
out:
	mutex_unlock(&pwm_lock);

	return ret;
}
EXPORT_SYMBOL_GPL(pwmchip_remove);
@@ -124,50 +217,64 @@ EXPORT_SYMBOL_GPL(pwmchip_remove);
 * @pwm_id: global PWM device index
 * @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;
	int ret;
	struct pwm_device *dev;
	int err;

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

	mutex_lock(&pwm_lock);

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

	if (test_bit(FLAG_REQUESTED, &pwm->flags)) {
		pwm = ERR_PTR(-EBUSY);
		goto out;
	}
	err = pwm_device_request(dev, label);
	if (err < 0)
		dev = ERR_PTR(err);

	if (!try_module_get(pwm->chip->ops->owner)) {
		pwm = ERR_PTR(-ENODEV);
		goto out;
	}
out:
	mutex_unlock(&pwm_lock);

	if (pwm->chip->ops->request) {
		ret = pwm->chip->ops->request(pwm->chip);
		if (ret) {
			pwm = ERR_PTR(ret);
			goto out_put;
		}
	return dev;
}
EXPORT_SYMBOL_GPL(pwm_request);

	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:
	module_put(pwm->chip->ops->owner);
out:
	mutex_unlock(&pwm_lock);
	mutex_lock(&pwm_lock);
	pwm = &chip->pwms[index];

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

	mutex_unlock(&pwm_lock);
	return pwm;
}
EXPORT_SYMBOL_GPL(pwm_request);
EXPORT_SYMBOL_GPL(pwm_request_from_chip);

/**
 * pwm_free() - free a PWM device
@@ -177,11 +284,14 @@ void pwm_free(struct pwm_device *pwm)
{
	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");
		goto out;
	}

	if (pwm->chip->ops->free)
		pwm->chip->ops->free(pwm->chip, pwm);

	pwm->label = NULL;

	module_put(pwm->chip->ops->owner);
@@ -198,7 +308,10 @@ EXPORT_SYMBOL_GPL(pwm_free);
 */
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);

@@ -208,10 +321,10 @@ EXPORT_SYMBOL_GPL(pwm_config);
 */
int pwm_enable(struct pwm_device *pwm)
{
	if (!test_and_set_bit(FLAG_ENABLED, &pwm->flags))
		return pwm->chip->ops->enable(pwm->chip);
	if (pwm && !test_and_set_bit(PWMF_ENABLED, &pwm->flags))
		return pwm->chip->ops->enable(pwm->chip, pwm);

	return 0;
	return pwm ? 0 : -EINVAL;
}
EXPORT_SYMBOL_GPL(pwm_enable);

@@ -221,7 +334,7 @@ EXPORT_SYMBOL_GPL(pwm_enable);
 */
void pwm_disable(struct pwm_device *pwm)
{
	if (test_and_clear_bit(FLAG_ENABLED, &pwm->flags))
		pwm->chip->ops->disable(pwm->chip);
	if (pwm && test_and_clear_bit(PWMF_ENABLED, &pwm->flags))
		pwm->chip->ops->disable(pwm->chip, pwm);
}
EXPORT_SYMBOL_GPL(pwm_disable);
+58 −13
Original line number Diff line number Diff line
@@ -31,6 +31,33 @@ void pwm_disable(struct pwm_device *pwm);
#ifdef CONFIG_PWM
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
 * @request: optional hook for requesting a PWM
@@ -41,29 +68,47 @@ struct pwm_chip;
 * @owner: helps prevent removal of modules exporting active PWMs
 */
struct pwm_ops {
	int			(*request)(struct pwm_chip *chip);
	void			(*free)(struct pwm_chip *chip);
	int			(*config)(struct pwm_chip *chip, int duty_ns,
						int period_ns);
	int			(*enable)(struct pwm_chip *chip);
	void			(*disable)(struct pwm_chip *chip);
	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			(*enable)(struct pwm_chip *chip,
					  struct pwm_device *pwm);
	void			(*disable)(struct pwm_chip *chip,
					   struct pwm_device *pwm);
	struct module		*owner;
};

/**
 * struct pwm_chip - abstract a PWM
 * @pwm_id: global PWM device index
 * @label: PWM device label
 * @ops: controller operations
 * 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
 */
struct pwm_chip {
	int			pwm_id;
	const char		*label;
	struct pwm_ops		*ops;
	struct device		*dev;
	struct list_head	list;
	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_remove(struct pwm_chip *chip);
struct pwm_device *pwm_request_from_chip(struct pwm_chip *chip,
					 unsigned int index,
					 const char *label);
#endif

#endif /* __LINUX_PWM_H */