From 0c2498f1660878339350bea8d18550b1b87ca055 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 28 Jan 2011 09:40:40 +0100 Subject: pwm: Add PWM framework support This patch adds framework support for PWM (pulse width modulation) devices. The is a barebone PWM API already in the kernel under include/linux/pwm.h, but it does not allow for multiple drivers as each of them implements the pwm_*() functions. There are other PWM framework patches around from Bill Gatliff. Unlike his framework this one does not change the existing API for PWMs so that this framework can act as a drop in replacement for the existing API. Why another framework? Several people argue that there should not be another framework for PWMs but they should be integrated into one of the existing frameworks like led or hwmon. Unlike these frameworks the PWM framework is agnostic to the purpose of the PWM. In fact, a PWM can drive a LED, but this makes the LED framework a user of a PWM, like already done in leds-pwm.c. The gpio framework also is not suitable for PWMs. Every gpio could be turned into a PWM using timer based toggling, but on the other hand not every PWM hardware device can be turned into a gpio due to the lack of hardware capabilities. This patch does not try to improve the PWM API yet, this could be done in subsequent patches. Signed-off-by: Sascha Hauer Acked-by: Kurt Van Dijck Reviewed-by: Arnd Bergmann Reviewed-by: Matthias Kaehlcke Reviewed-by: Mark Brown Reviewed-by: Shawn Guo [thierry.reding@avionic-design.de: fixup typos, kerneldoc comments] Signed-off-by: Thierry Reding --- Documentation/pwm.txt | 54 ++++++++++++ MAINTAINERS | 6 ++ drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/pwm/Kconfig | 12 +++ drivers/pwm/Makefile | 1 + drivers/pwm/core.c | 227 ++++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/pwm.h | 38 +++++++++ 8 files changed, 341 insertions(+) create mode 100644 Documentation/pwm.txt create mode 100644 drivers/pwm/Kconfig create mode 100644 drivers/pwm/Makefile create mode 100644 drivers/pwm/core.c diff --git a/Documentation/pwm.txt b/Documentation/pwm.txt new file mode 100644 index 000000000000..03e39d145911 --- /dev/null +++ b/Documentation/pwm.txt @@ -0,0 +1,54 @@ +Pulse Width Modulation (PWM) interface + +This provides an overview about the Linux PWM interface + +PWMs are commonly used for controlling LEDs, fans or vibrators in +cell phones. PWMs with a fixed purpose have no need implementing +the Linux PWM API (although they could). However, PWMs are often +found as discrete devices on SoCs which have no fixed purpose. It's +up to the board designer to connect them to LEDs or fans. To provide +this kind of flexibility the generic PWM API exists. + +Identifying PWMs +---------------- + +Users of the legacy PWM API use unique IDs to refer to PWM devices. One +goal of the new PWM framework is to get rid of this global namespace. + +Using PWMs +---------- + +A PWM can be requested using pwm_request() and freed after usage with +pwm_free(). After being requested a PWM has to be configured using + +int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns); + +To start/stop toggling the PWM output use pwm_enable()/pwm_disable(). + +Implementing a PWM driver +------------------------- + +Currently there are two ways to implement pwm drivers. Traditionally +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. + +Locking +------- + +The PWM core list manipulations are protected by a mutex, so pwm_request() +and pwm_free() may not be called from an atomic context. Currently the +PWM core does not enforce any locking to pwm_enable(), pwm_disable() and +pwm_config(), so the calling context is currently driver specific. This +is an issue derived from the former barebone API and should be fixed soon. + +Helpers +------- + +Currently a PWM can only be configured with period_ns and duty_ns. For several +use cases freq_hz and duty_percent might be better. Instead of calculating +this in your driver please consider adding appropriate helpers to the framework. diff --git a/MAINTAINERS b/MAINTAINERS index 14bc7071f9df..67d6cb70cb7e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5487,6 +5487,12 @@ S: Maintained F: Documentation/video4linux/README.pvrusb2 F: drivers/media/video/pvrusb2/ +PWM core +M: Sascha Hauer +L: linux-kernel@vger.kernel.org +S: Maintained +F: drivers/pwm/ + PXA2xx/PXA3xx SUPPORT M: Eric Miao M: Russell King diff --git a/drivers/Kconfig b/drivers/Kconfig index bfc918633fd9..805c432c9439 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -148,4 +148,6 @@ source "drivers/iio/Kconfig" source "drivers/vme/Kconfig" +source "drivers/pwm/Kconfig" + endmenu diff --git a/drivers/Makefile b/drivers/Makefile index 2ba29ffef2cb..bd36f09f2246 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -8,6 +8,7 @@ # GPIO must come after pinctrl as gpios may need to mux pins etc obj-y += pinctrl/ obj-y += gpio/ +obj-y += pwm/ obj-$(CONFIG_PCI) += pci/ obj-$(CONFIG_PARISC) += parisc/ obj-$(CONFIG_RAPIDIO) += rapidio/ diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig new file mode 100644 index 000000000000..93c1052291a3 --- /dev/null +++ b/drivers/pwm/Kconfig @@ -0,0 +1,12 @@ +menuconfig PWM + bool "PWM Support" + help + This enables PWM support through the generic PWM framework. + You only need to enable this, if you also want to enable + one or more of the PWM drivers below. + + If unsure, say N. + +if PWM + +endif diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile new file mode 100644 index 000000000000..3469c3d28b7a --- /dev/null +++ b/drivers/pwm/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_PWM) += core.o diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c new file mode 100644 index 000000000000..0b8a38eca23c --- /dev/null +++ b/drivers/pwm/core.c @@ -0,0 +1,227 @@ +/* + * Generic pwmlib implementation + * + * Copyright (C) 2011 Sascha Hauer + * + * 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 + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +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); + +static DEFINE_MUTEX(pwm_lock); + +static struct pwm_device *_find_pwm(int pwm_id) +{ + struct pwm_device *pwm; + + list_for_each_entry(pwm, &pwm_list, node) { + if (pwm->chip->pwm_id == pwm_id) + return pwm; + } + + return NULL; +} + +/** + * pwmchip_add() - register a new PWM chip + * @chip: the PWM chip to add + */ +int pwmchip_add(struct pwm_chip *chip) +{ + struct pwm_device *pwm; + int ret = 0; + + pwm = kzalloc(sizeof(*pwm), GFP_KERNEL); + if (!pwm) + return -ENOMEM; + + pwm->chip = chip; + + mutex_lock(&pwm_lock); + + if (chip->pwm_id >= 0 && _find_pwm(chip->pwm_id)) { + ret = -EBUSY; + goto out; + } + + list_add_tail(&pwm->node, &pwm_list); +out: + mutex_unlock(&pwm_lock); + + if (ret) + kfree(pwm); + + return ret; +} +EXPORT_SYMBOL_GPL(pwmchip_add); + +/** + * pwmchip_remove() - remove a PWM chip + * @chip: the PWM chip to remove + * + * Removes a PWM chip. This function may return busy if the PWM chip provides + * a PWM device that is still requested. + */ +int pwmchip_remove(struct pwm_chip *chip) +{ + struct pwm_device *pwm; + int ret = 0; + + mutex_lock(&pwm_lock); + + pwm = _find_pwm(chip->pwm_id); + if (!pwm) { + ret = -ENOENT; + goto out; + } + + if (test_bit(FLAG_REQUESTED, &pwm->flags)) { + ret = -EBUSY; + goto out; + } + + list_del(&pwm->node); + + kfree(pwm); +out: + mutex_unlock(&pwm_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(pwmchip_remove); + +/** + * pwm_request() - request a PWM device + * @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; + int ret; + + mutex_lock(&pwm_lock); + + pwm = _find_pwm(pwm_id); + if (!pwm) { + pwm = ERR_PTR(-ENOENT); + goto out; + } + + if (test_bit(FLAG_REQUESTED, &pwm->flags)) { + pwm = ERR_PTR(-EBUSY); + goto out; + } + + if (!try_module_get(pwm->chip->ops->owner)) { + pwm = ERR_PTR(-ENODEV); + goto out; + } + + if (pwm->chip->ops->request) { + ret = pwm->chip->ops->request(pwm->chip); + if (ret) { + pwm = ERR_PTR(ret); + goto out_put; + } + } + + pwm->label = label; + set_bit(FLAG_REQUESTED, &pwm->flags); + + goto out; + +out_put: + module_put(pwm->chip->ops->owner); +out: + mutex_unlock(&pwm_lock); + + return pwm; +} +EXPORT_SYMBOL_GPL(pwm_request); + +/** + * pwm_free() - free a PWM device + * @pwm: PWM device + */ +void pwm_free(struct pwm_device *pwm) +{ + mutex_lock(&pwm_lock); + + if (!test_and_clear_bit(FLAG_REQUESTED, &pwm->flags)) { + pr_warning("PWM device already freed\n"); + goto out; + } + + pwm->label = NULL; + + module_put(pwm->chip->ops->owner); +out: + mutex_unlock(&pwm_lock); +} +EXPORT_SYMBOL_GPL(pwm_free); + +/** + * pwm_config() - change a PWM device configuration + * @pwm: PWM device + * @duty_ns: "on" time (in nanoseconds) + * @period_ns: duration (in nanoseconds) of one cycle + */ +int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) +{ + return pwm->chip->ops->config(pwm->chip, duty_ns, period_ns); +} +EXPORT_SYMBOL_GPL(pwm_config); + +/** + * pwm_enable() - start a PWM output toggling + * @pwm: PWM device + */ +int pwm_enable(struct pwm_device *pwm) +{ + if (!test_and_set_bit(FLAG_ENABLED, &pwm->flags)) + return pwm->chip->ops->enable(pwm->chip); + + return 0; +} +EXPORT_SYMBOL_GPL(pwm_enable); + +/** + * pwm_disable() - stop a PWM output toggling + * @pwm: PWM device + */ +void pwm_disable(struct pwm_device *pwm) +{ + if (test_and_clear_bit(FLAG_ENABLED, &pwm->flags)) + pwm->chip->ops->disable(pwm->chip); +} +EXPORT_SYMBOL_GPL(pwm_disable); diff --git a/include/linux/pwm.h b/include/linux/pwm.h index 7c775751392c..1f308a13105f 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -28,4 +28,42 @@ int pwm_enable(struct pwm_device *pwm); */ void pwm_disable(struct pwm_device *pwm); +#ifdef CONFIG_PWM +struct pwm_chip; + +/** + * struct pwm_ops - PWM controller operations + * @request: optional hook for requesting a PWM + * @free: optional hook for freeing a PWM + * @config: configure duty cycles and period length for this PWM + * @enable: enable PWM output toggling + * @disable: disable PWM output toggling + * @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); + 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 { + int pwm_id; + const char *label; + struct pwm_ops *ops; +}; + +int pwmchip_add(struct pwm_chip *chip); +int pwmchip_remove(struct pwm_chip *chip); +#endif + #endif /* __LINUX_PWM_H */ -- cgit v1.2.3-59-g8ed1b From f051c466cf690ac661d713d3ceb56b4efcecc853 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 14 Dec 2011 11:12:23 +0100 Subject: pwm: Allow chips to support multiple PWMs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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: Mark Brown Reviewed-by: Shawn Guo [eric@eukrea.com: fix error handling in pwmchip_add] Signed-off-by: Eric BĂ©nard Signed-off-by: Thierry Reding --- Documentation/pwm.txt | 9 +- drivers/pwm/core.c | 267 +++++++++++++++++++++++++++++++++++--------------- include/linux/pwm.h | 71 +++++++++++--- 3 files changed, 254 insertions(+), 93 deletions(-) diff --git a/Documentation/pwm.txt b/Documentation/pwm.txt index 03e39d145911..48f598acdd16 100644 --- a/Documentation/pwm.txt +++ b/Documentation/pwm.txt @@ -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 ------- diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index 0b8a38eca23c..a447be128328 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -2,6 +2,7 @@ * Generic pwmlib implementation * * Copyright (C) 2011 Sascha Hauer + * 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 #include +#include #include #include #include #include #include -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); +} + +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; - list_for_each_entry(pwm, &pwm_list, node) { - if (pwm->chip->pwm_id == pwm_id) - return 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 * @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; - if (ret) - kfree(pwm); + 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; + +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)) { - ret = -EBUSY; - goto out; + 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); diff --git a/include/linux/pwm.h b/include/linux/pwm.h index 1f308a13105f..57103911f4c7 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -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 */ -- cgit v1.2.3-59-g8ed1b From 62099abf67a20cfb98d4c031fb1925e10a78ee1b Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 26 Mar 2012 09:31:48 +0200 Subject: pwm: Add debugfs interface This commit adds a debugfs interface that can be used to list the current internal state of the PWM devices registered with the PWM framework. Reviewed-by: Mark Brown Reviewed-by: Shawn Guo Signed-off-by: Thierry Reding --- drivers/pwm/core.c | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/pwm.h | 6 ++++ 2 files changed, 96 insertions(+) diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index a447be128328..aadc1d797449 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -27,6 +27,8 @@ #include #include #include +#include +#include #define MAX_PWMS 1024 @@ -338,3 +340,91 @@ void pwm_disable(struct pwm_device *pwm) pwm->chip->ops->disable(pwm->chip, pwm); } EXPORT_SYMBOL_GPL(pwm_disable); + +#ifdef CONFIG_DEBUG_FS +static void pwm_dbg_show(struct pwm_chip *chip, struct seq_file *s) +{ + unsigned int i; + + for (i = 0; i < chip->npwm; i++) { + struct pwm_device *pwm = &chip->pwms[i]; + + seq_printf(s, " pwm-%-3d (%-20.20s):", i, pwm->label); + + if (test_bit(PWMF_REQUESTED, &pwm->flags)) + seq_printf(s, " requested"); + + if (test_bit(PWMF_ENABLED, &pwm->flags)) + seq_printf(s, " enabled"); + + seq_printf(s, "\n"); + } +} + +static void *pwm_seq_start(struct seq_file *s, loff_t *pos) +{ + mutex_lock(&pwm_lock); + s->private = ""; + + return seq_list_start(&pwm_chips, *pos); +} + +static void *pwm_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + s->private = "\n"; + + return seq_list_next(v, &pwm_chips, pos); +} + +static void pwm_seq_stop(struct seq_file *s, void *v) +{ + mutex_unlock(&pwm_lock); +} + +static int pwm_seq_show(struct seq_file *s, void *v) +{ + struct pwm_chip *chip = list_entry(v, struct pwm_chip, list); + + seq_printf(s, "%s%s/%s, %d PWM device%s\n", (char *)s->private, + chip->dev->bus ? chip->dev->bus->name : "no-bus", + dev_name(chip->dev), chip->npwm, + (chip->npwm != 1) ? "s" : ""); + + if (chip->ops->dbg_show) + chip->ops->dbg_show(chip, s); + else + pwm_dbg_show(chip, s); + + return 0; +} + +static const struct seq_operations pwm_seq_ops = { + .start = pwm_seq_start, + .next = pwm_seq_next, + .stop = pwm_seq_stop, + .show = pwm_seq_show, +}; + +static int pwm_seq_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &pwm_seq_ops); +} + +static const struct file_operations pwm_debugfs_ops = { + .owner = THIS_MODULE, + .open = pwm_seq_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static int __init pwm_debugfs_init(void) +{ + debugfs_create_file("pwm", S_IFREG | S_IRUGO, NULL, NULL, + &pwm_debugfs_ops); + + return 0; +} + +subsys_initcall(pwm_debugfs_init); +#endif /* CONFIG_DEBUG_FS */ diff --git a/include/linux/pwm.h b/include/linux/pwm.h index 57103911f4c7..047cd5351a3b 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -2,6 +2,7 @@ #define __LINUX_PWM_H struct pwm_device; +struct seq_file; /* * pwm_request - request a PWM device @@ -65,6 +66,7 @@ static inline unsigned int pwm_get_period(struct pwm_device *pwm) * @config: configure duty cycles and period length for this PWM * @enable: enable PWM output toggling * @disable: disable PWM output toggling + * @dbg_show: optional routine to show contents in debugfs * @owner: helps prevent removal of modules exporting active PWMs */ struct pwm_ops { @@ -79,6 +81,10 @@ struct pwm_ops { 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; }; -- cgit v1.2.3-59-g8ed1b From 8138d2ddbcca2a100482dac390133f83c5a60f94 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 26 Mar 2012 08:42:48 +0200 Subject: pwm: Add table-based lookup for static mappings In order to get rid of the global namespace for PWM devices, this commit provides an alternative method, similar to that of the regulator or clock frameworks, for registering a static mapping for PWM devices. This works by providing a table with a provider/consumer map in the board setup code. With the new pwm_get() and pwm_put() functions available, usage of pwm_request() and pwm_free() becomes deprecated. Reviewed-by: Shawn Guo Reviewed-by: Mark Brown Signed-off-by: Thierry Reding --- Documentation/pwm.txt | 27 ++++++-- drivers/pwm/core.c | 169 +++++++++++++++++++++++++++++++++++++++++++++----- include/linux/pwm.h | 22 +++++++ 3 files changed, 199 insertions(+), 19 deletions(-) diff --git a/Documentation/pwm.txt b/Documentation/pwm.txt index 48f598acdd16..554290ebab94 100644 --- a/Documentation/pwm.txt +++ b/Documentation/pwm.txt @@ -12,14 +12,33 @@ this kind of flexibility the generic PWM API exists. Identifying PWMs ---------------- -Users of the legacy PWM API use unique IDs to refer to PWM devices. One -goal of the new PWM framework is to get rid of this global namespace. +Users of the legacy PWM API use unique IDs to refer to PWM devices. + +Instead of referring to a PWM device via its unique ID, board setup code +should instead register a static mapping that can be used to match PWM +consumers to providers, as given in the following example: + + static struct pwm_lookup board_pwm_lookup[] = { + PWM_LOOKUP("tegra-pwm", 0, "pwm-backlight", NULL), + }; + + static void __init board_init(void) + { + ... + pwm_add_table(board_pwm_lookup, ARRAY_SIZE(board_pwm_lookup)); + ... + } Using PWMs ---------- -A PWM can be requested using pwm_request() and freed after usage with -pwm_free(). After being requested a PWM has to be configured using +Legacy users can request a PWM device using pwm_request() and free it +after usage with pwm_free(). + +New users should use the pwm_get() function and pass to it the consumer +device or a consumer name. pwm_put() is used to free the PWM device. + +After being requested a PWM has to be configured using: int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns); diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index aadc1d797449..a2af599e61ae 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -32,6 +32,8 @@ #define MAX_PWMS 1024 +static DEFINE_MUTEX(pwm_lookup_lock); +static LIST_HEAD(pwm_lookup_list); static DEFINE_MUTEX(pwm_lock); static LIST_HEAD(pwm_chips); static DECLARE_BITMAP(allocated_pwms, MAX_PWMS); @@ -80,6 +82,29 @@ static void free_pwms(struct pwm_chip *chip) chip->pwms = NULL; } +static struct pwm_chip *pwmchip_find_by_name(const char *name) +{ + struct pwm_chip *chip; + + if (!name) + return NULL; + + mutex_lock(&pwm_lock); + + list_for_each_entry(chip, &pwm_chips, list) { + const char *chip_name = dev_name(chip->dev); + + if (chip_name && strcmp(chip_name, name) == 0) { + mutex_unlock(&pwm_lock); + return chip; + } + } + + mutex_unlock(&pwm_lock); + + return NULL; +} + static int pwm_device_request(struct pwm_device *pwm, const char *label) { int err; @@ -218,6 +243,8 @@ EXPORT_SYMBOL_GPL(pwmchip_remove); * pwm_request() - request a PWM device * @pwm_id: global PWM device index * @label: PWM device label + * + * This function is deprecated, use pwm_get() instead. */ struct pwm_device *pwm_request(int pwm, const char *label) { @@ -281,24 +308,12 @@ EXPORT_SYMBOL_GPL(pwm_request_from_chip); /** * pwm_free() - free a PWM device * @pwm: PWM device + * + * This function is deprecated, use pwm_put() instead. */ void pwm_free(struct pwm_device *pwm) { - mutex_lock(&pwm_lock); - - 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); -out: - mutex_unlock(&pwm_lock); + pwm_put(pwm); } EXPORT_SYMBOL_GPL(pwm_free); @@ -341,6 +356,130 @@ void pwm_disable(struct pwm_device *pwm) } EXPORT_SYMBOL_GPL(pwm_disable); +/** + * pwm_add_table() - register PWM device consumers + * @table: array of consumers to register + * @num: number of consumers in table + */ +void __init pwm_add_table(struct pwm_lookup *table, size_t num) +{ + mutex_lock(&pwm_lookup_lock); + + while (num--) { + list_add_tail(&table->list, &pwm_lookup_list); + table++; + } + + mutex_unlock(&pwm_lookup_lock); +} + +/** + * pwm_get() - look up and request a PWM device + * @dev: device for PWM consumer + * @con_id: consumer name + * + * Look up a PWM chip and a relative index via a table supplied by board setup + * code (see pwm_add_table()). + * + * Once a PWM chip has been found the specified PWM device will be requested + * and is ready to be used. + */ +struct pwm_device *pwm_get(struct device *dev, const char *con_id) +{ + struct pwm_device *pwm = ERR_PTR(-EPROBE_DEFER); + const char *dev_id = dev ? dev_name(dev): NULL; + struct pwm_chip *chip = NULL; + unsigned int best = 0; + struct pwm_lookup *p; + unsigned int index; + unsigned int match; + + /* + * We look up the provider in the static table typically provided by + * board setup code. We first try to lookup the consumer device by + * name. If the consumer device was passed in as NULL or if no match + * was found, we try to find the consumer by directly looking it up + * by name. + * + * If a match is found, the provider PWM chip is looked up by name + * and a PWM device is requested using the PWM device per-chip index. + * + * The lookup algorithm was shamelessly taken from the clock + * framework: + * + * We do slightly fuzzy matching here: + * An entry with a NULL ID is assumed to be a wildcard. + * If an entry has a device ID, it must match + * If an entry has a connection ID, it must match + * Then we take the most specific entry - with the following order + * of precedence: dev+con > dev only > con only. + */ + mutex_lock(&pwm_lookup_lock); + + list_for_each_entry(p, &pwm_lookup_list, list) { + match = 0; + + if (p->dev_id) { + if (!dev_id || strcmp(p->dev_id, dev_id)) + continue; + + match += 2; + } + + if (p->con_id) { + if (!con_id || strcmp(p->con_id, con_id)) + continue; + + match += 1; + } + + if (match > best) { + chip = pwmchip_find_by_name(p->provider); + index = p->index; + + if (match != 3) + best = match; + else + break; + } + } + + if (chip) + pwm = pwm_request_from_chip(chip, index, con_id ?: dev_id); + + mutex_unlock(&pwm_lookup_lock); + + return pwm; +} +EXPORT_SYMBOL_GPL(pwm_get); + +/** + * pwm_put() - release a PWM device + * @pwm: PWM device + */ +void pwm_put(struct pwm_device *pwm) +{ + if (!pwm) + return; + + mutex_lock(&pwm_lock); + + 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); +out: + mutex_unlock(&pwm_lock); +} +EXPORT_SYMBOL_GPL(pwm_put); + #ifdef CONFIG_DEBUG_FS static void pwm_dbg_show(struct pwm_chip *chip, struct seq_file *s) { diff --git a/include/linux/pwm.h b/include/linux/pwm.h index 047cd5351a3b..2947a4fea6ad 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -115,6 +115,28 @@ int pwmchip_remove(struct pwm_chip *chip); struct pwm_device *pwm_request_from_chip(struct pwm_chip *chip, unsigned int index, const char *label); + +struct pwm_device *pwm_get(struct device *dev, const char *consumer); +void pwm_put(struct pwm_device *pwm); + +struct pwm_lookup { + struct list_head list; + const char *provider; + unsigned int index; + const char *dev_id; + const char *con_id; +}; + +#define PWM_LOOKUP(_provider, _index, _dev_id, _con_id) \ + { \ + .provider = _provider, \ + .index = _index, \ + .dev_id = _dev_id, \ + .con_id = _con_id, \ + } + +void pwm_add_table(struct pwm_lookup *table, size_t num); + #endif #endif /* __LINUX_PWM_H */ -- cgit v1.2.3-59-g8ed1b From bd3d5500f0c41a30149cb184362716096a17bc75 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 13 Apr 2012 16:18:34 +0200 Subject: dt: Add empty of_property_match_string() function This commit adds an empty of_property_match_string() function for !CONFIG_OF builds. Acked-by: Rob Herring Signed-off-by: Thierry Reding --- include/linux/of.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/linux/of.h b/include/linux/of.h index 2ec1083af7ff..597e571110a2 100644 --- a/include/linux/of.h +++ b/include/linux/of.h @@ -377,6 +377,13 @@ static inline int of_property_read_u64(const struct device_node *np, return -ENOSYS; } +static inline int of_property_match_string(struct device_node *np, + const char *propname, + const char *string) +{ + return -ENOSYS; +} + static inline struct device_node *of_parse_phandle(struct device_node *np, const char *phandle_name, int index) -- cgit v1.2.3-59-g8ed1b From e05e5070f0ec2557d2d2ff3655ba03f29e297151 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 13 Apr 2012 16:19:21 +0200 Subject: dt: Add empty of_parse_phandle_with_args() function This commit adds an empty of_parse_phandle_with_args() function for !CONFIG_OF builds. Acked-by: Rob Herring Signed-off-by: Thierry Reding --- include/linux/of.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/include/linux/of.h b/include/linux/of.h index 597e571110a2..d5dd5c06efd3 100644 --- a/include/linux/of.h +++ b/include/linux/of.h @@ -391,6 +391,15 @@ static inline struct device_node *of_parse_phandle(struct device_node *np, return NULL; } +static inline int of_parse_phandle_with_args(struct device_node *np, + const char *list_name, + const char *cells_name, + int index, + struct of_phandle_args *out_args) +{ + return -ENOSYS; +} + static inline int of_alias_get_id(struct device_node *np, const char *stem) { return -ENOSYS; -- cgit v1.2.3-59-g8ed1b From 7299ab70e68e20e70cb45fe4ab4b6029fe964acd Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 14 Dec 2011 11:10:32 +0100 Subject: pwm: Add device tree support This patch adds helpers to support device tree bindings for the generic PWM API. Device tree binding documentation for PWM controllers is also provided. Acked-by: Arnd Bergmann Reviewed-by: Shawn Guo Signed-off-by: Thierry Reding --- Documentation/devicetree/bindings/pwm/pwm.txt | 57 ++++++++++ drivers/pwm/core.c | 148 +++++++++++++++++++++++++- include/linux/pwm.h | 6 ++ 3 files changed, 209 insertions(+), 2 deletions(-) create mode 100644 Documentation/devicetree/bindings/pwm/pwm.txt diff --git a/Documentation/devicetree/bindings/pwm/pwm.txt b/Documentation/devicetree/bindings/pwm/pwm.txt new file mode 100644 index 000000000000..73ec962bfe8c --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/pwm.txt @@ -0,0 +1,57 @@ +Specifying PWM information for devices +====================================== + +1) PWM user nodes +----------------- + +PWM users should specify a list of PWM devices that they want to use +with a property containing a 'pwm-list': + + pwm-list ::= [pwm-list] + single-pwm ::= + pwm-phandle : phandle to PWM controller node + pwm-specifier : array of #pwm-cells specifying the given PWM + (controller specific) + +PWM properties should be named "pwms". The exact meaning of each pwms +property must be documented in the device tree binding for each device. +An optional property "pwm-names" may contain a list of strings to label +each of the PWM devices listed in the "pwms" property. If no "pwm-names" +property is given, the name of the user node will be used as fallback. + +Drivers for devices that use more than a single PWM device can use the +"pwm-names" property to map the name of the PWM device requested by the +pwm_get() call to an index into the list given by the "pwms" property. + +The following example could be used to describe a PWM-based backlight +device: + + pwm: pwm { + #pwm-cells = <2>; + }; + + [...] + + bl: backlight { + pwms = <&pwm 0 5000000>; + pwm-names = "backlight"; + }; + +pwm-specifier typically encodes the chip-relative PWM number and the PWM +period in nanoseconds. Note that in the example above, specifying the +"pwm-names" is redundant because the name "backlight" would be used as +fallback anyway. + +2) PWM controller nodes +----------------------- + +PWM controller nodes must specify the number of cells used for the +specifier using the '#pwm-cells' property. + +An example PWM controller might look like this: + + pwm: pwm@7000a000 { + compatible = "nvidia,tegra20-pwm"; + reg = <0x7000a000 0x100>; + #pwm-cells = <2>; + }; diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index a2af599e61ae..dbab53005da8 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -129,6 +129,45 @@ static int pwm_device_request(struct pwm_device *pwm, const char *label) return 0; } +static struct pwm_device *of_pwm_simple_xlate(struct pwm_chip *pc, + const struct of_phandle_args *args) +{ + struct pwm_device *pwm; + + if (pc->of_pwm_n_cells < 2) + return ERR_PTR(-EINVAL); + + if (args->args[0] >= pc->npwm) + return ERR_PTR(-EINVAL); + + pwm = pwm_request_from_chip(pc, args->args[0], NULL); + if (IS_ERR(pwm)) + return pwm; + + pwm_set_period(pwm, args->args[1]); + + return pwm; +} + +void of_pwmchip_add(struct pwm_chip *chip) +{ + if (!chip->dev || !chip->dev->of_node) + return; + + if (!chip->of_xlate) { + chip->of_xlate = of_pwm_simple_xlate; + chip->of_pwm_n_cells = 2; + } + + of_node_get(chip->dev->of_node); +} + +void of_pwmchip_remove(struct pwm_chip *chip) +{ + if (chip->dev && chip->dev->of_node) + of_node_put(chip->dev->of_node); +} + /** * pwm_set_chip_data() - set private chip data for a PWM * @pwm: PWM device @@ -201,6 +240,9 @@ int pwmchip_add(struct pwm_chip *chip) ret = 0; + if (IS_ENABLED(CONFIG_OF)) + of_pwmchip_add(chip); + out: mutex_unlock(&pwm_lock); return ret; @@ -231,6 +273,10 @@ int pwmchip_remove(struct pwm_chip *chip) } list_del_init(&chip->list); + + if (IS_ENABLED(CONFIG_OF)) + of_pwmchip_remove(chip); + free_pwms(chip); out: @@ -356,6 +402,99 @@ void pwm_disable(struct pwm_device *pwm) } EXPORT_SYMBOL_GPL(pwm_disable); +static struct pwm_chip *of_node_to_pwmchip(struct device_node *np) +{ + struct pwm_chip *chip; + + mutex_lock(&pwm_lock); + + list_for_each_entry(chip, &pwm_chips, list) + if (chip->dev && chip->dev->of_node == np) { + mutex_unlock(&pwm_lock); + return chip; + } + + mutex_unlock(&pwm_lock); + + return ERR_PTR(-EPROBE_DEFER); +} + +/** + * of_pwm_request() - request a PWM via the PWM framework + * @np: device node to get the PWM from + * @con_id: consumer name + * + * Returns the PWM device parsed from the phandle and index specified in the + * "pwms" property of a device tree node or a negative error-code on failure. + * Values parsed from the device tree are stored in the returned PWM device + * object. + * + * If con_id is NULL, the first PWM device listed in the "pwms" property will + * be requested. Otherwise the "pwm-names" property is used to do a reverse + * lookup of the PWM index. This also means that the "pwm-names" property + * becomes mandatory for devices that look up the PWM device via the con_id + * parameter. + */ +static struct pwm_device *of_pwm_request(struct device_node *np, + const char *con_id) +{ + struct pwm_device *pwm = NULL; + struct of_phandle_args args; + struct pwm_chip *pc; + int index = 0; + int err; + + if (con_id) { + index = of_property_match_string(np, "pwm-names", con_id); + if (index < 0) + return ERR_PTR(index); + } + + err = of_parse_phandle_with_args(np, "pwms", "#pwm-cells", index, + &args); + if (err) { + pr_debug("%s(): can't parse \"pwms\" property\n", __func__); + return ERR_PTR(err); + } + + pc = of_node_to_pwmchip(args.np); + if (IS_ERR(pc)) { + pr_debug("%s(): PWM chip not found\n", __func__); + pwm = ERR_CAST(pc); + goto put; + } + + if (args.args_count != pc->of_pwm_n_cells) { + pr_debug("%s: wrong #pwm-cells for %s\n", np->full_name, + args.np->full_name); + pwm = ERR_PTR(-EINVAL); + goto put; + } + + pwm = pc->of_xlate(pc, &args); + if (IS_ERR(pwm)) + goto put; + + /* + * If a consumer name was not given, try to look it up from the + * "pwm-names" property if it exists. Otherwise use the name of + * the user device node. + */ + if (!con_id) { + err = of_property_read_string_index(np, "pwm-names", index, + &con_id); + if (err < 0) + con_id = np->name; + } + + pwm->label = con_id; + +put: + of_node_put(args.np); + + return pwm; +} + /** * pwm_add_table() - register PWM device consumers * @table: array of consumers to register @@ -378,8 +517,9 @@ void __init pwm_add_table(struct pwm_lookup *table, size_t num) * @dev: device for PWM consumer * @con_id: consumer name * - * Look up a PWM chip and a relative index via a table supplied by board setup - * code (see pwm_add_table()). + * Lookup is first attempted using DT. If the device was not instantiated from + * a device tree, a PWM chip and a relative index is looked up via a table + * supplied by board setup code (see pwm_add_table()). * * Once a PWM chip has been found the specified PWM device will be requested * and is ready to be used. @@ -394,6 +534,10 @@ struct pwm_device *pwm_get(struct device *dev, const char *con_id) unsigned int index; unsigned int match; + /* look up via DT first */ + if (IS_ENABLED(CONFIG_OF) && dev && dev->of_node) + return of_pwm_request(dev->of_node, con_id); + /* * We look up the provider in the static table typically provided by * board setup code. We first try to lookup the consumer device by diff --git a/include/linux/pwm.h b/include/linux/pwm.h index 2947a4fea6ad..21d076c5089e 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -1,6 +1,8 @@ #ifndef __LINUX_PWM_H #define __LINUX_PWM_H +#include + struct pwm_device; struct seq_file; @@ -105,6 +107,10 @@ struct pwm_chip { 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; }; int pwm_set_chip_data(struct pwm_device *pwm, void *data); -- cgit v1.2.3-59-g8ed1b From 0134b932a02f272a3137e8331e38b69eff011587 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 21 Dec 2011 07:47:07 +0100 Subject: pwm: Add NVIDIA Tegra SoC support This commit adds a generic PWM framework driver for the PWFM controller found on NVIDIA Tegra SoCs. The driver is based on code from the Chromium kernel tree and was originally written by Gary King (NVIDIA) and later modified by Simon Que (Chromium). Acked-by: Stephen Warren Signed-off-by: Thierry Reding --- drivers/pwm/Kconfig | 10 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-tegra.c | 254 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 265 insertions(+) create mode 100644 drivers/pwm/pwm-tegra.c diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 93c1052291a3..bda6f23af532 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -9,4 +9,14 @@ menuconfig PWM if PWM +config PWM_TEGRA + tristate "NVIDIA Tegra PWM support" + depends on ARCH_TEGRA + help + Generic PWM framework driver for the PWFM controller found on NVIDIA + Tegra SoCs. + + To compile this driver as a module, choose M here: the module + will be called pwm-tegra. + endif diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 3469c3d28b7a..12300f5e9016 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -1 +1,2 @@ obj-$(CONFIG_PWM) += core.o +obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c new file mode 100644 index 000000000000..0950142d1913 --- /dev/null +++ b/drivers/pwm/pwm-tegra.c @@ -0,0 +1,254 @@ +/* + * drivers/pwm/pwm-tegra.c + * + * Tegra pulse-width-modulation controller driver + * + * Copyright (c) 2010, NVIDIA Corporation. + * Based on arch/arm/plat-mxc/pwm.c by Sascha Hauer + * + * 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define PWM_ENABLE (1 << 31) +#define PWM_DUTY_WIDTH 8 +#define PWM_DUTY_SHIFT 16 +#define PWM_SCALE_WIDTH 13 +#define PWM_SCALE_SHIFT 0 + +#define NUM_PWM 4 + +struct tegra_pwm_chip { + struct pwm_chip chip; + struct device *dev; + + struct clk *clk; + + void __iomem *mmio_base; +}; + +static inline struct tegra_pwm_chip *to_tegra_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct tegra_pwm_chip, chip); +} + +static inline u32 pwm_readl(struct tegra_pwm_chip *chip, unsigned int num) +{ + return readl(chip->mmio_base + (num << 4)); +} + +static inline void pwm_writel(struct tegra_pwm_chip *chip, unsigned int num, + unsigned long val) +{ + writel(val, chip->mmio_base + (num << 4)); +} + +static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip); + unsigned long long c; + unsigned long rate, hz; + u32 val = 0; + int err; + + /* + * Convert from duty_ns / period_ns to a fixed number of duty ticks + * per (1 << PWM_DUTY_WIDTH) cycles and make sure to round to the + * nearest integer during division. + */ + c = duty_ns * ((1 << PWM_DUTY_WIDTH) - 1) + period_ns / 2; + do_div(c, period_ns); + + val = (u32)c << PWM_DUTY_SHIFT; + + /* + * Compute the prescaler value for which (1 << PWM_DUTY_WIDTH) + * cycles at the PWM clock rate will take period_ns nanoseconds. + */ + rate = clk_get_rate(pc->clk) >> PWM_DUTY_WIDTH; + hz = 1000000000ul / period_ns; + + rate = (rate + (hz / 2)) / hz; + + /* + * Since the actual PWM divider is the register's frequency divider + * field minus 1, we need to decrement to get the correct value to + * write to the register. + */ + if (rate > 0) + rate--; + + /* + * Make sure that the rate will fit in the register's frequency + * divider field. + */ + if (rate >> PWM_SCALE_WIDTH) + return -EINVAL; + + val |= rate << PWM_SCALE_SHIFT; + + /* + * If the PWM channel is disabled, make sure to turn on the clock + * before writing the register. Otherwise, keep it enabled. + */ + if (!test_bit(PWMF_ENABLED, &pwm->flags)) { + err = clk_prepare_enable(pc->clk); + if (err < 0) + return err; + } else + val |= PWM_ENABLE; + + pwm_writel(pc, pwm->hwpwm, val); + + /* + * If the PWM is not enabled, turn the clock off again to save power. + */ + if (!test_bit(PWMF_ENABLED, &pwm->flags)) + clk_disable_unprepare(pc->clk); + + return 0; +} + +static int tegra_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip); + int rc = 0; + u32 val; + + rc = clk_prepare_enable(pc->clk); + if (rc < 0) + return rc; + + val = pwm_readl(pc, pwm->hwpwm); + val |= PWM_ENABLE; + pwm_writel(pc, pwm->hwpwm, val); + + return 0; +} + +static void tegra_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip); + u32 val; + + val = pwm_readl(pc, pwm->hwpwm); + val &= ~PWM_ENABLE; + pwm_writel(pc, pwm->hwpwm, val); + + clk_disable_unprepare(pc->clk); +} + +static const struct pwm_ops tegra_pwm_ops = { + .config = tegra_pwm_config, + .enable = tegra_pwm_enable, + .disable = tegra_pwm_disable, + .owner = THIS_MODULE, +}; + +static int tegra_pwm_probe(struct platform_device *pdev) +{ + struct tegra_pwm_chip *pwm; + struct resource *r; + int ret; + + pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); + if (!pwm) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + pwm->dev = &pdev->dev; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + dev_err(&pdev->dev, "no memory resources defined\n"); + return -ENODEV; + } + + pwm->mmio_base = devm_request_and_ioremap(&pdev->dev, r); + if (!pwm->mmio_base) { + dev_err(&pdev->dev, "failed to ioremap() region\n"); + return -EADDRNOTAVAIL; + } + + platform_set_drvdata(pdev, pwm); + + pwm->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(pwm->clk)) + return PTR_ERR(pwm->clk); + + pwm->chip.dev = &pdev->dev; + pwm->chip.ops = &tegra_pwm_ops; + pwm->chip.base = -1; + pwm->chip.npwm = NUM_PWM; + + ret = pwmchip_add(&pwm->chip); + if (ret < 0) { + dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); + clk_put(pwm->clk); + return ret; + } + + return 0; +} + +static int __devexit tegra_pwm_remove(struct platform_device *pdev) +{ + struct tegra_pwm_chip *pc = platform_get_drvdata(pdev); + int i; + + if (WARN_ON(!pc)) + return -ENODEV; + + for (i = 0; i < NUM_PWM; i++) { + struct pwm_device *pwm = &pc->chip.pwms[i]; + + if (!test_bit(PWMF_ENABLED, &pwm->flags)) + if (clk_prepare_enable(pc->clk) < 0) + continue; + + pwm_writel(pc, i, 0); + + clk_disable_unprepare(pc->clk); + } + + pwmchip_remove(&pc->chip); + clk_put(pc->clk); + + return 0; +} + +static struct platform_driver tegra_pwm_driver = { + .driver = { + .name = "tegra-pwm", + }, + .probe = tegra_pwm_probe, + .remove = __devexit_p(tegra_pwm_remove), +}; + +module_platform_driver(tegra_pwm_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("NVIDIA Corporation"); +MODULE_ALIAS("platform:tegra-pwm"); -- cgit v1.2.3-59-g8ed1b From 140fd977dc46bc750258f082cdf1cfea79dc1d14 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 21 Dec 2011 08:04:13 +0100 Subject: pwm: tegra: Add device tree support Add auxdata to instantiate the PWFM controller from a device tree, include the corresponding nodes in the dtsi files for Tegra 20 and Tegra 30 and add binding documentation. Acked-by: Stephen Warren Signed-off-by: Thierry Reding --- .../devicetree/bindings/pwm/nvidia,tegra20-pwm.txt | 18 ++++++++++++++++++ arch/arm/boot/dts/tegra20.dtsi | 6 ++++++ arch/arm/boot/dts/tegra30.dtsi | 6 ++++++ arch/arm/mach-tegra/board-dt-tegra20.c | 1 + arch/arm/mach-tegra/board-dt-tegra30.c | 3 +++ drivers/pwm/pwm-tegra.c | 11 +++++++++++ 6 files changed, 45 insertions(+) create mode 100644 Documentation/devicetree/bindings/pwm/nvidia,tegra20-pwm.txt diff --git a/Documentation/devicetree/bindings/pwm/nvidia,tegra20-pwm.txt b/Documentation/devicetree/bindings/pwm/nvidia,tegra20-pwm.txt new file mode 100644 index 000000000000..bbbeedb4ec05 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/nvidia,tegra20-pwm.txt @@ -0,0 +1,18 @@ +Tegra SoC PWFM controller + +Required properties: +- compatible: should be one of: + - "nvidia,tegra20-pwm" + - "nvidia,tegra30-pwm" +- reg: physical base address and length of the controller's registers +- #pwm-cells: On Tegra the number of cells used to specify a PWM is 2. The + first cell specifies the per-chip index of the PWM to use and the second + cell is the duty cycle in nanoseconds. + +Example: + + pwm: pwm@7000a000 { + compatible = "nvidia,tegra20-pwm"; + reg = <0x7000a000 0x100>; + #pwm-cells = <2>; + }; diff --git a/arch/arm/boot/dts/tegra20.dtsi b/arch/arm/boot/dts/tegra20.dtsi index c417d67e9027..846af573f649 100644 --- a/arch/arm/boot/dts/tegra20.dtsi +++ b/arch/arm/boot/dts/tegra20.dtsi @@ -123,6 +123,12 @@ status = "disable"; }; + pwm { + compatible = "nvidia,tegra20-pwm"; + reg = <0x7000a000 0x100>; + #pwm-cells = <2>; + }; + i2c@7000c000 { compatible = "nvidia,tegra20-i2c"; reg = <0x7000c000 0x100>; diff --git a/arch/arm/boot/dts/tegra30.dtsi b/arch/arm/boot/dts/tegra30.dtsi index 2dcc09e784b5..f7f428e81e3a 100644 --- a/arch/arm/boot/dts/tegra30.dtsi +++ b/arch/arm/boot/dts/tegra30.dtsi @@ -117,6 +117,12 @@ status = "disable"; }; + pwm { + compatible = "nvidia,tegra30-pwm", "nvidia,tegra20-pwm"; + reg = <0x7000a000 0x100>; + #pwm-cells = <2>; + }; + i2c@7000c000 { compatible = "nvidia,tegra30-i2c", "nvidia,tegra20-i2c"; reg = <0x7000c000 0x100>; diff --git a/arch/arm/mach-tegra/board-dt-tegra20.c b/arch/arm/mach-tegra/board-dt-tegra20.c index eb7249db50a5..962f1a1b12e9 100644 --- a/arch/arm/mach-tegra/board-dt-tegra20.c +++ b/arch/arm/mach-tegra/board-dt-tegra20.c @@ -64,6 +64,7 @@ struct of_dev_auxdata tegra20_auxdata_lookup[] __initdata = { &tegra_ehci2_pdata), OF_DEV_AUXDATA("nvidia,tegra20-ehci", TEGRA_USB3_BASE, "tegra-ehci.2", &tegra_ehci3_pdata), + OF_DEV_AUXDATA("nvidia,tegra20-pwm", TEGRA_PWFM_BASE, "tegra-pwm", NULL), {} }; diff --git a/arch/arm/mach-tegra/board-dt-tegra30.c b/arch/arm/mach-tegra/board-dt-tegra30.c index 4f76fa7a5da3..ffc6cae3eb2c 100644 --- a/arch/arm/mach-tegra/board-dt-tegra30.c +++ b/arch/arm/mach-tegra/board-dt-tegra30.c @@ -33,6 +33,8 @@ #include #include +#include + #include "board.h" #include "clock.h" @@ -52,6 +54,7 @@ struct of_dev_auxdata tegra30_auxdata_lookup[] __initdata = { OF_DEV_AUXDATA("nvidia,tegra20-i2c", 0x7000C700, "tegra-i2c.3", NULL), OF_DEV_AUXDATA("nvidia,tegra20-i2c", 0x7000D000, "tegra-i2c.4", NULL), OF_DEV_AUXDATA("nvidia,tegra30-ahub", 0x70080000, "tegra30-ahub", NULL), + OF_DEV_AUXDATA("nvidia,tegra30-pwm", TEGRA_PWFM_BASE, "tegra-pwm", NULL), {} }; diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c index 0950142d1913..472b74e8221a 100644 --- a/drivers/pwm/pwm-tegra.c +++ b/drivers/pwm/pwm-tegra.c @@ -239,9 +239,20 @@ static int __devexit tegra_pwm_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_OF +static struct of_device_id tegra_pwm_of_match[] = { + { .compatible = "nvidia,tegra20-pwm" }, + { .compatible = "nvidia,tegra30-pwm" }, + { } +}; + +MODULE_DEVICE_TABLE(of, tegra_pwm_of_match); +#endif + static struct platform_driver tegra_pwm_driver = { .driver = { .name = "tegra-pwm", + .of_match_table = of_match_ptr(tegra_pwm_of_match), }, .probe = tegra_pwm_probe, .remove = __devexit_p(tegra_pwm_remove), -- cgit v1.2.3-59-g8ed1b From a4315e3c11f97ea0fa602184fa310161309657df Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 2 Jan 2012 20:53:50 +0100 Subject: pwm: Move Blackfin PWM driver to PWM framework This commit moves the Blackfin PWM driver to the drivers/pwm sub- directory and converts it to register with the new PWM framework. Signed-off-by: Thierry Reding --- arch/blackfin/Kconfig | 10 --- arch/blackfin/kernel/Makefile | 1 - arch/blackfin/kernel/pwm.c | 100 -------------------------- drivers/pwm/Kconfig | 9 +++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-bfin.c | 164 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 174 insertions(+), 111 deletions(-) delete mode 100644 arch/blackfin/kernel/pwm.c create mode 100644 drivers/pwm/pwm-bfin.c diff --git a/arch/blackfin/Kconfig b/arch/blackfin/Kconfig index fef96f47876c..3307d2e7368e 100644 --- a/arch/blackfin/Kconfig +++ b/arch/blackfin/Kconfig @@ -996,16 +996,6 @@ config BFIN_GPTIMERS To compile this driver as a module, choose M here: the module will be called gptimers. -config HAVE_PWM - tristate "Enable PWM API support" - depends on BFIN_GPTIMERS - help - Enable support for the Pulse Width Modulation framework (as - found in linux/pwm.h). - - To compile this driver as a module, choose M here: the module - will be called pwm. - choice prompt "Uncached DMA region" default DMA_UNCACHED_1M diff --git a/arch/blackfin/kernel/Makefile b/arch/blackfin/kernel/Makefile index 08e6625106be..735f24e07425 100644 --- a/arch/blackfin/kernel/Makefile +++ b/arch/blackfin/kernel/Makefile @@ -21,7 +21,6 @@ obj-$(CONFIG_FUNCTION_TRACER) += ftrace-entry.o obj-$(CONFIG_FUNCTION_GRAPH_TRACER) += ftrace.o CFLAGS_REMOVE_ftrace.o = -pg -obj-$(CONFIG_HAVE_PWM) += pwm.o obj-$(CONFIG_IPIPE) += ipipe.o obj-$(CONFIG_BFIN_GPTIMERS) += gptimers.o obj-$(CONFIG_CPLB_INFO) += cplbinfo.o diff --git a/arch/blackfin/kernel/pwm.c b/arch/blackfin/kernel/pwm.c deleted file mode 100644 index 33f5942733bd..000000000000 --- a/arch/blackfin/kernel/pwm.c +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Blackfin Pulse Width Modulation (PWM) core - * - * Copyright (c) 2011 Analog Devices Inc. - * - * Licensed under the GPL-2 or later. - */ - -#include -#include -#include - -#include -#include - -struct pwm_device { - unsigned id; - unsigned short pin; -}; - -static const unsigned short pwm_to_gptimer_per[] = { - P_TMR0, P_TMR1, P_TMR2, P_TMR3, P_TMR4, P_TMR5, - P_TMR6, P_TMR7, P_TMR8, P_TMR9, P_TMR10, P_TMR11, -}; - -struct pwm_device *pwm_request(int pwm_id, const char *label) -{ - struct pwm_device *pwm; - int ret; - - /* XXX: pwm_id really should be unsigned */ - if (pwm_id < 0) - return NULL; - - pwm = kzalloc(sizeof(*pwm), GFP_KERNEL); - if (!pwm) - return pwm; - - pwm->id = pwm_id; - if (pwm->id >= ARRAY_SIZE(pwm_to_gptimer_per)) - goto err; - - pwm->pin = pwm_to_gptimer_per[pwm->id]; - ret = peripheral_request(pwm->pin, label); - if (ret) - goto err; - - return pwm; - err: - kfree(pwm); - return NULL; -} -EXPORT_SYMBOL(pwm_request); - -void pwm_free(struct pwm_device *pwm) -{ - peripheral_free(pwm->pin); - kfree(pwm); -} -EXPORT_SYMBOL(pwm_free); - -int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) -{ - unsigned long period, duty; - unsigned long long val; - - if (duty_ns < 0 || duty_ns > period_ns) - return -EINVAL; - - val = (unsigned long long)get_sclk() * period_ns; - do_div(val, NSEC_PER_SEC); - period = val; - - val = (unsigned long long)period * duty_ns; - do_div(val, period_ns); - duty = period - val; - - if (duty >= period) - duty = period - 1; - - set_gptimer_config(pwm->id, TIMER_MODE_PWM | TIMER_PERIOD_CNT); - set_gptimer_pwidth(pwm->id, duty); - set_gptimer_period(pwm->id, period); - - return 0; -} -EXPORT_SYMBOL(pwm_config); - -int pwm_enable(struct pwm_device *pwm) -{ - enable_gptimer(pwm->id); - return 0; -} -EXPORT_SYMBOL(pwm_enable); - -void pwm_disable(struct pwm_device *pwm) -{ - disable_gptimer(pwm->id); -} -EXPORT_SYMBOL(pwm_disable); diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index bda6f23af532..eb5404268135 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -9,6 +9,15 @@ menuconfig PWM if PWM +config PWM_BFIN + tristate "Blackfin PWM support" + depends on BFIN_GPTIMERS + help + Generic PWM framework driver for Blackfin. + + To compile this driver as a module, choose M here: the module + will be called pwm-bfin. + config PWM_TEGRA tristate "NVIDIA Tegra PWM support" depends on ARCH_TEGRA diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 12300f5e9016..251b8d22435f 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -1,2 +1,3 @@ obj-$(CONFIG_PWM) += core.o +obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o diff --git a/drivers/pwm/pwm-bfin.c b/drivers/pwm/pwm-bfin.c new file mode 100644 index 000000000000..a0c6bf9070ea --- /dev/null +++ b/drivers/pwm/pwm-bfin.c @@ -0,0 +1,164 @@ +/* + * Blackfin Pulse Width Modulation (PWM) core + * + * Copyright (c) 2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include + +#include +#include + +struct bfin_pwm_chip { + struct pwm_chip chip; +}; + +struct bfin_pwm { + unsigned short pin; +}; + +static const unsigned short pwm_to_gptimer_per[] = { + P_TMR0, P_TMR1, P_TMR2, P_TMR3, P_TMR4, P_TMR5, + P_TMR6, P_TMR7, P_TMR8, P_TMR9, P_TMR10, P_TMR11, +}; + +static int bfin_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct bfin_pwm *priv; + int ret; + + if (pwm->hwpwm >= ARRAY_SIZE(pwm_to_gptimer_per)) + return -EINVAL; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->pin = pwm_to_gptimer_per[pwm->hwpwm]; + + ret = peripheral_request(priv->pin, NULL); + if (ret) { + kfree(priv); + return ret; + } + + pwm_set_chip_data(pwm, priv); + + return 0; +} + +static void bfin_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct bfin_pwm *priv = pwm_get_chip_data(pwm); + + if (priv) { + peripheral_free(priv->pin); + kfree(priv); + } +} + +static int bfin_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct bfin_pwm *priv = pwm_get_chip_data(pwm); + unsigned long period, duty; + unsigned long long val; + + if (duty_ns < 0 || duty_ns > period_ns) + return -EINVAL; + + val = (unsigned long long)get_sclk() * period_ns; + do_div(val, NSEC_PER_SEC); + period = val; + + val = (unsigned long long)period * duty_ns; + do_div(val, period_ns); + duty = period - val; + + if (duty >= period) + duty = period - 1; + + set_gptimer_config(priv->pin, TIMER_MODE_PWM | TIMER_PERIOD_CNT); + set_gptimer_pwidth(priv->pin, duty); + set_gptimer_period(priv->pin, period); + + return 0; +} + +static int bfin_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct bfin_pwm *priv = pwm_get_chip_data(pwm); + + enable_gptimer(priv->pin); + + return 0; +} + +static void bfin_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct bfin_pwm *priv = pwm_get_chip_data(pwm); + + disable_gptimer(priv->pin); +} + +static struct pwm_ops bfin_pwm_ops = { + .request = bfin_pwm_request, + .free = bfin_pwm_free, + .config = bfin_pwm_config, + .enable = bfin_pwm_enable, + .disable = bfin_pwm_disable, + .owner = THIS_MODULE, +}; + +static int bfin_pwm_probe(struct platform_device *pdev) +{ + struct bfin_pwm_chip *pwm; + int ret; + + pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); + if (!pwm) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, pwm); + + pwm->chip.dev = &pdev->dev; + pwm->chip.ops = &bfin_pwm_ops; + pwm->chip.base = -1; + pwm->chip.npwm = 12; + + ret = pwmchip_add(&pwm->chip); + if (ret < 0) { + dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int __devexit bfin_pwm_remove(struct platform_device *pdev) +{ + struct bfin_pwm_chip *pwm = platform_get_drvdata(pdev); + + pwmchip_remove(&pwm->chip); + + return 0; +} + +static struct platform_driver bfin_pwm_driver = { + .driver = { + .name = "bfin-pwm", + }, + .probe = bfin_pwm_probe, + .remove = __devexit_p(bfin_pwm_remove), +}; + +module_platform_driver(bfin_pwm_driver); + +MODULE_LICENSE("GPL"); -- cgit v1.2.3-59-g8ed1b From 17b2b4780f879a559b8dc96c170dd6df98fe9ab3 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 2 Jan 2012 21:22:38 +0100 Subject: pwm: Move PXA PWM driver to PWM framework This commit moves the PXA PWM driver to the drivers/pwm subdirectory and converts it to use the new PWM framework. Signed-off-by: Thierry Reding --- arch/arm/plat-pxa/Makefile | 1 - arch/arm/plat-pxa/pwm.c | 304 --------------------------------------------- drivers/pwm/Kconfig | 9 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-pxa.c | 248 ++++++++++++++++++++++++++++++++++++ 5 files changed, 258 insertions(+), 305 deletions(-) delete mode 100644 arch/arm/plat-pxa/pwm.c create mode 100644 drivers/pwm/pwm-pxa.c diff --git a/arch/arm/plat-pxa/Makefile b/arch/arm/plat-pxa/Makefile index f302d048392d..af8e484001e5 100644 --- a/arch/arm/plat-pxa/Makefile +++ b/arch/arm/plat-pxa/Makefile @@ -8,5 +8,4 @@ obj-$(CONFIG_PXA3xx) += mfp.o obj-$(CONFIG_PXA95x) += mfp.o obj-$(CONFIG_ARCH_MMP) += mfp.o -obj-$(CONFIG_HAVE_PWM) += pwm.o obj-$(CONFIG_PXA_SSP) += ssp.o diff --git a/arch/arm/plat-pxa/pwm.c b/arch/arm/plat-pxa/pwm.c deleted file mode 100644 index ef32686feef9..000000000000 --- a/arch/arm/plat-pxa/pwm.c +++ /dev/null @@ -1,304 +0,0 @@ -/* - * linux/arch/arm/mach-pxa/pwm.c - * - * simple driver for PWM (Pulse Width Modulator) controller - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * 2008-02-13 initial version - * eric miao - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#define HAS_SECONDARY_PWM 0x10 -#define PWM_ID_BASE(d) ((d) & 0xf) - -static const struct platform_device_id pwm_id_table[] = { - /* PWM has_secondary_pwm? */ - { "pxa25x-pwm", 0 }, - { "pxa27x-pwm", 0 | HAS_SECONDARY_PWM }, - { "pxa168-pwm", 1 }, - { "pxa910-pwm", 1 }, - { }, -}; -MODULE_DEVICE_TABLE(platform, pwm_id_table); - -/* PWM registers and bits definitions */ -#define PWMCR (0x00) -#define PWMDCR (0x04) -#define PWMPCR (0x08) - -#define PWMCR_SD (1 << 6) -#define PWMDCR_FD (1 << 10) - -struct pwm_device { - struct list_head node; - struct pwm_device *secondary; - struct platform_device *pdev; - - const char *label; - struct clk *clk; - int clk_enabled; - void __iomem *mmio_base; - - unsigned int use_count; - unsigned int pwm_id; -}; - -/* - * period_ns = 10^9 * (PRESCALE + 1) * (PV + 1) / PWM_CLK_RATE - * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE - */ -int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) -{ - unsigned long long c; - unsigned long period_cycles, prescale, pv, dc; - - if (pwm == NULL || period_ns == 0 || duty_ns > period_ns) - return -EINVAL; - - c = clk_get_rate(pwm->clk); - c = c * period_ns; - do_div(c, 1000000000); - period_cycles = c; - - if (period_cycles < 1) - period_cycles = 1; - prescale = (period_cycles - 1) / 1024; - pv = period_cycles / (prescale + 1) - 1; - - if (prescale > 63) - return -EINVAL; - - if (duty_ns == period_ns) - dc = PWMDCR_FD; - else - dc = (pv + 1) * duty_ns / period_ns; - - /* NOTE: the clock to PWM has to be enabled first - * before writing to the registers - */ - clk_enable(pwm->clk); - __raw_writel(prescale, pwm->mmio_base + PWMCR); - __raw_writel(dc, pwm->mmio_base + PWMDCR); - __raw_writel(pv, pwm->mmio_base + PWMPCR); - clk_disable(pwm->clk); - - return 0; -} -EXPORT_SYMBOL(pwm_config); - -int pwm_enable(struct pwm_device *pwm) -{ - int rc = 0; - - if (!pwm->clk_enabled) { - rc = clk_enable(pwm->clk); - if (!rc) - pwm->clk_enabled = 1; - } - return rc; -} -EXPORT_SYMBOL(pwm_enable); - -void pwm_disable(struct pwm_device *pwm) -{ - if (pwm->clk_enabled) { - clk_disable(pwm->clk); - pwm->clk_enabled = 0; - } -} -EXPORT_SYMBOL(pwm_disable); - -static DEFINE_MUTEX(pwm_lock); -static LIST_HEAD(pwm_list); - -struct pwm_device *pwm_request(int pwm_id, const char *label) -{ - struct pwm_device *pwm; - int found = 0; - - mutex_lock(&pwm_lock); - - list_for_each_entry(pwm, &pwm_list, node) { - if (pwm->pwm_id == pwm_id) { - found = 1; - break; - } - } - - if (found) { - if (pwm->use_count == 0) { - pwm->use_count++; - pwm->label = label; - } else - pwm = ERR_PTR(-EBUSY); - } else - pwm = ERR_PTR(-ENOENT); - - mutex_unlock(&pwm_lock); - return pwm; -} -EXPORT_SYMBOL(pwm_request); - -void pwm_free(struct pwm_device *pwm) -{ - mutex_lock(&pwm_lock); - - if (pwm->use_count) { - pwm->use_count--; - pwm->label = NULL; - } else - pr_warning("PWM device already freed\n"); - - mutex_unlock(&pwm_lock); -} -EXPORT_SYMBOL(pwm_free); - -static inline void __add_pwm(struct pwm_device *pwm) -{ - mutex_lock(&pwm_lock); - list_add_tail(&pwm->node, &pwm_list); - mutex_unlock(&pwm_lock); -} - -static int __devinit pwm_probe(struct platform_device *pdev) -{ - const struct platform_device_id *id = platform_get_device_id(pdev); - struct pwm_device *pwm, *secondary = NULL; - struct resource *r; - int ret = 0; - - pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL); - if (pwm == NULL) { - dev_err(&pdev->dev, "failed to allocate memory\n"); - return -ENOMEM; - } - - pwm->clk = clk_get(&pdev->dev, NULL); - if (IS_ERR(pwm->clk)) { - ret = PTR_ERR(pwm->clk); - goto err_free; - } - pwm->clk_enabled = 0; - - pwm->use_count = 0; - pwm->pwm_id = PWM_ID_BASE(id->driver_data) + pdev->id; - pwm->pdev = pdev; - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (r == NULL) { - dev_err(&pdev->dev, "no memory resource defined\n"); - ret = -ENODEV; - goto err_free_clk; - } - - r = request_mem_region(r->start, resource_size(r), pdev->name); - if (r == NULL) { - dev_err(&pdev->dev, "failed to request memory resource\n"); - ret = -EBUSY; - goto err_free_clk; - } - - pwm->mmio_base = ioremap(r->start, resource_size(r)); - if (pwm->mmio_base == NULL) { - dev_err(&pdev->dev, "failed to ioremap() registers\n"); - ret = -ENODEV; - goto err_free_mem; - } - - if (id->driver_data & HAS_SECONDARY_PWM) { - secondary = kzalloc(sizeof(struct pwm_device), GFP_KERNEL); - if (secondary == NULL) { - ret = -ENOMEM; - goto err_free_mem; - } - - *secondary = *pwm; - pwm->secondary = secondary; - - /* registers for the second PWM has offset of 0x10 */ - secondary->mmio_base = pwm->mmio_base + 0x10; - secondary->pwm_id = pdev->id + 2; - } - - __add_pwm(pwm); - if (secondary) - __add_pwm(secondary); - - platform_set_drvdata(pdev, pwm); - return 0; - -err_free_mem: - release_mem_region(r->start, resource_size(r)); -err_free_clk: - clk_put(pwm->clk); -err_free: - kfree(pwm); - return ret; -} - -static int __devexit pwm_remove(struct platform_device *pdev) -{ - struct pwm_device *pwm; - struct resource *r; - - pwm = platform_get_drvdata(pdev); - if (pwm == NULL) - return -ENODEV; - - mutex_lock(&pwm_lock); - - if (pwm->secondary) { - list_del(&pwm->secondary->node); - kfree(pwm->secondary); - } - - list_del(&pwm->node); - mutex_unlock(&pwm_lock); - - iounmap(pwm->mmio_base); - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - release_mem_region(r->start, resource_size(r)); - - clk_put(pwm->clk); - kfree(pwm); - return 0; -} - -static struct platform_driver pwm_driver = { - .driver = { - .name = "pxa25x-pwm", - .owner = THIS_MODULE, - }, - .probe = pwm_probe, - .remove = __devexit_p(pwm_remove), - .id_table = pwm_id_table, -}; - -static int __init pwm_init(void) -{ - return platform_driver_register(&pwm_driver); -} -arch_initcall(pwm_init); - -static void __exit pwm_exit(void) -{ - platform_driver_unregister(&pwm_driver); -} -module_exit(pwm_exit); - -MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index eb5404268135..0ef4f3037c4a 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -18,6 +18,15 @@ config PWM_BFIN To compile this driver as a module, choose M here: the module will be called pwm-bfin. +config PWM_PXA + tristate "PXA PWM support" + depends on ARCH_PXA + help + Generic PWM framework driver for PXA. + + To compile this driver as a module, choose M here: the module + will be called pwm-pxa. + config PWM_TEGRA tristate "NVIDIA Tegra PWM support" depends on ARCH_TEGRA diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 251b8d22435f..e859c513add8 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -1,3 +1,4 @@ obj-$(CONFIG_PWM) += core.o obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o +obj-$(CONFIG_PWM_PXA) += pwm-pxa.o obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o diff --git a/drivers/pwm/pwm-pxa.c b/drivers/pwm/pwm-pxa.c new file mode 100644 index 000000000000..d5c6ce513e29 --- /dev/null +++ b/drivers/pwm/pwm-pxa.c @@ -0,0 +1,248 @@ +/* + * linux/arch/arm/mach-pxa/pwm.c + * + * simple driver for PWM (Pulse Width Modulator) controller + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 2008-02-13 initial version + * eric miao + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define HAS_SECONDARY_PWM 0x10 +#define PWM_ID_BASE(d) ((d) & 0xf) + +static const struct platform_device_id pwm_id_table[] = { + /* PWM has_secondary_pwm? */ + { "pxa25x-pwm", 0 }, + { "pxa27x-pwm", 0 | HAS_SECONDARY_PWM }, + { "pxa168-pwm", 1 }, + { "pxa910-pwm", 1 }, + { }, +}; +MODULE_DEVICE_TABLE(platform, pwm_id_table); + +/* PWM registers and bits definitions */ +#define PWMCR (0x00) +#define PWMDCR (0x04) +#define PWMPCR (0x08) + +#define PWMCR_SD (1 << 6) +#define PWMDCR_FD (1 << 10) + +struct pxa_pwm_chip { + struct pwm_chip chip; + struct device *dev; + + struct clk *clk; + int clk_enabled; + void __iomem *mmio_base; +}; + +static inline struct pxa_pwm_chip *to_pxa_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct pxa_pwm_chip, chip); +} + +/* + * period_ns = 10^9 * (PRESCALE + 1) * (PV + 1) / PWM_CLK_RATE + * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE + */ +static int pxa_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct pxa_pwm_chip *pc = to_pxa_pwm_chip(chip); + unsigned long long c; + unsigned long period_cycles, prescale, pv, dc; + unsigned long offset; + int rc; + + if (period_ns == 0 || duty_ns > period_ns) + return -EINVAL; + + offset = pwm->hwpwm ? 0x10 : 0; + + c = clk_get_rate(pc->clk); + c = c * period_ns; + do_div(c, 1000000000); + period_cycles = c; + + if (period_cycles < 1) + period_cycles = 1; + prescale = (period_cycles - 1) / 1024; + pv = period_cycles / (prescale + 1) - 1; + + if (prescale > 63) + return -EINVAL; + + if (duty_ns == period_ns) + dc = PWMDCR_FD; + else + dc = (pv + 1) * duty_ns / period_ns; + + /* NOTE: the clock to PWM has to be enabled first + * before writing to the registers + */ + rc = clk_prepare_enable(pc->clk); + if (rc < 0) + return rc; + + writel(prescale, pc->mmio_base + offset + PWMCR); + writel(dc, pc->mmio_base + offset + PWMDCR); + writel(pv, pc->mmio_base + offset + PWMPCR); + + clk_disable_unprepare(pc->clk); + return 0; +} + +static int pxa_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pxa_pwm_chip *pc = to_pxa_pwm_chip(chip); + int rc = 0; + + if (!pc->clk_enabled) { + rc = clk_prepare_enable(pc->clk); + if (!rc) + pc->clk_enabled++; + } + return rc; +} + +static void pxa_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pxa_pwm_chip *pc = to_pxa_pwm_chip(chip); + + if (pc->clk_enabled) { + clk_disable_unprepare(pc->clk); + pc->clk_enabled--; + } +} + +static struct pwm_ops pxa_pwm_ops = { + .config = pxa_pwm_config, + .enable = pxa_pwm_enable, + .disable = pxa_pwm_disable, + .owner = THIS_MODULE, +}; + +static int __devinit pwm_probe(struct platform_device *pdev) +{ + const struct platform_device_id *id = platform_get_device_id(pdev); + struct pxa_pwm_chip *pwm; + struct resource *r; + int ret = 0; + + pwm = kzalloc(sizeof(*pwm), GFP_KERNEL); + if (pwm == NULL) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + pwm->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(pwm->clk)) { + ret = PTR_ERR(pwm->clk); + goto err_free; + } + pwm->clk_enabled = 0; + + pwm->chip.dev = &pdev->dev; + pwm->chip.ops = &pxa_pwm_ops; + pwm->chip.base = -1; + pwm->chip.npwm = (id->driver_data & HAS_SECONDARY_PWM) ? 2 : 1; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (r == NULL) { + dev_err(&pdev->dev, "no memory resource defined\n"); + ret = -ENODEV; + goto err_free_clk; + } + + r = request_mem_region(r->start, resource_size(r), pdev->name); + if (r == NULL) { + dev_err(&pdev->dev, "failed to request memory resource\n"); + ret = -EBUSY; + goto err_free_clk; + } + + pwm->mmio_base = ioremap(r->start, resource_size(r)); + if (pwm->mmio_base == NULL) { + dev_err(&pdev->dev, "failed to ioremap() registers\n"); + ret = -ENODEV; + goto err_free_mem; + } + + ret = pwmchip_add(&pwm->chip); + if (ret < 0) { + dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, pwm); + return 0; + +err_free_mem: + release_mem_region(r->start, resource_size(r)); +err_free_clk: + clk_put(pwm->clk); +err_free: + kfree(pwm); + return ret; +} + +static int __devexit pwm_remove(struct platform_device *pdev) +{ + struct pxa_pwm_chip *chip; + struct resource *r; + + chip = platform_get_drvdata(pdev); + if (chip == NULL) + return -ENODEV; + + pwmchip_remove(&chip->chip); + + iounmap(chip->mmio_base); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(r->start, resource_size(r)); + + clk_put(chip->clk); + kfree(chip); + return 0; +} + +static struct platform_driver pwm_driver = { + .driver = { + .name = "pxa25x-pwm", + .owner = THIS_MODULE, + }, + .probe = pwm_probe, + .remove = __devexit_p(pwm_remove), + .id_table = pwm_id_table, +}; + +static int __init pwm_init(void) +{ + return platform_driver_register(&pwm_driver); +} +arch_initcall(pwm_init); + +static void __exit pwm_exit(void) +{ + platform_driver_unregister(&pwm_driver); +} +module_exit(pwm_exit); + +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-59-g8ed1b From 29693248edf10830db2ef82b36806e378ff75c67 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 15 Mar 2012 10:04:35 +0100 Subject: ARM i.MX: Move i.MX pwm driver to pwm framework MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the driver to drivers/pwm/ and convert it to use the framework. Signed-off-by: Sascha Hauer [eric@eukrea.com: set chip.dev to prevent probe failure] [eric@eukrea.com: fix pwmchip_add return code test] Signed-off-by: Eric BĂ©nard Signed-off-by: Thierry Reding --- arch/arm/plat-mxc/Kconfig | 6 - arch/arm/plat-mxc/Makefile | 1 - arch/arm/plat-mxc/pwm.c | 306 --------------------------------------------- drivers/pwm/Kconfig | 9 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-imx.c | 267 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 277 insertions(+), 313 deletions(-) delete mode 100644 arch/arm/plat-mxc/pwm.c create mode 100644 drivers/pwm/pwm-imx.c diff --git a/arch/arm/plat-mxc/Kconfig b/arch/arm/plat-mxc/Kconfig index c722f9ce6918..baf9064c0844 100644 --- a/arch/arm/plat-mxc/Kconfig +++ b/arch/arm/plat-mxc/Kconfig @@ -47,12 +47,6 @@ config MXC_TZIC config MXC_AVIC bool -config MXC_PWM - tristate "Enable PWM driver" - select HAVE_PWM - help - Enable support for the i.MX PWM controller(s). - config MXC_DEBUG_BOARD bool "Enable MXC debug board(for 3-stack)" help diff --git a/arch/arm/plat-mxc/Makefile b/arch/arm/plat-mxc/Makefile index e81290c27c65..46ba8de2fe4e 100644 --- a/arch/arm/plat-mxc/Makefile +++ b/arch/arm/plat-mxc/Makefile @@ -11,7 +11,6 @@ obj-$(CONFIG_MXC_AVIC) += avic.o obj-$(CONFIG_IMX_HAVE_IOMUX_V1) += iomux-v1.o obj-$(CONFIG_ARCH_MXC_IOMUX_V3) += iomux-v3.o obj-$(CONFIG_IRAM_ALLOC) += iram_alloc.o -obj-$(CONFIG_MXC_PWM) += pwm.o obj-$(CONFIG_MXC_ULPI) += ulpi.o obj-$(CONFIG_MXC_USE_EPIT) += epit.o obj-$(CONFIG_MXC_DEBUG_BOARD) += 3ds_debugboard.o diff --git a/arch/arm/plat-mxc/pwm.c b/arch/arm/plat-mxc/pwm.c deleted file mode 100644 index c0cab2270dd1..000000000000 --- a/arch/arm/plat-mxc/pwm.c +++ /dev/null @@ -1,306 +0,0 @@ -/* - * simple driver for PWM (Pulse Width Modulator) controller - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * Derived from pxa PWM driver by eric miao - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -/* i.MX1 and i.MX21 share the same PWM function block: */ - -#define MX1_PWMC 0x00 /* PWM Control Register */ -#define MX1_PWMS 0x04 /* PWM Sample Register */ -#define MX1_PWMP 0x08 /* PWM Period Register */ - - -/* i.MX27, i.MX31, i.MX35 share the same PWM function block: */ - -#define MX3_PWMCR 0x00 /* PWM Control Register */ -#define MX3_PWMSAR 0x0C /* PWM Sample Register */ -#define MX3_PWMPR 0x10 /* PWM Period Register */ -#define MX3_PWMCR_PRESCALER(x) (((x - 1) & 0xFFF) << 4) -#define MX3_PWMCR_DOZEEN (1 << 24) -#define MX3_PWMCR_WAITEN (1 << 23) -#define MX3_PWMCR_DBGEN (1 << 22) -#define MX3_PWMCR_CLKSRC_IPG_HIGH (2 << 16) -#define MX3_PWMCR_CLKSRC_IPG (1 << 16) -#define MX3_PWMCR_EN (1 << 0) - - - -struct pwm_device { - struct list_head node; - struct platform_device *pdev; - - const char *label; - struct clk *clk; - - int clk_enabled; - void __iomem *mmio_base; - - unsigned int use_count; - unsigned int pwm_id; -}; - -int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) -{ - if (pwm == NULL || period_ns == 0 || duty_ns > period_ns) - return -EINVAL; - - if (!(cpu_is_mx1() || cpu_is_mx21())) { - unsigned long long c; - unsigned long period_cycles, duty_cycles, prescale; - u32 cr; - - c = clk_get_rate(pwm->clk); - c = c * period_ns; - do_div(c, 1000000000); - period_cycles = c; - - prescale = period_cycles / 0x10000 + 1; - - period_cycles /= prescale; - c = (unsigned long long)period_cycles * duty_ns; - do_div(c, period_ns); - duty_cycles = c; - - /* - * according to imx pwm RM, the real period value should be - * PERIOD value in PWMPR plus 2. - */ - if (period_cycles > 2) - period_cycles -= 2; - else - period_cycles = 0; - - writel(duty_cycles, pwm->mmio_base + MX3_PWMSAR); - writel(period_cycles, pwm->mmio_base + MX3_PWMPR); - - cr = MX3_PWMCR_PRESCALER(prescale) | - MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN | - MX3_PWMCR_DBGEN | MX3_PWMCR_EN; - - if (cpu_is_mx25()) - cr |= MX3_PWMCR_CLKSRC_IPG; - else - cr |= MX3_PWMCR_CLKSRC_IPG_HIGH; - - writel(cr, pwm->mmio_base + MX3_PWMCR); - } else if (cpu_is_mx1() || cpu_is_mx21()) { - /* The PWM subsystem allows for exact frequencies. However, - * I cannot connect a scope on my device to the PWM line and - * thus cannot provide the program the PWM controller - * exactly. Instead, I'm relying on the fact that the - * Bootloader (u-boot or WinCE+haret) has programmed the PWM - * function group already. So I'll just modify the PWM sample - * register to follow the ratio of duty_ns vs. period_ns - * accordingly. - * - * This is good enough for programming the brightness of - * the LCD backlight. - * - * The real implementation would divide PERCLK[0] first by - * both the prescaler (/1 .. /128) and then by CLKSEL - * (/2 .. /16). - */ - u32 max = readl(pwm->mmio_base + MX1_PWMP); - u32 p = max * duty_ns / period_ns; - writel(max - p, pwm->mmio_base + MX1_PWMS); - } else { - BUG(); - } - - return 0; -} -EXPORT_SYMBOL(pwm_config); - -int pwm_enable(struct pwm_device *pwm) -{ - int rc = 0; - - if (!pwm->clk_enabled) { - rc = clk_prepare_enable(pwm->clk); - if (!rc) - pwm->clk_enabled = 1; - } - return rc; -} -EXPORT_SYMBOL(pwm_enable); - -void pwm_disable(struct pwm_device *pwm) -{ - writel(0, pwm->mmio_base + MX3_PWMCR); - - if (pwm->clk_enabled) { - clk_disable_unprepare(pwm->clk); - pwm->clk_enabled = 0; - } -} -EXPORT_SYMBOL(pwm_disable); - -static DEFINE_MUTEX(pwm_lock); -static LIST_HEAD(pwm_list); - -struct pwm_device *pwm_request(int pwm_id, const char *label) -{ - struct pwm_device *pwm; - int found = 0; - - mutex_lock(&pwm_lock); - - list_for_each_entry(pwm, &pwm_list, node) { - if (pwm->pwm_id == pwm_id) { - found = 1; - break; - } - } - - if (found) { - if (pwm->use_count == 0) { - pwm->use_count++; - pwm->label = label; - } else - pwm = ERR_PTR(-EBUSY); - } else - pwm = ERR_PTR(-ENOENT); - - mutex_unlock(&pwm_lock); - return pwm; -} -EXPORT_SYMBOL(pwm_request); - -void pwm_free(struct pwm_device *pwm) -{ - mutex_lock(&pwm_lock); - - if (pwm->use_count) { - pwm->use_count--; - pwm->label = NULL; - } else - pr_warning("PWM device already freed\n"); - - mutex_unlock(&pwm_lock); -} -EXPORT_SYMBOL(pwm_free); - -static int __devinit mxc_pwm_probe(struct platform_device *pdev) -{ - struct pwm_device *pwm; - struct resource *r; - int ret = 0; - - pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL); - if (pwm == NULL) { - dev_err(&pdev->dev, "failed to allocate memory\n"); - return -ENOMEM; - } - - pwm->clk = clk_get(&pdev->dev, "pwm"); - - if (IS_ERR(pwm->clk)) { - ret = PTR_ERR(pwm->clk); - goto err_free; - } - - pwm->clk_enabled = 0; - - pwm->use_count = 0; - pwm->pwm_id = pdev->id; - pwm->pdev = pdev; - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (r == NULL) { - dev_err(&pdev->dev, "no memory resource defined\n"); - ret = -ENODEV; - goto err_free_clk; - } - - r = request_mem_region(r->start, resource_size(r), pdev->name); - if (r == NULL) { - dev_err(&pdev->dev, "failed to request memory resource\n"); - ret = -EBUSY; - goto err_free_clk; - } - - pwm->mmio_base = ioremap(r->start, resource_size(r)); - if (pwm->mmio_base == NULL) { - dev_err(&pdev->dev, "failed to ioremap() registers\n"); - ret = -ENODEV; - goto err_free_mem; - } - - mutex_lock(&pwm_lock); - list_add_tail(&pwm->node, &pwm_list); - mutex_unlock(&pwm_lock); - - platform_set_drvdata(pdev, pwm); - return 0; - -err_free_mem: - release_mem_region(r->start, resource_size(r)); -err_free_clk: - clk_put(pwm->clk); -err_free: - kfree(pwm); - return ret; -} - -static int __devexit mxc_pwm_remove(struct platform_device *pdev) -{ - struct pwm_device *pwm; - struct resource *r; - - pwm = platform_get_drvdata(pdev); - if (pwm == NULL) - return -ENODEV; - - mutex_lock(&pwm_lock); - list_del(&pwm->node); - mutex_unlock(&pwm_lock); - - iounmap(pwm->mmio_base); - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - release_mem_region(r->start, resource_size(r)); - - clk_put(pwm->clk); - - kfree(pwm); - return 0; -} - -static struct platform_driver mxc_pwm_driver = { - .driver = { - .name = "mxc_pwm", - }, - .probe = mxc_pwm_probe, - .remove = __devexit_p(mxc_pwm_remove), -}; - -static int __init mxc_pwm_init(void) -{ - return platform_driver_register(&mxc_pwm_driver); -} -arch_initcall(mxc_pwm_init); - -static void __exit mxc_pwm_exit(void) -{ - platform_driver_unregister(&mxc_pwm_driver); -} -module_exit(mxc_pwm_exit); - -MODULE_LICENSE("GPL v2"); -MODULE_AUTHOR("Sascha Hauer "); diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 0ef4f3037c4a..a9a971581108 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -18,6 +18,15 @@ config PWM_BFIN To compile this driver as a module, choose M here: the module will be called pwm-bfin. +config PWM_IMX + tristate "i.MX pwm support" + depends on ARCH_MXC + help + Generic PWM framework driver for i.MX. + + To compile this driver as a module, choose M here: the module + will be called pwm-imx. + config PWM_PXA tristate "PXA PWM support" depends on ARCH_PXA diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index e859c513add8..3dd1b8357a0c 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -1,4 +1,5 @@ obj-$(CONFIG_PWM) += core.o obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o +obj-$(CONFIG_PWM_IMX) += pwm-imx.o obj-$(CONFIG_PWM_PXA) += pwm-pxa.o obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o diff --git a/drivers/pwm/pwm-imx.c b/drivers/pwm/pwm-imx.c new file mode 100644 index 000000000000..900d14562de8 --- /dev/null +++ b/drivers/pwm/pwm-imx.c @@ -0,0 +1,267 @@ +/* + * simple driver for PWM (Pulse Width Modulator) controller + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Derived from pxa PWM driver by eric miao + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* i.MX1 and i.MX21 share the same PWM function block: */ + +#define MX1_PWMC 0x00 /* PWM Control Register */ +#define MX1_PWMS 0x04 /* PWM Sample Register */ +#define MX1_PWMP 0x08 /* PWM Period Register */ + + +/* i.MX27, i.MX31, i.MX35 share the same PWM function block: */ + +#define MX3_PWMCR 0x00 /* PWM Control Register */ +#define MX3_PWMSAR 0x0C /* PWM Sample Register */ +#define MX3_PWMPR 0x10 /* PWM Period Register */ +#define MX3_PWMCR_PRESCALER(x) (((x - 1) & 0xFFF) << 4) +#define MX3_PWMCR_DOZEEN (1 << 24) +#define MX3_PWMCR_WAITEN (1 << 23) +#define MX3_PWMCR_DBGEN (1 << 22) +#define MX3_PWMCR_CLKSRC_IPG_HIGH (2 << 16) +#define MX3_PWMCR_CLKSRC_IPG (1 << 16) +#define MX3_PWMCR_EN (1 << 0) + +struct imx_chip { + struct clk *clk; + + int clk_enabled; + void __iomem *mmio_base; + + struct pwm_chip chip; +}; + +#define to_imx_chip(chip) container_of(chip, struct imx_chip, chip) + +static int imx_pwm_config(struct pwm_chip *chip, + struct pwm_device *pwm, int duty_ns, int period_ns) +{ + struct imx_chip *imx = to_imx_chip(chip); + + if (!(cpu_is_mx1() || cpu_is_mx21())) { + unsigned long long c; + unsigned long period_cycles, duty_cycles, prescale; + u32 cr; + + c = clk_get_rate(imx->clk); + c = c * period_ns; + do_div(c, 1000000000); + period_cycles = c; + + prescale = period_cycles / 0x10000 + 1; + + period_cycles /= prescale; + c = (unsigned long long)period_cycles * duty_ns; + do_div(c, period_ns); + duty_cycles = c; + + /* + * according to imx pwm RM, the real period value should be + * PERIOD value in PWMPR plus 2. + */ + if (period_cycles > 2) + period_cycles -= 2; + else + period_cycles = 0; + + writel(duty_cycles, imx->mmio_base + MX3_PWMSAR); + writel(period_cycles, imx->mmio_base + MX3_PWMPR); + + cr = MX3_PWMCR_PRESCALER(prescale) | + MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN | + MX3_PWMCR_DBGEN | MX3_PWMCR_EN; + + if (cpu_is_mx25()) + cr |= MX3_PWMCR_CLKSRC_IPG; + else + cr |= MX3_PWMCR_CLKSRC_IPG_HIGH; + + writel(cr, imx->mmio_base + MX3_PWMCR); + } else if (cpu_is_mx1() || cpu_is_mx21()) { + /* The PWM subsystem allows for exact frequencies. However, + * I cannot connect a scope on my device to the PWM line and + * thus cannot provide the program the PWM controller + * exactly. Instead, I'm relying on the fact that the + * Bootloader (u-boot or WinCE+haret) has programmed the PWM + * function group already. So I'll just modify the PWM sample + * register to follow the ratio of duty_ns vs. period_ns + * accordingly. + * + * This is good enough for programming the brightness of + * the LCD backlight. + * + * The real implementation would divide PERCLK[0] first by + * both the prescaler (/1 .. /128) and then by CLKSEL + * (/2 .. /16). + */ + u32 max = readl(imx->mmio_base + MX1_PWMP); + u32 p = max * duty_ns / period_ns; + writel(max - p, imx->mmio_base + MX1_PWMS); + } else { + BUG(); + } + + return 0; +} + +static int imx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct imx_chip *imx = to_imx_chip(chip); + int rc = 0; + + if (!imx->clk_enabled) { + rc = clk_prepare_enable(imx->clk); + if (!rc) + imx->clk_enabled = 1; + } + return rc; +} + +static void imx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct imx_chip *imx = to_imx_chip(chip); + + writel(0, imx->mmio_base + MX3_PWMCR); + + if (imx->clk_enabled) { + clk_disable_unprepare(imx->clk); + imx->clk_enabled = 0; + } +} + +static struct pwm_ops imx_pwm_ops = { + .enable = imx_pwm_enable, + .disable = imx_pwm_disable, + .config = imx_pwm_config, + .owner = THIS_MODULE, +}; + +static int __devinit imx_pwm_probe(struct platform_device *pdev) +{ + struct imx_chip *imx; + struct resource *r; + int ret = 0; + + imx = kzalloc(sizeof(*imx), GFP_KERNEL); + if (imx == NULL) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + imx->clk = clk_get(&pdev->dev, "pwm"); + + if (IS_ERR(imx->clk)) { + ret = PTR_ERR(imx->clk); + goto err_free; + } + + imx->chip.ops = &imx_pwm_ops; + imx->chip.dev = &pdev->dev; + imx->chip.base = -1; + imx->chip.npwm = 1; + + imx->clk_enabled = 0; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (r == NULL) { + dev_err(&pdev->dev, "no memory resource defined\n"); + ret = -ENODEV; + goto err_free_clk; + } + + r = request_mem_region(r->start, resource_size(r), pdev->name); + if (r == NULL) { + dev_err(&pdev->dev, "failed to request memory resource\n"); + ret = -EBUSY; + goto err_free_clk; + } + + imx->mmio_base = ioremap(r->start, resource_size(r)); + if (imx->mmio_base == NULL) { + dev_err(&pdev->dev, "failed to ioremap() registers\n"); + ret = -ENODEV; + goto err_free_mem; + } + + ret = pwmchip_add(&imx->chip); + if (ret < 0) + goto err_iounmap; + + platform_set_drvdata(pdev, imx); + return 0; + +err_iounmap: + iounmap(imx->mmio_base); +err_free_mem: + release_mem_region(r->start, resource_size(r)); +err_free_clk: + clk_put(imx->clk); +err_free: + kfree(imx); + return ret; +} + +static int __devexit imx_pwm_remove(struct platform_device *pdev) +{ + struct imx_chip *imx; + struct resource *r; + int ret; + + imx = platform_get_drvdata(pdev); + if (imx == NULL) + return -ENODEV; + + ret = pwmchip_remove(&imx->chip); + if (ret) + return ret; + + iounmap(imx->mmio_base); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(r->start, resource_size(r)); + + clk_put(imx->clk); + + kfree(imx); + return 0; +} + +static struct platform_driver imx_pwm_driver = { + .driver = { + .name = "mxc_pwm", + }, + .probe = imx_pwm_probe, + .remove = __devexit_p(imx_pwm_remove), +}; + +static int __init imx_pwm_init(void) +{ + return platform_driver_register(&imx_pwm_driver); +} +arch_initcall(imx_pwm_init); + +static void __exit imx_pwm_exit(void) +{ + platform_driver_unregister(&imx_pwm_driver); +} +module_exit(imx_pwm_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Sascha Hauer "); -- cgit v1.2.3-59-g8ed1b From 215c29d3d0e925189ade522d1ea6052a320d7692 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 15 Mar 2012 10:04:36 +0100 Subject: ARM Samsung: Move s3c pwm driver to pwm framework MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the driver to drivers/pwm/ and convert it to use the framework. Signed-off-by: Sascha Hauer Cc: Ben Dooks Cc: Kukjin Kim [eric@eukrea.com: fix pwmchip_add return code test] Signed-off-by: Eric BĂ©nard Signed-off-by: Thierry Reding --- arch/arm/plat-samsung/Makefile | 4 - arch/arm/plat-samsung/pwm.c | 420 ----------------------------------------- drivers/pwm/Kconfig | 9 + drivers/pwm/Makefile | 1 + drivers/pwm/pwm-samsung.c | 366 +++++++++++++++++++++++++++++++++++ 5 files changed, 376 insertions(+), 424 deletions(-) delete mode 100644 arch/arm/plat-samsung/pwm.c create mode 100644 drivers/pwm/pwm-samsung.c diff --git a/arch/arm/plat-samsung/Makefile b/arch/arm/plat-samsung/Makefile index 860b2db4db15..96854721319c 100644 --- a/arch/arm/plat-samsung/Makefile +++ b/arch/arm/plat-samsung/Makefile @@ -63,7 +63,3 @@ obj-$(CONFIG_S5P_SLEEP) += s5p-sleep.o # PD support obj-$(CONFIG_SAMSUNG_PD) += pd.o - -# PWM support - -obj-$(CONFIG_HAVE_PWM) += pwm.o diff --git a/arch/arm/plat-samsung/pwm.c b/arch/arm/plat-samsung/pwm.c deleted file mode 100644 index c559d8438c70..000000000000 --- a/arch/arm/plat-samsung/pwm.c +++ /dev/null @@ -1,420 +0,0 @@ -/* arch/arm/plat-s3c/pwm.c - * - * Copyright (c) 2007 Ben Dooks - * Copyright (c) 2008 Simtec Electronics - * Ben Dooks , - * - * S3C series PWM device core - * - * 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 - * the Free Software Foundation; either version 2 of the License. -*/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -struct pwm_device { - struct list_head list; - struct platform_device *pdev; - - struct clk *clk_div; - struct clk *clk; - const char *label; - - unsigned int period_ns; - unsigned int duty_ns; - - unsigned char tcon_base; - unsigned char running; - unsigned char use_count; - unsigned char pwm_id; -}; - -#define pwm_dbg(_pwm, msg...) dev_dbg(&(_pwm)->pdev->dev, msg) - -static struct clk *clk_scaler[2]; - -static inline int pwm_is_tdiv(struct pwm_device *pwm) -{ - return clk_get_parent(pwm->clk) == pwm->clk_div; -} - -static DEFINE_MUTEX(pwm_lock); -static LIST_HEAD(pwm_list); - -struct pwm_device *pwm_request(int pwm_id, const char *label) -{ - struct pwm_device *pwm; - int found = 0; - - mutex_lock(&pwm_lock); - - list_for_each_entry(pwm, &pwm_list, list) { - if (pwm->pwm_id == pwm_id) { - found = 1; - break; - } - } - - if (found) { - if (pwm->use_count == 0) { - pwm->use_count = 1; - pwm->label = label; - } else - pwm = ERR_PTR(-EBUSY); - } else - pwm = ERR_PTR(-ENOENT); - - mutex_unlock(&pwm_lock); - return pwm; -} - -EXPORT_SYMBOL(pwm_request); - - -void pwm_free(struct pwm_device *pwm) -{ - mutex_lock(&pwm_lock); - - if (pwm->use_count) { - pwm->use_count--; - pwm->label = NULL; - } else - printk(KERN_ERR "PWM%d device already freed\n", pwm->pwm_id); - - mutex_unlock(&pwm_lock); -} - -EXPORT_SYMBOL(pwm_free); - -#define pwm_tcon_start(pwm) (1 << (pwm->tcon_base + 0)) -#define pwm_tcon_invert(pwm) (1 << (pwm->tcon_base + 2)) -#define pwm_tcon_autoreload(pwm) (1 << (pwm->tcon_base + 3)) -#define pwm_tcon_manulupdate(pwm) (1 << (pwm->tcon_base + 1)) - -int pwm_enable(struct pwm_device *pwm) -{ - unsigned long flags; - unsigned long tcon; - - local_irq_save(flags); - - tcon = __raw_readl(S3C2410_TCON); - tcon |= pwm_tcon_start(pwm); - __raw_writel(tcon, S3C2410_TCON); - - local_irq_restore(flags); - - pwm->running = 1; - return 0; -} - -EXPORT_SYMBOL(pwm_enable); - -void pwm_disable(struct pwm_device *pwm) -{ - unsigned long flags; - unsigned long tcon; - - local_irq_save(flags); - - tcon = __raw_readl(S3C2410_TCON); - tcon &= ~pwm_tcon_start(pwm); - __raw_writel(tcon, S3C2410_TCON); - - local_irq_restore(flags); - - pwm->running = 0; -} - -EXPORT_SYMBOL(pwm_disable); - -static unsigned long pwm_calc_tin(struct pwm_device *pwm, unsigned long freq) -{ - unsigned long tin_parent_rate; - unsigned int div; - - tin_parent_rate = clk_get_rate(clk_get_parent(pwm->clk_div)); - pwm_dbg(pwm, "tin parent at %lu\n", tin_parent_rate); - - for (div = 2; div <= 16; div *= 2) { - if ((tin_parent_rate / (div << 16)) < freq) - return tin_parent_rate / div; - } - - return tin_parent_rate / 16; -} - -#define NS_IN_HZ (1000000000UL) - -int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) -{ - unsigned long tin_rate; - unsigned long tin_ns; - unsigned long period; - unsigned long flags; - unsigned long tcon; - unsigned long tcnt; - long tcmp; - - /* We currently avoid using 64bit arithmetic by using the - * fact that anything faster than 1Hz is easily representable - * by 32bits. */ - - if (period_ns > NS_IN_HZ || duty_ns > NS_IN_HZ) - return -ERANGE; - - if (duty_ns > period_ns) - return -EINVAL; - - if (period_ns == pwm->period_ns && - duty_ns == pwm->duty_ns) - return 0; - - /* The TCMP and TCNT can be read without a lock, they're not - * shared between the timers. */ - - tcmp = __raw_readl(S3C2410_TCMPB(pwm->pwm_id)); - tcnt = __raw_readl(S3C2410_TCNTB(pwm->pwm_id)); - - period = NS_IN_HZ / period_ns; - - pwm_dbg(pwm, "duty_ns=%d, period_ns=%d (%lu)\n", - duty_ns, period_ns, period); - - /* Check to see if we are changing the clock rate of the PWM */ - - if (pwm->period_ns != period_ns) { - if (pwm_is_tdiv(pwm)) { - tin_rate = pwm_calc_tin(pwm, period); - clk_set_rate(pwm->clk_div, tin_rate); - } else - tin_rate = clk_get_rate(pwm->clk); - - pwm->period_ns = period_ns; - - pwm_dbg(pwm, "tin_rate=%lu\n", tin_rate); - - tin_ns = NS_IN_HZ / tin_rate; - tcnt = period_ns / tin_ns; - } else - tin_ns = NS_IN_HZ / clk_get_rate(pwm->clk); - - /* Note, counters count down */ - - tcmp = duty_ns / tin_ns; - tcmp = tcnt - tcmp; - /* the pwm hw only checks the compare register after a decrement, - so the pin never toggles if tcmp = tcnt */ - if (tcmp == tcnt) - tcmp--; - - pwm_dbg(pwm, "tin_ns=%lu, tcmp=%ld/%lu\n", tin_ns, tcmp, tcnt); - - if (tcmp < 0) - tcmp = 0; - - /* Update the PWM register block. */ - - local_irq_save(flags); - - __raw_writel(tcmp, S3C2410_TCMPB(pwm->pwm_id)); - __raw_writel(tcnt, S3C2410_TCNTB(pwm->pwm_id)); - - tcon = __raw_readl(S3C2410_TCON); - tcon |= pwm_tcon_manulupdate(pwm); - tcon |= pwm_tcon_autoreload(pwm); - __raw_writel(tcon, S3C2410_TCON); - - tcon &= ~pwm_tcon_manulupdate(pwm); - __raw_writel(tcon, S3C2410_TCON); - - local_irq_restore(flags); - - return 0; -} - -EXPORT_SYMBOL(pwm_config); - -static int pwm_register(struct pwm_device *pwm) -{ - pwm->duty_ns = -1; - pwm->period_ns = -1; - - mutex_lock(&pwm_lock); - list_add_tail(&pwm->list, &pwm_list); - mutex_unlock(&pwm_lock); - - return 0; -} - -static int s3c_pwm_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct pwm_device *pwm; - unsigned long flags; - unsigned long tcon; - unsigned int id = pdev->id; - int ret; - - if (id == 4) { - dev_err(dev, "TIMER4 is currently not supported\n"); - return -ENXIO; - } - - pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL); - if (pwm == NULL) { - dev_err(dev, "failed to allocate pwm_device\n"); - return -ENOMEM; - } - - pwm->pdev = pdev; - pwm->pwm_id = id; - - /* calculate base of control bits in TCON */ - pwm->tcon_base = id == 0 ? 0 : (id * 4) + 4; - - pwm->clk = clk_get(dev, "pwm-tin"); - if (IS_ERR(pwm->clk)) { - dev_err(dev, "failed to get pwm tin clk\n"); - ret = PTR_ERR(pwm->clk); - goto err_alloc; - } - - pwm->clk_div = clk_get(dev, "pwm-tdiv"); - if (IS_ERR(pwm->clk_div)) { - dev_err(dev, "failed to get pwm tdiv clk\n"); - ret = PTR_ERR(pwm->clk_div); - goto err_clk_tin; - } - - clk_enable(pwm->clk); - clk_enable(pwm->clk_div); - - local_irq_save(flags); - - tcon = __raw_readl(S3C2410_TCON); - tcon |= pwm_tcon_invert(pwm); - __raw_writel(tcon, S3C2410_TCON); - - local_irq_restore(flags); - - - ret = pwm_register(pwm); - if (ret) { - dev_err(dev, "failed to register pwm\n"); - goto err_clk_tdiv; - } - - pwm_dbg(pwm, "config bits %02x\n", - (__raw_readl(S3C2410_TCON) >> pwm->tcon_base) & 0x0f); - - dev_info(dev, "tin at %lu, tdiv at %lu, tin=%sclk, base %d\n", - clk_get_rate(pwm->clk), - clk_get_rate(pwm->clk_div), - pwm_is_tdiv(pwm) ? "div" : "ext", pwm->tcon_base); - - platform_set_drvdata(pdev, pwm); - return 0; - - err_clk_tdiv: - clk_disable(pwm->clk_div); - clk_disable(pwm->clk); - clk_put(pwm->clk_div); - - err_clk_tin: - clk_put(pwm->clk); - - err_alloc: - kfree(pwm); - return ret; -} - -static int __devexit s3c_pwm_remove(struct platform_device *pdev) -{ - struct pwm_device *pwm = platform_get_drvdata(pdev); - - clk_disable(pwm->clk_div); - clk_disable(pwm->clk); - clk_put(pwm->clk_div); - clk_put(pwm->clk); - kfree(pwm); - - return 0; -} - -#ifdef CONFIG_PM -static int s3c_pwm_suspend(struct platform_device *pdev, pm_message_t state) -{ - struct pwm_device *pwm = platform_get_drvdata(pdev); - - /* No one preserve these values during suspend so reset them - * Otherwise driver leaves PWM unconfigured if same values - * passed to pwm_config - */ - pwm->period_ns = 0; - pwm->duty_ns = 0; - - return 0; -} - -static int s3c_pwm_resume(struct platform_device *pdev) -{ - struct pwm_device *pwm = platform_get_drvdata(pdev); - unsigned long tcon; - - /* Restore invertion */ - tcon = __raw_readl(S3C2410_TCON); - tcon |= pwm_tcon_invert(pwm); - __raw_writel(tcon, S3C2410_TCON); - - return 0; -} - -#else -#define s3c_pwm_suspend NULL -#define s3c_pwm_resume NULL -#endif - -static struct platform_driver s3c_pwm_driver = { - .driver = { - .name = "s3c24xx-pwm", - .owner = THIS_MODULE, - }, - .probe = s3c_pwm_probe, - .remove = __devexit_p(s3c_pwm_remove), - .suspend = s3c_pwm_suspend, - .resume = s3c_pwm_resume, -}; - -static int __init pwm_init(void) -{ - int ret; - - clk_scaler[0] = clk_get(NULL, "pwm-scaler0"); - clk_scaler[1] = clk_get(NULL, "pwm-scaler1"); - - if (IS_ERR(clk_scaler[0]) || IS_ERR(clk_scaler[1])) { - printk(KERN_ERR "%s: failed to get scaler clocks\n", __func__); - return -EINVAL; - } - - ret = platform_driver_register(&s3c_pwm_driver); - if (ret) - printk(KERN_ERR "%s: failed to add pwm driver\n", __func__); - - return ret; -} - -arch_initcall(pwm_init); diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index a9a971581108..0d9aa92636c3 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -36,6 +36,15 @@ config PWM_PXA To compile this driver as a module, choose M here: the module will be called pwm-pxa. +config PWM_SAMSUNG + tristate "Samsung pwm support" + depends on PLAT_SAMSUNG + help + Generic PWM framework driver for Samsung. + + To compile this driver as a module, choose M here: the module + will be called pwm-samsung. + config PWM_TEGRA tristate "NVIDIA Tegra PWM support" depends on ARCH_TEGRA diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 3dd1b8357a0c..a1d87b173312 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -2,4 +2,5 @@ obj-$(CONFIG_PWM) += core.o obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o obj-$(CONFIG_PWM_IMX) += pwm-imx.o obj-$(CONFIG_PWM_PXA) += pwm-pxa.o +obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o diff --git a/drivers/pwm/pwm-samsung.c b/drivers/pwm/pwm-samsung.c new file mode 100644 index 000000000000..c40c37e968e6 --- /dev/null +++ b/drivers/pwm/pwm-samsung.c @@ -0,0 +1,366 @@ +/* arch/arm/plat-s3c/pwm.c + * + * Copyright (c) 2007 Ben Dooks + * Copyright (c) 2008 Simtec Electronics + * Ben Dooks , + * + * S3C series PWM device core + * + * 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 + * the Free Software Foundation; either version 2 of the License. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +struct s3c_chip { + struct platform_device *pdev; + + struct clk *clk_div; + struct clk *clk; + const char *label; + + unsigned int period_ns; + unsigned int duty_ns; + + unsigned char tcon_base; + unsigned char pwm_id; + struct pwm_chip chip; +}; + +#define to_s3c_chip(chip) container_of(chip, struct s3c_chip, chip) + +#define pwm_dbg(_pwm, msg...) dev_dbg(&(_pwm)->pdev->dev, msg) + +static struct clk *clk_scaler[2]; + +static inline int pwm_is_tdiv(struct s3c_chip *chip) +{ + return clk_get_parent(chip->clk) == chip->clk_div; +} + +#define pwm_tcon_start(pwm) (1 << (pwm->tcon_base + 0)) +#define pwm_tcon_invert(pwm) (1 << (pwm->tcon_base + 2)) +#define pwm_tcon_autoreload(pwm) (1 << (pwm->tcon_base + 3)) +#define pwm_tcon_manulupdate(pwm) (1 << (pwm->tcon_base + 1)) + +static int s3c_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct s3c_chip *s3c = to_s3c_chip(chip); + unsigned long flags; + unsigned long tcon; + + local_irq_save(flags); + + tcon = __raw_readl(S3C2410_TCON); + tcon |= pwm_tcon_start(s3c); + __raw_writel(tcon, S3C2410_TCON); + + local_irq_restore(flags); + + return 0; +} + +static void s3c_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct s3c_chip *s3c = to_s3c_chip(chip); + unsigned long flags; + unsigned long tcon; + + local_irq_save(flags); + + tcon = __raw_readl(S3C2410_TCON); + tcon &= ~pwm_tcon_start(s3c); + __raw_writel(tcon, S3C2410_TCON); + + local_irq_restore(flags); +} + +static unsigned long pwm_calc_tin(struct s3c_chip *s3c, unsigned long freq) +{ + unsigned long tin_parent_rate; + unsigned int div; + + tin_parent_rate = clk_get_rate(clk_get_parent(s3c->clk_div)); + pwm_dbg(s3c, "tin parent at %lu\n", tin_parent_rate); + + for (div = 2; div <= 16; div *= 2) { + if ((tin_parent_rate / (div << 16)) < freq) + return tin_parent_rate / div; + } + + return tin_parent_rate / 16; +} + +#define NS_IN_HZ (1000000000UL) + +static int s3c_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct s3c_chip *s3c = to_s3c_chip(chip); + unsigned long tin_rate; + unsigned long tin_ns; + unsigned long period; + unsigned long flags; + unsigned long tcon; + unsigned long tcnt; + long tcmp; + + /* We currently avoid using 64bit arithmetic by using the + * fact that anything faster than 1Hz is easily representable + * by 32bits. */ + + if (period_ns > NS_IN_HZ || duty_ns > NS_IN_HZ) + return -ERANGE; + + if (duty_ns > period_ns) + return -EINVAL; + + if (period_ns == s3c->period_ns && + duty_ns == s3c->duty_ns) + return 0; + + /* The TCMP and TCNT can be read without a lock, they're not + * shared between the timers. */ + + tcmp = __raw_readl(S3C2410_TCMPB(s3c->pwm_id)); + tcnt = __raw_readl(S3C2410_TCNTB(s3c->pwm_id)); + + period = NS_IN_HZ / period_ns; + + pwm_dbg(s3c, "duty_ns=%d, period_ns=%d (%lu)\n", + duty_ns, period_ns, period); + + /* Check to see if we are changing the clock rate of the PWM */ + + if (s3c->period_ns != period_ns) { + if (pwm_is_tdiv(s3c)) { + tin_rate = pwm_calc_tin(s3c, period); + clk_set_rate(s3c->clk_div, tin_rate); + } else + tin_rate = clk_get_rate(s3c->clk); + + s3c->period_ns = period_ns; + + pwm_dbg(s3c, "tin_rate=%lu\n", tin_rate); + + tin_ns = NS_IN_HZ / tin_rate; + tcnt = period_ns / tin_ns; + } else + tin_ns = NS_IN_HZ / clk_get_rate(s3c->clk); + + /* Note, counters count down */ + + tcmp = duty_ns / tin_ns; + tcmp = tcnt - tcmp; + /* the pwm hw only checks the compare register after a decrement, + so the pin never toggles if tcmp = tcnt */ + if (tcmp == tcnt) + tcmp--; + + pwm_dbg(s3c, "tin_ns=%lu, tcmp=%ld/%lu\n", tin_ns, tcmp, tcnt); + + if (tcmp < 0) + tcmp = 0; + + /* Update the PWM register block. */ + + local_irq_save(flags); + + __raw_writel(tcmp, S3C2410_TCMPB(s3c->pwm_id)); + __raw_writel(tcnt, S3C2410_TCNTB(s3c->pwm_id)); + + tcon = __raw_readl(S3C2410_TCON); + tcon |= pwm_tcon_manulupdate(s3c); + tcon |= pwm_tcon_autoreload(s3c); + __raw_writel(tcon, S3C2410_TCON); + + tcon &= ~pwm_tcon_manulupdate(s3c); + __raw_writel(tcon, S3C2410_TCON); + + local_irq_restore(flags); + + return 0; +} + +static struct pwm_ops s3c_pwm_ops = { + .enable = s3c_pwm_enable, + .disable = s3c_pwm_disable, + .config = s3c_pwm_config, + .owner = THIS_MODULE, +}; + +static int s3c_pwm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct s3c_chip *s3c; + unsigned long flags; + unsigned long tcon; + unsigned int id = pdev->id; + int ret; + + if (id == 4) { + dev_err(dev, "TIMER4 is currently not supported\n"); + return -ENXIO; + } + + s3c = kzalloc(sizeof(*s3c), GFP_KERNEL); + if (s3c == NULL) { + dev_err(dev, "failed to allocate pwm_device\n"); + return -ENOMEM; + } + + /* calculate base of control bits in TCON */ + s3c->tcon_base = id == 0 ? 0 : (id * 4) + 4; + s3c->chip.ops = &s3c_pwm_ops; + s3c->chip.base = -1; + s3c->chip.npwm = 1; + + s3c->clk = clk_get(dev, "pwm-tin"); + if (IS_ERR(s3c->clk)) { + dev_err(dev, "failed to get pwm tin clk\n"); + ret = PTR_ERR(s3c->clk); + goto err_alloc; + } + + s3c->clk_div = clk_get(dev, "pwm-tdiv"); + if (IS_ERR(s3c->clk_div)) { + dev_err(dev, "failed to get pwm tdiv clk\n"); + ret = PTR_ERR(s3c->clk_div); + goto err_clk_tin; + } + + clk_enable(s3c->clk); + clk_enable(s3c->clk_div); + + local_irq_save(flags); + + tcon = __raw_readl(S3C2410_TCON); + tcon |= pwm_tcon_invert(s3c); + __raw_writel(tcon, S3C2410_TCON); + + local_irq_restore(flags); + + ret = pwmchip_add(&s3c->chip); + if (ret < 0) { + dev_err(dev, "failed to register pwm\n"); + goto err_clk_tdiv; + } + + pwm_dbg(s3c, "config bits %02x\n", + (__raw_readl(S3C2410_TCON) >> s3c->tcon_base) & 0x0f); + + dev_info(dev, "tin at %lu, tdiv at %lu, tin=%sclk, base %d\n", + clk_get_rate(s3c->clk), + clk_get_rate(s3c->clk_div), + pwm_is_tdiv(s3c) ? "div" : "ext", s3c->tcon_base); + + platform_set_drvdata(pdev, s3c); + return 0; + + err_clk_tdiv: + clk_disable(s3c->clk_div); + clk_disable(s3c->clk); + clk_put(s3c->clk_div); + + err_clk_tin: + clk_put(s3c->clk); + + err_alloc: + kfree(s3c); + return ret; +} + +static int __devexit s3c_pwm_remove(struct platform_device *pdev) +{ + struct s3c_chip *s3c = platform_get_drvdata(pdev); + int err; + + err = pwmchip_remove(&s3c->chip); + if (err < 0) + return err; + + clk_disable(s3c->clk_div); + clk_disable(s3c->clk); + clk_put(s3c->clk_div); + clk_put(s3c->clk); + kfree(s3c); + + return 0; +} + +#ifdef CONFIG_PM +static int s3c_pwm_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct s3c_chip *s3c = platform_get_drvdata(pdev); + + /* No one preserve these values during suspend so reset them + * Otherwise driver leaves PWM unconfigured if same values + * passed to pwm_config + */ + s3c->period_ns = 0; + s3c->duty_ns = 0; + + return 0; +} + +static int s3c_pwm_resume(struct platform_device *pdev) +{ + struct s3c_chip *s3c = platform_get_drvdata(pdev); + unsigned long tcon; + + /* Restore invertion */ + tcon = __raw_readl(S3C2410_TCON); + tcon |= pwm_tcon_invert(s3c); + __raw_writel(tcon, S3C2410_TCON); + + return 0; +} + +#else +#define s3c_pwm_suspend NULL +#define s3c_pwm_resume NULL +#endif + +static struct platform_driver s3c_pwm_driver = { + .driver = { + .name = "s3c24xx-pwm", + .owner = THIS_MODULE, + }, + .probe = s3c_pwm_probe, + .remove = __devexit_p(s3c_pwm_remove), + .suspend = s3c_pwm_suspend, + .resume = s3c_pwm_resume, +}; + +static int __init pwm_init(void) +{ + int ret; + + clk_scaler[0] = clk_get(NULL, "pwm-scaler0"); + clk_scaler[1] = clk_get(NULL, "pwm-scaler1"); + + if (IS_ERR(clk_scaler[0]) || IS_ERR(clk_scaler[1])) { + printk(KERN_ERR "%s: failed to get scaler clocks\n", __func__); + return -EINVAL; + } + + ret = platform_driver_register(&s3c_pwm_driver); + if (ret) + printk(KERN_ERR "%s: failed to add pwm driver\n", __func__); + + return ret; +} + +arch_initcall(pwm_init); -- cgit v1.2.3-59-g8ed1b From a245ccebb4aad9fae60597d5cbad158c0de4483e Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 15 Mar 2012 10:04:37 +0100 Subject: ARM vt8500: Move vt8500 pwm driver to pwm framework Move the driver to drivers/pwm/ and convert it to use the framework. Signed-off-by: Sascha Hauer Cc: Alexey Charkov Signed-off-by: Thierry Reding --- arch/arm/Kconfig | 1 - arch/arm/mach-vt8500/Makefile | 2 - arch/arm/mach-vt8500/pwm.c | 265 ------------------------------------------ drivers/pwm/Kconfig | 9 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-vt8500.c | 208 +++++++++++++++++++++++++++++++++ 6 files changed, 218 insertions(+), 268 deletions(-) delete mode 100644 arch/arm/mach-vt8500/pwm.c create mode 100644 drivers/pwm/pwm-vt8500.c diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 84449dd8f031..8da8f82f6fe3 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -962,7 +962,6 @@ config ARCH_VT8500 select ARCH_HAS_CPUFREQ select GENERIC_CLOCKEVENTS select ARCH_REQUIRE_GPIOLIB - select HAVE_PWM help Support for VIA/WonderMedia VT8500/WM85xx System-on-Chip. diff --git a/arch/arm/mach-vt8500/Makefile b/arch/arm/mach-vt8500/Makefile index 81aedb7c893c..8df9e4abab6d 100644 --- a/arch/arm/mach-vt8500/Makefile +++ b/arch/arm/mach-vt8500/Makefile @@ -5,5 +5,3 @@ obj-$(CONFIG_VTWM_VERSION_WM8505) += devices-wm8505.o obj-$(CONFIG_MACH_BV07) += bv07.o obj-$(CONFIG_MACH_WM8505_7IN_NETBOOK) += wm8505_7in.o - -obj-$(CONFIG_HAVE_PWM) += pwm.o diff --git a/arch/arm/mach-vt8500/pwm.c b/arch/arm/mach-vt8500/pwm.c deleted file mode 100644 index 8ad825e93592..000000000000 --- a/arch/arm/mach-vt8500/pwm.c +++ /dev/null @@ -1,265 +0,0 @@ -/* - * arch/arm/mach-vt8500/pwm.c - * - * Copyright (C) 2010 Alexey Charkov - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#define VT8500_NR_PWMS 4 - -static DEFINE_MUTEX(pwm_lock); -static LIST_HEAD(pwm_list); - -struct pwm_device { - struct list_head node; - struct platform_device *pdev; - - const char *label; - - void __iomem *regbase; - - unsigned int use_count; - unsigned int pwm_id; -}; - -#define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t) -static inline void pwm_busy_wait(void __iomem *reg, u8 bitmask) -{ - int loops = msecs_to_loops(10); - while ((readb(reg) & bitmask) && --loops) - cpu_relax(); - - if (unlikely(!loops)) - pr_warning("Waiting for status bits 0x%x to clear timed out\n", - bitmask); -} - -int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) -{ - unsigned long long c; - unsigned long period_cycles, prescale, pv, dc; - - if (pwm == NULL || period_ns == 0 || duty_ns > period_ns) - return -EINVAL; - - c = 25000000/2; /* wild guess --- need to implement clocks */ - c = c * period_ns; - do_div(c, 1000000000); - period_cycles = c; - - if (period_cycles < 1) - period_cycles = 1; - prescale = (period_cycles - 1) / 4096; - pv = period_cycles / (prescale + 1) - 1; - if (pv > 4095) - pv = 4095; - - if (prescale > 1023) - return -EINVAL; - - c = (unsigned long long)pv * duty_ns; - do_div(c, period_ns); - dc = c; - - pwm_busy_wait(pwm->regbase + 0x40 + pwm->pwm_id, (1 << 1)); - writel(prescale, pwm->regbase + 0x4 + (pwm->pwm_id << 4)); - - pwm_busy_wait(pwm->regbase + 0x40 + pwm->pwm_id, (1 << 2)); - writel(pv, pwm->regbase + 0x8 + (pwm->pwm_id << 4)); - - pwm_busy_wait(pwm->regbase + 0x40 + pwm->pwm_id, (1 << 3)); - writel(dc, pwm->regbase + 0xc + (pwm->pwm_id << 4)); - - return 0; -} -EXPORT_SYMBOL(pwm_config); - -int pwm_enable(struct pwm_device *pwm) -{ - pwm_busy_wait(pwm->regbase + 0x40 + pwm->pwm_id, (1 << 0)); - writel(5, pwm->regbase + (pwm->pwm_id << 4)); - return 0; -} -EXPORT_SYMBOL(pwm_enable); - -void pwm_disable(struct pwm_device *pwm) -{ - pwm_busy_wait(pwm->regbase + 0x40 + pwm->pwm_id, (1 << 0)); - writel(0, pwm->regbase + (pwm->pwm_id << 4)); -} -EXPORT_SYMBOL(pwm_disable); - -struct pwm_device *pwm_request(int pwm_id, const char *label) -{ - struct pwm_device *pwm; - int found = 0; - - mutex_lock(&pwm_lock); - - list_for_each_entry(pwm, &pwm_list, node) { - if (pwm->pwm_id == pwm_id) { - found = 1; - break; - } - } - - if (found) { - if (pwm->use_count == 0) { - pwm->use_count++; - pwm->label = label; - } else { - pwm = ERR_PTR(-EBUSY); - } - } else { - pwm = ERR_PTR(-ENOENT); - } - - mutex_unlock(&pwm_lock); - return pwm; -} -EXPORT_SYMBOL(pwm_request); - -void pwm_free(struct pwm_device *pwm) -{ - mutex_lock(&pwm_lock); - - if (pwm->use_count) { - pwm->use_count--; - pwm->label = NULL; - } else { - pr_warning("PWM device already freed\n"); - } - - mutex_unlock(&pwm_lock); -} -EXPORT_SYMBOL(pwm_free); - -static inline void __add_pwm(struct pwm_device *pwm) -{ - mutex_lock(&pwm_lock); - list_add_tail(&pwm->node, &pwm_list); - mutex_unlock(&pwm_lock); -} - -static int __devinit pwm_probe(struct platform_device *pdev) -{ - struct pwm_device *pwms; - struct resource *r; - int ret = 0; - int i; - - pwms = kzalloc(sizeof(struct pwm_device) * VT8500_NR_PWMS, GFP_KERNEL); - if (pwms == NULL) { - dev_err(&pdev->dev, "failed to allocate memory\n"); - return -ENOMEM; - } - - for (i = 0; i < VT8500_NR_PWMS; i++) { - pwms[i].use_count = 0; - pwms[i].pwm_id = i; - pwms[i].pdev = pdev; - } - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (r == NULL) { - dev_err(&pdev->dev, "no memory resource defined\n"); - ret = -ENODEV; - goto err_free; - } - - r = request_mem_region(r->start, resource_size(r), pdev->name); - if (r == NULL) { - dev_err(&pdev->dev, "failed to request memory resource\n"); - ret = -EBUSY; - goto err_free; - } - - pwms[0].regbase = ioremap(r->start, resource_size(r)); - if (pwms[0].regbase == NULL) { - dev_err(&pdev->dev, "failed to ioremap() registers\n"); - ret = -ENODEV; - goto err_free_mem; - } - - for (i = 1; i < VT8500_NR_PWMS; i++) - pwms[i].regbase = pwms[0].regbase; - - for (i = 0; i < VT8500_NR_PWMS; i++) - __add_pwm(&pwms[i]); - - platform_set_drvdata(pdev, pwms); - return 0; - -err_free_mem: - release_mem_region(r->start, resource_size(r)); -err_free: - kfree(pwms); - return ret; -} - -static int __devexit pwm_remove(struct platform_device *pdev) -{ - struct pwm_device *pwms; - struct resource *r; - int i; - - pwms = platform_get_drvdata(pdev); - if (pwms == NULL) - return -ENODEV; - - mutex_lock(&pwm_lock); - - for (i = 0; i < VT8500_NR_PWMS; i++) - list_del(&pwms[i].node); - mutex_unlock(&pwm_lock); - - iounmap(pwms[0].regbase); - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - release_mem_region(r->start, resource_size(r)); - - kfree(pwms); - return 0; -} - -static struct platform_driver pwm_driver = { - .driver = { - .name = "vt8500-pwm", - .owner = THIS_MODULE, - }, - .probe = pwm_probe, - .remove = __devexit_p(pwm_remove), -}; - -static int __init pwm_init(void) -{ - return platform_driver_register(&pwm_driver); -} -arch_initcall(pwm_init); - -static void __exit pwm_exit(void) -{ - platform_driver_unregister(&pwm_driver); -} -module_exit(pwm_exit); - -MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 0d9aa92636c3..a93feffbc36a 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -55,4 +55,13 @@ config PWM_TEGRA To compile this driver as a module, choose M here: the module will be called pwm-tegra. +config PWM_VT8500 + tristate "vt8500 pwm support" + depends on ARCH_VT8500 + help + Generic PWM framework driver for vt8500. + + To compile this driver as a module, choose M here: the module + will be called pwm-vt8500. + endif diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index a1d87b173312..b7c0fcf4de16 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_PWM_IMX) += pwm-imx.o obj-$(CONFIG_PWM_PXA) += pwm-pxa.o obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o +obj-$(CONFIG_PWM_VT8500) += pwm-vt8500.o diff --git a/drivers/pwm/pwm-vt8500.c b/drivers/pwm/pwm-vt8500.c new file mode 100644 index 000000000000..3db0746f7200 --- /dev/null +++ b/drivers/pwm/pwm-vt8500.c @@ -0,0 +1,208 @@ +/* + * arch/arm/mach-vt8500/pwm.c + * + * Copyright (C) 2010 Alexey Charkov + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define VT8500_NR_PWMS 4 + +struct vt8500_chip { + struct pwm_chip chip; + void __iomem *base; +}; + +#define to_vt8500_chip(chip) container_of(chip, struct vt8500_chip, chip) + +#define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t) +static inline void pwm_busy_wait(void __iomem *reg, u8 bitmask) +{ + int loops = msecs_to_loops(10); + while ((readb(reg) & bitmask) && --loops) + cpu_relax(); + + if (unlikely(!loops)) + pr_warning("Waiting for status bits 0x%x to clear timed out\n", + bitmask); +} + +static int vt8500_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct vt8500_chip *vt8500 = to_vt8500_chip(chip); + unsigned long long c; + unsigned long period_cycles, prescale, pv, dc; + + c = 25000000/2; /* wild guess --- need to implement clocks */ + c = c * period_ns; + do_div(c, 1000000000); + period_cycles = c; + + if (period_cycles < 1) + period_cycles = 1; + prescale = (period_cycles - 1) / 4096; + pv = period_cycles / (prescale + 1) - 1; + if (pv > 4095) + pv = 4095; + + if (prescale > 1023) + return -EINVAL; + + c = (unsigned long long)pv * duty_ns; + do_div(c, period_ns); + dc = c; + + pwm_busy_wait(vt8500->base + 0x40 + pwm->hwpwm, (1 << 1)); + writel(prescale, vt8500->base + 0x4 + (pwm->hwpwm << 4)); + + pwm_busy_wait(vt8500->base + 0x40 + pwm->hwpwm, (1 << 2)); + writel(pv, vt8500->base + 0x8 + (pwm->hwpwm << 4)); + + pwm_busy_wait(vt8500->base + 0x40 + pwm->hwpwm, (1 << 3)); + writel(dc, vt8500->base + 0xc + (pwm->hwpwm << 4)); + + return 0; +} + +static int vt8500_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct vt8500_chip *vt8500 = to_vt8500_chip(chip); + + pwm_busy_wait(vt8500->base + 0x40 + pwm->hwpwm, (1 << 0)); + writel(5, vt8500->base + (pwm->hwpwm << 4)); + return 0; +} + +static void vt8500_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct vt8500_chip *vt8500 = to_vt8500_chip(chip); + + pwm_busy_wait(vt8500->base + 0x40 + pwm->hwpwm, (1 << 0)); + writel(0, vt8500->base + (pwm->hwpwm << 4)); +} + +static struct pwm_ops vt8500_pwm_ops = { + .enable = vt8500_pwm_enable, + .disable = vt8500_pwm_disable, + .config = vt8500_pwm_config, + .owner = THIS_MODULE, +}; + +static int __devinit pwm_probe(struct platform_device *pdev) +{ + struct vt8500_chip *chip; + struct resource *r; + int ret; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (chip == NULL) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + chip->chip.dev = &pdev->dev; + chip->chip.ops = &vt8500_pwm_ops; + chip->chip.base = -1; + chip->chip.npwm = VT8500_NR_PWMS; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (r == NULL) { + dev_err(&pdev->dev, "no memory resource defined\n"); + ret = -ENODEV; + goto err_free; + } + + r = request_mem_region(r->start, resource_size(r), pdev->name); + if (r == NULL) { + dev_err(&pdev->dev, "failed to request memory resource\n"); + ret = -EBUSY; + goto err_free; + } + + chip->base = ioremap(r->start, resource_size(r)); + if (chip->base == NULL) { + dev_err(&pdev->dev, "failed to ioremap() registers\n"); + ret = -ENODEV; + goto err_free_mem; + } + + ret = pwmchip_add(&chip->chip); + if (ret < 0) + goto err_unmap; + + platform_set_drvdata(pdev, chip); + return ret; + +err_unmap: + iounmap(chip->base); +err_free_mem: + release_mem_region(r->start, resource_size(r)); +err_free: + kfree(chip); + return ret; +} + +static int __devexit pwm_remove(struct platform_device *pdev) +{ + struct vt8500_chip *chip; + struct resource *r; + int err; + + chip = platform_get_drvdata(pdev); + if (chip == NULL) + return -ENODEV; + + err = pwmchip_remove(&chip->chip); + if (err < 0) + return err; + + iounmap(chip->base); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(r->start, resource_size(r)); + + kfree(chip); + return 0; +} + +static struct platform_driver pwm_driver = { + .driver = { + .name = "vt8500-pwm", + .owner = THIS_MODULE, + }, + .probe = pwm_probe, + .remove = __devexit_p(pwm_remove), +}; + +static int __init pwm_init(void) +{ + return platform_driver_register(&pwm_driver); +} +arch_initcall(pwm_init); + +static void __exit pwm_exit(void) +{ + platform_driver_unregister(&pwm_driver); +} +module_exit(pwm_exit); + +MODULE_LICENSE("GPL"); -- cgit v1.2.3-59-g8ed1b From 4dce82c1e84007d38747533673c2289bfc6497d2 Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Wed, 4 Apr 2012 10:50:52 +0800 Subject: pwm: add pwm-mxs support Add generic PWM framework driver (DT only) for Freescale MXS. Signed-off-by: Sascha Hauer Signed-off-by: Shawn Guo Signed-off-by: Thierry Reding --- Documentation/devicetree/bindings/pwm/mxs-pwm.txt | 17 ++ drivers/pwm/Kconfig | 9 + drivers/pwm/Makefile | 1 + drivers/pwm/pwm-mxs.c | 207 ++++++++++++++++++++++ 4 files changed, 234 insertions(+) create mode 100644 Documentation/devicetree/bindings/pwm/mxs-pwm.txt create mode 100644 drivers/pwm/pwm-mxs.c diff --git a/Documentation/devicetree/bindings/pwm/mxs-pwm.txt b/Documentation/devicetree/bindings/pwm/mxs-pwm.txt new file mode 100644 index 000000000000..48ead0db178c --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/mxs-pwm.txt @@ -0,0 +1,17 @@ +Freescale MXS PWM controller + +Required properties: +- compatible: should be "fsl,mxs-pwm" +- reg: physical base address and length of the controller's registers +- #pwm-cells: should be 2. The first cell specifies the per-chip index + of the PWM to use and the second cell is the duty cycle in nanoseconds. +- fsl,pwm-number: the number of PWM devices + +Example: + +pwm: pwm@80064000 { + compatible = "fsl,imx28-pwm", "fsl,mxs-pwm"; + reg = <0x80064000 2000>; + #pwm-cells = <2>; + fsl,pwm-number = <8>; +}; diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index a93feffbc36a..39bdebc0cb20 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -27,6 +27,15 @@ config PWM_IMX To compile this driver as a module, choose M here: the module will be called pwm-imx. +config PWM_MXS + tristate "Freescale MXS PWM support" + depends on ARCH_MXS && OF + help + Generic PWM framework driver for Freescale MXS. + + To compile this driver as a module, choose M here: the module + will be called pwm-mxs. + config PWM_PXA tristate "PXA PWM support" depends on ARCH_PXA diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index b7c0fcf4de16..cec250091cff 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -1,6 +1,7 @@ obj-$(CONFIG_PWM) += core.o obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o obj-$(CONFIG_PWM_IMX) += pwm-imx.o +obj-$(CONFIG_PWM_MXS) += pwm-mxs.o obj-$(CONFIG_PWM_PXA) += pwm-pxa.o obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o diff --git a/drivers/pwm/pwm-mxs.c b/drivers/pwm/pwm-mxs.c new file mode 100644 index 000000000000..9602708372b8 --- /dev/null +++ b/drivers/pwm/pwm-mxs.c @@ -0,0 +1,207 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SET 0x4 +#define CLR 0x8 +#define TOG 0xc + +#define PWM_CTRL 0x0 +#define PWM_ACTIVE0 0x10 +#define PWM_PERIOD0 0x20 +#define PERIOD_PERIOD(p) ((p) & 0xffff) +#define PERIOD_PERIOD_MAX 0x10000 +#define PERIOD_ACTIVE_HIGH (3 << 16) +#define PERIOD_INACTIVE_LOW (2 << 18) +#define PERIOD_CDIV(div) (((div) & 0x7) << 20) +#define PERIOD_CDIV_MAX 8 + +struct mxs_pwm_chip { + struct pwm_chip chip; + struct device *dev; + struct clk *clk; + void __iomem *base; +}; + +#define to_mxs_pwm_chip(_chip) container_of(_chip, struct mxs_pwm_chip, chip) + +static int mxs_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct mxs_pwm_chip *mxs = to_mxs_pwm_chip(chip); + int ret, div = 0; + unsigned int period_cycles, duty_cycles; + unsigned long rate; + unsigned long long c; + + rate = clk_get_rate(mxs->clk); + while (1) { + c = rate / (1 << div); + c = c * period_ns; + do_div(c, 1000000000); + if (c < PERIOD_PERIOD_MAX) + break; + div++; + if (div > PERIOD_CDIV_MAX) + return -EINVAL; + } + + period_cycles = c; + c *= duty_ns; + do_div(c, period_ns); + duty_cycles = c; + + /* + * If the PWM channel is disabled, make sure to turn on the clock + * before writing the register. Otherwise, keep it enabled. + */ + if (!test_bit(PWMF_ENABLED, &pwm->flags)) { + ret = clk_prepare_enable(mxs->clk); + if (ret) + return ret; + } + + writel(duty_cycles << 16, + mxs->base + PWM_ACTIVE0 + pwm->hwpwm * 0x20); + writel(PERIOD_PERIOD(period_cycles) | PERIOD_ACTIVE_HIGH | + PERIOD_INACTIVE_LOW | PERIOD_CDIV(div), + mxs->base + PWM_PERIOD0 + pwm->hwpwm * 0x20); + + /* + * If the PWM is not enabled, turn the clock off again to save power. + */ + if (!test_bit(PWMF_ENABLED, &pwm->flags)) + clk_disable_unprepare(mxs->clk); + + return 0; +} + +static int mxs_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct mxs_pwm_chip *mxs = to_mxs_pwm_chip(chip); + int ret; + + ret = clk_prepare_enable(mxs->clk); + if (ret) + return ret; + + writel(1 << pwm->hwpwm, mxs->base + PWM_CTRL + SET); + + return 0; +} + +static void mxs_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct mxs_pwm_chip *mxs = to_mxs_pwm_chip(chip); + + writel(1 << pwm->hwpwm, mxs->base + PWM_CTRL + CLR); + + clk_disable_unprepare(mxs->clk); +} + +static const struct pwm_ops mxs_pwm_ops = { + .config = mxs_pwm_config, + .enable = mxs_pwm_enable, + .disable = mxs_pwm_disable, + .owner = THIS_MODULE, +}; + +static int mxs_pwm_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct mxs_pwm_chip *mxs; + int ret; + + mxs = devm_kzalloc(&pdev->dev, sizeof(*mxs), GFP_KERNEL); + if (!mxs) + return -ENOMEM; + + mxs->base = of_iomap(np, 0); + if (!mxs->base) + return -EADDRNOTAVAIL; + + mxs->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(mxs->clk)) { + ret = PTR_ERR(mxs->clk); + goto iounmap; + } + + mxs->chip.dev = &pdev->dev; + mxs->chip.ops = &mxs_pwm_ops; + mxs->chip.base = -1; + ret = of_property_read_u32(np, "fsl,pwm-number", &mxs->chip.npwm); + if (ret < 0) { + dev_err(&pdev->dev, "failed to get pwm number: %d\n", ret); + goto clk_put; + } + + ret = pwmchip_add(&mxs->chip); + if (ret < 0) { + dev_err(&pdev->dev, "failed to add pwm chip %d\n", ret); + goto clk_put; + } + + mxs->dev = &pdev->dev; + platform_set_drvdata(pdev, mxs); + + mxs_reset_block(mxs->base); + + return 0; + +clk_put: + clk_put(mxs->clk); +iounmap: + iounmap(mxs->base); + return ret; +} + +static int __devexit mxs_pwm_remove(struct platform_device *pdev) +{ + struct mxs_pwm_chip *mxs = platform_get_drvdata(pdev); + + pwmchip_remove(&mxs->chip); + clk_put(mxs->clk); + iounmap(mxs->base); + + return 0; +} + +static struct of_device_id mxs_pwm_dt_ids[] = { + { .compatible = "fsl,mxs-pwm", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mxs_pwm_dt_ids); + +static struct platform_driver mxs_pwm_driver = { + .driver = { + .name = "mxs-pwm", + .of_match_table = of_match_ptr(mxs_pwm_dt_ids), + }, + .probe = mxs_pwm_probe, + .remove = __devexit_p(mxs_pwm_remove), +}; +module_platform_driver(mxs_pwm_driver); + +MODULE_ALIAS("platform:mxs-pwm"); +MODULE_AUTHOR("Shawn Guo "); +MODULE_DESCRIPTION("Freescale MXS PWM Driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-59-g8ed1b From 3e3ed6cdc49d758719c148a78c8b04c243ef74d0 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 16 Dec 2011 21:25:29 +0100 Subject: pwm-backlight: Add rudimentary device tree support This commit adds very basic support for device tree probing. Currently, only a PWM and a list of distinct brightness levels can be specified. Enabling or disabling backlight power via GPIOs is not yet supported. Reviewed-by: Shawn Guo Reviewed-by: Mark Brown Signed-off-by: Thierry Reding --- .../bindings/video/backlight/pwm-backlight.txt | 28 ++++ drivers/video/backlight/Kconfig | 2 +- drivers/video/backlight/pwm_bl.c | 149 ++++++++++++++++++--- include/linux/pwm_backlight.h | 1 + 4 files changed, 159 insertions(+), 21 deletions(-) create mode 100644 Documentation/devicetree/bindings/video/backlight/pwm-backlight.txt diff --git a/Documentation/devicetree/bindings/video/backlight/pwm-backlight.txt b/Documentation/devicetree/bindings/video/backlight/pwm-backlight.txt new file mode 100644 index 000000000000..1e4fc727f3b1 --- /dev/null +++ b/Documentation/devicetree/bindings/video/backlight/pwm-backlight.txt @@ -0,0 +1,28 @@ +pwm-backlight bindings + +Required properties: + - compatible: "pwm-backlight" + - pwms: OF device-tree PWM specification (see PWM binding[0]) + - brightness-levels: Array of distinct brightness levels. Typically these + are in the range from 0 to 255, but any range starting at 0 will do. + The actual brightness level (PWM duty cycle) will be interpolated + from these values. 0 means a 0% duty cycle (darkest/off), while the + last value in the array represents a 100% duty cycle (brightest). + - default-brightness-level: the default brightness level (index into the + array defined by the "brightness-levels" property) + +Optional properties: + - pwm-names: a list of names for the PWM devices specified in the + "pwms" property (see PWM binding[0]) + +[0]: Documentation/devicetree/bindings/pwm/pwm.txt + +Example: + + backlight { + compatible = "pwm-backlight"; + pwms = <&pwm 0 5000000>; + + brightness-levels = <0 4 8 16 32 64 128 255>; + default-brightness-level = <6>; + }; diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index fa2b03750316..4c9c02216351 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig @@ -245,7 +245,7 @@ config BACKLIGHT_CARILLO_RANCH config BACKLIGHT_PWM tristate "Generic PWM based Backlight Driver" - depends on HAVE_PWM + depends on PWM help If you have a LCD backlight adjustable by PWM, say Y to enable this driver. diff --git a/drivers/video/backlight/pwm_bl.c b/drivers/video/backlight/pwm_bl.c index 342b7d7cbb63..057389d69a51 100644 --- a/drivers/video/backlight/pwm_bl.c +++ b/drivers/video/backlight/pwm_bl.c @@ -26,11 +26,13 @@ struct pwm_bl_data { struct device *dev; unsigned int period; unsigned int lth_brightness; + unsigned int *levels; int (*notify)(struct device *, int brightness); void (*notify_after)(struct device *, int brightness); int (*check_fb)(struct device *, struct fb_info *); + void (*exit)(struct device *); }; static int pwm_backlight_update_status(struct backlight_device *bl) @@ -52,6 +54,11 @@ static int pwm_backlight_update_status(struct backlight_device *bl) pwm_config(pb->pwm, 0, pb->period); pwm_disable(pb->pwm); } else { + if (pb->levels) { + brightness = pb->levels[brightness]; + max = pb->levels[max]; + } + brightness = pb->lth_brightness + (brightness * (pb->period - pb->lth_brightness) / max); pwm_config(pb->pwm, brightness, pb->period); @@ -83,17 +90,98 @@ static const struct backlight_ops pwm_backlight_ops = { .check_fb = pwm_backlight_check_fb, }; +#ifdef CONFIG_OF +static int pwm_backlight_parse_dt(struct device *dev, + struct platform_pwm_backlight_data *data) +{ + struct device_node *node = dev->of_node; + struct property *prop; + int length; + u32 value; + int ret; + + if (!node) + return -ENODEV; + + memset(data, 0, sizeof(*data)); + + /* determine the number of brightness levels */ + prop = of_find_property(node, "brightness-levels", &length); + if (!prop) + return -EINVAL; + + data->max_brightness = length / sizeof(u32); + + /* read brightness levels from DT property */ + if (data->max_brightness > 0) { + size_t size = sizeof(*data->levels) * data->max_brightness; + + data->levels = devm_kzalloc(dev, size, GFP_KERNEL); + if (!data->levels) + return -ENOMEM; + + ret = of_property_read_u32_array(node, "brightness-levels", + data->levels, + data->max_brightness); + if (ret < 0) + return ret; + + ret = of_property_read_u32(node, "default-brightness-level", + &value); + if (ret < 0) + return ret; + + if (value >= data->max_brightness) { + dev_warn(dev, "invalid default brightness level: %u, using %u\n", + value, data->max_brightness - 1); + value = data->max_brightness - 1; + } + + data->dft_brightness = value; + data->max_brightness--; + } + + /* + * TODO: Most users of this driver use a number of GPIOs to control + * backlight power. Support for specifying these needs to be + * added. + */ + + return 0; +} + +static struct of_device_id pwm_backlight_of_match[] = { + { .compatible = "pwm-backlight" }, + { } +}; + +MODULE_DEVICE_TABLE(of, pwm_backlight_of_match); +#else +static int pwm_backlight_parse_dt(struct device *dev, + struct platform_pwm_backlight_data *data) +{ + return -ENODEV; +} +#endif + static int pwm_backlight_probe(struct platform_device *pdev) { - struct backlight_properties props; struct platform_pwm_backlight_data *data = pdev->dev.platform_data; + struct platform_pwm_backlight_data defdata; + struct backlight_properties props; struct backlight_device *bl; struct pwm_bl_data *pb; + unsigned int max; int ret; if (!data) { - dev_err(&pdev->dev, "failed to find platform data\n"); - return -EINVAL; + ret = pwm_backlight_parse_dt(&pdev->dev, &defdata); + if (ret < 0) { + dev_err(&pdev->dev, "failed to find platform data\n"); + return ret; + } + + data = &defdata; } if (data->init) { @@ -109,21 +197,42 @@ static int pwm_backlight_probe(struct platform_device *pdev) goto err_alloc; } - pb->period = data->pwm_period_ns; + if (data->levels) { + max = data->levels[data->max_brightness]; + pb->levels = data->levels; + } else + max = data->max_brightness; + pb->notify = data->notify; pb->notify_after = data->notify_after; pb->check_fb = data->check_fb; - pb->lth_brightness = data->lth_brightness * - (data->pwm_period_ns / data->max_brightness); + pb->exit = data->exit; pb->dev = &pdev->dev; - pb->pwm = pwm_request(data->pwm_id, "backlight"); + pb->pwm = pwm_get(&pdev->dev, NULL); if (IS_ERR(pb->pwm)) { - dev_err(&pdev->dev, "unable to request PWM for backlight\n"); - ret = PTR_ERR(pb->pwm); - goto err_alloc; - } else - dev_dbg(&pdev->dev, "got pwm for backlight\n"); + dev_err(&pdev->dev, "unable to request PWM, trying legacy API\n"); + + pb->pwm = pwm_request(data->pwm_id, "pwm-backlight"); + if (IS_ERR(pb->pwm)) { + dev_err(&pdev->dev, "unable to request legacy PWM\n"); + ret = PTR_ERR(pb->pwm); + goto err_alloc; + } + } + + dev_dbg(&pdev->dev, "got pwm for backlight\n"); + + /* + * The DT case will set the pwm_period_ns field to 0 and store the + * period, parsed from the DT, in the PWM device. For the non-DT case, + * set the period from platform data. + */ + if (data->pwm_period_ns > 0) + pwm_set_period(pb->pwm, data->pwm_period_ns); + + pb->period = pwm_get_period(pb->pwm); + pb->lth_brightness = data->lth_brightness * (pb->period / max); memset(&props, 0, sizeof(struct backlight_properties)); props.type = BACKLIGHT_RAW; @@ -143,7 +252,7 @@ static int pwm_backlight_probe(struct platform_device *pdev) return 0; err_bl: - pwm_free(pb->pwm); + pwm_put(pb->pwm); err_alloc: if (data->exit) data->exit(&pdev->dev); @@ -152,16 +261,15 @@ err_alloc: static int pwm_backlight_remove(struct platform_device *pdev) { - struct platform_pwm_backlight_data *data = pdev->dev.platform_data; struct backlight_device *bl = platform_get_drvdata(pdev); struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev); backlight_device_unregister(bl); pwm_config(pb->pwm, 0, pb->period); pwm_disable(pb->pwm); - pwm_free(pb->pwm); - if (data->exit) - data->exit(&pdev->dev); + pwm_put(pb->pwm); + if (pb->exit) + pb->exit(&pdev->dev); return 0; } @@ -195,11 +303,12 @@ static SIMPLE_DEV_PM_OPS(pwm_backlight_pm_ops, pwm_backlight_suspend, static struct platform_driver pwm_backlight_driver = { .driver = { - .name = "pwm-backlight", - .owner = THIS_MODULE, + .name = "pwm-backlight", + .owner = THIS_MODULE, #ifdef CONFIG_PM - .pm = &pwm_backlight_pm_ops, + .pm = &pwm_backlight_pm_ops, #endif + .of_match_table = of_match_ptr(pwm_backlight_of_match), }, .probe = pwm_backlight_probe, .remove = pwm_backlight_remove, diff --git a/include/linux/pwm_backlight.h b/include/linux/pwm_backlight.h index 63d2df43e61a..56f4a866539a 100644 --- a/include/linux/pwm_backlight.h +++ b/include/linux/pwm_backlight.h @@ -12,6 +12,7 @@ struct platform_pwm_backlight_data { unsigned int dft_brightness; unsigned int lth_brightness; unsigned int pwm_period_ns; + unsigned int *levels; int (*init)(struct device *dev); int (*notify)(struct device *dev, int brightness); void (*notify_after)(struct device *dev, int brightness); -- cgit v1.2.3-59-g8ed1b From 200efedd87662e3510b2e9ac8b56c8a32baf3e12 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 27 Mar 2012 13:16:18 +0200 Subject: pwm: Take over maintainership of the PWM subsystem I'm taking over the maintainership of the PWM subsystem. This commit also adds the URLs to the gitorious project and repository as well as any missing files related to the PWM subsystem. Acked-by: Arnd Bergmann Acked-by: Sascha Hauer Signed-off-by: Thierry Reding --- MAINTAINERS | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 67d6cb70cb7e..09da19dd5e02 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5487,10 +5487,16 @@ S: Maintained F: Documentation/video4linux/README.pvrusb2 F: drivers/media/video/pvrusb2/ -PWM core -M: Sascha Hauer +PWM SUBSYSTEM +M: Thierry Reding L: linux-kernel@vger.kernel.org S: Maintained +W: http://gitorious.org/linux-pwm +T: git git://gitorious.org/linux-pwm/linux-pwm.git +F: Documentation/pwm.txt +F: Documentation/devicetree/bindings/pwm/ +F: include/linux/pwm.h +F: include/linux/of_pwm.h F: drivers/pwm/ PXA2xx/PXA3xx SUPPORT -- cgit v1.2.3-59-g8ed1b From 071407eefd708d7e273b67af150f8dff6c51eb58 Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Tue, 26 Jun 2012 16:58:08 +0800 Subject: pwm: pwm-mxs: encode soc name in compatible string Encode soc name in the compatible string to know the specific version hardware block. This is the general approach adopted for most bindings. Change mxs-pwm binding to use the approach. Signed-off-by: Shawn Guo Signed-off-by: Thierry Reding --- Documentation/devicetree/bindings/pwm/mxs-pwm.txt | 4 ++-- drivers/pwm/pwm-mxs.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/pwm/mxs-pwm.txt b/Documentation/devicetree/bindings/pwm/mxs-pwm.txt index 48ead0db178c..b16f4a57d111 100644 --- a/Documentation/devicetree/bindings/pwm/mxs-pwm.txt +++ b/Documentation/devicetree/bindings/pwm/mxs-pwm.txt @@ -1,7 +1,7 @@ Freescale MXS PWM controller Required properties: -- compatible: should be "fsl,mxs-pwm" +- compatible: should be "fsl,imx23-pwm" - reg: physical base address and length of the controller's registers - #pwm-cells: should be 2. The first cell specifies the per-chip index of the PWM to use and the second cell is the duty cycle in nanoseconds. @@ -10,7 +10,7 @@ Required properties: Example: pwm: pwm@80064000 { - compatible = "fsl,imx28-pwm", "fsl,mxs-pwm"; + compatible = "fsl,imx28-pwm", "fsl,imx23-pwm"; reg = <0x80064000 2000>; #pwm-cells = <2>; fsl,pwm-number = <8>; diff --git a/drivers/pwm/pwm-mxs.c b/drivers/pwm/pwm-mxs.c index 9602708372b8..d9a38050a502 100644 --- a/drivers/pwm/pwm-mxs.c +++ b/drivers/pwm/pwm-mxs.c @@ -186,7 +186,7 @@ static int __devexit mxs_pwm_remove(struct platform_device *pdev) } static struct of_device_id mxs_pwm_dt_ids[] = { - { .compatible = "fsl,mxs-pwm", }, + { .compatible = "fsl,imx23-pwm", }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, mxs_pwm_dt_ids); -- cgit v1.2.3-59-g8ed1b From 01bf32e94968fd13804062859cfd935efebc11e9 Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Tue, 26 Jun 2012 16:58:09 +0800 Subject: pwm: pwm-mxs: use global reset function stmp_reset_block Use global reset function stmp_reset_block instead of mxs_reset_block to remove inclusion. Signed-off-by: Shawn Guo Signed-off-by: Thierry Reding --- drivers/pwm/Kconfig | 1 + drivers/pwm/pwm-mxs.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 39bdebc0cb20..5227415f1855 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -30,6 +30,7 @@ config PWM_IMX config PWM_MXS tristate "Freescale MXS PWM support" depends on ARCH_MXS && OF + select STMP_DEVICE help Generic PWM framework driver for Freescale MXS. diff --git a/drivers/pwm/pwm-mxs.c b/drivers/pwm/pwm-mxs.c index d9a38050a502..23e524bbee03 100644 --- a/drivers/pwm/pwm-mxs.c +++ b/drivers/pwm/pwm-mxs.c @@ -19,7 +19,7 @@ #include #include #include -#include +#include #define SET 0x4 #define CLR 0x8 @@ -163,7 +163,7 @@ static int mxs_pwm_probe(struct platform_device *pdev) mxs->dev = &pdev->dev; platform_set_drvdata(pdev, mxs); - mxs_reset_block(mxs->base); + stmp_reset_block(mxs->base); return 0; -- cgit v1.2.3-59-g8ed1b From 22d260bd88452ae81c80514810434d4cc98b7978 Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Tue, 26 Jun 2012 16:58:10 +0800 Subject: pwm: pwm-mxs: use devm_* managed functions Use devm_* managed functions to have a clean fail-out. Signed-off-by: Shawn Guo Signed-off-by: Thierry Reding --- drivers/pwm/pwm-mxs.c | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/drivers/pwm/pwm-mxs.c b/drivers/pwm/pwm-mxs.c index 23e524bbee03..3b7f3ab116e3 100644 --- a/drivers/pwm/pwm-mxs.c +++ b/drivers/pwm/pwm-mxs.c @@ -129,21 +129,21 @@ static int mxs_pwm_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; struct mxs_pwm_chip *mxs; + struct resource *res; int ret; mxs = devm_kzalloc(&pdev->dev, sizeof(*mxs), GFP_KERNEL); if (!mxs) return -ENOMEM; - mxs->base = of_iomap(np, 0); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mxs->base = devm_request_and_ioremap(&pdev->dev, res); if (!mxs->base) return -EADDRNOTAVAIL; - mxs->clk = clk_get(&pdev->dev, NULL); - if (IS_ERR(mxs->clk)) { - ret = PTR_ERR(mxs->clk); - goto iounmap; - } + mxs->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(mxs->clk)) + return PTR_ERR(mxs->clk); mxs->chip.dev = &pdev->dev; mxs->chip.ops = &mxs_pwm_ops; @@ -151,13 +151,13 @@ static int mxs_pwm_probe(struct platform_device *pdev) ret = of_property_read_u32(np, "fsl,pwm-number", &mxs->chip.npwm); if (ret < 0) { dev_err(&pdev->dev, "failed to get pwm number: %d\n", ret); - goto clk_put; + return ret; } ret = pwmchip_add(&mxs->chip); if (ret < 0) { dev_err(&pdev->dev, "failed to add pwm chip %d\n", ret); - goto clk_put; + return ret; } mxs->dev = &pdev->dev; @@ -166,12 +166,6 @@ static int mxs_pwm_probe(struct platform_device *pdev) stmp_reset_block(mxs->base); return 0; - -clk_put: - clk_put(mxs->clk); -iounmap: - iounmap(mxs->base); - return ret; } static int __devexit mxs_pwm_remove(struct platform_device *pdev) @@ -179,8 +173,6 @@ static int __devexit mxs_pwm_remove(struct platform_device *pdev) struct mxs_pwm_chip *mxs = platform_get_drvdata(pdev); pwmchip_remove(&mxs->chip); - clk_put(mxs->clk); - iounmap(mxs->base); return 0; } -- cgit v1.2.3-59-g8ed1b From 1112fe88d4ebbb5f46750726b87fb0beb1396835 Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Tue, 26 Jun 2012 16:58:11 +0800 Subject: pwm: pwm-mxs: add pinctrl support Call pinctrl subsystem to set up pwm pin. Signed-off-by: Shawn Guo Signed-off-by: Thierry Reding --- drivers/pwm/pwm-mxs.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/pwm/pwm-mxs.c b/drivers/pwm/pwm-mxs.c index 3b7f3ab116e3..269d7c2a87d6 100644 --- a/drivers/pwm/pwm-mxs.c +++ b/drivers/pwm/pwm-mxs.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -130,6 +131,7 @@ static int mxs_pwm_probe(struct platform_device *pdev) struct device_node *np = pdev->dev.of_node; struct mxs_pwm_chip *mxs; struct resource *res; + struct pinctrl *pinctrl; int ret; mxs = devm_kzalloc(&pdev->dev, sizeof(*mxs), GFP_KERNEL); @@ -141,6 +143,10 @@ static int mxs_pwm_probe(struct platform_device *pdev) if (!mxs->base) return -EADDRNOTAVAIL; + pinctrl = devm_pinctrl_get_select_default(&pdev->dev); + if (IS_ERR(pinctrl)) + return PTR_ERR(pinctrl); + mxs->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(mxs->clk)) return PTR_ERR(mxs->clk); -- cgit v1.2.3-59-g8ed1b From eac7a92fd0bc5b1edff0f9c7be5dce4f88491fb3 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 2 Jul 2012 21:29:45 +0200 Subject: pwm: Conflict with legacy PWM API In order to avoid duplicate symbols with legacy PWM API implementations, the new PWM framework needs to conflict with any of the existing legacy implementations. This is done in two ways: for implementations provided by drivers, a conflict is added to the driver to ensure it will have to be ported to the PWM subsystem before it can coexist with other PWM providers. For architecture-specific code, the conflict is added to the PWM symbol to avoid confusion when a previously picked platform or machine can no longer be selected because of the PWM subsystem being included. Signed-off-by: Thierry Reding --- drivers/mfd/Kconfig | 1 + drivers/misc/Kconfig | 1 + drivers/pwm/Kconfig | 1 + 3 files changed, 3 insertions(+) diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index e129c820df7d..19c7f3809963 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -276,6 +276,7 @@ config TWL6030_PWM tristate "TWL6030 PWM (Pulse Width Modulator) Support" depends on TWL4030_CORE select HAVE_PWM + depends on !PWM default n help Say yes here if you want support for TWL6030 PWM. diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 2661f6e366f9..731ed8504a64 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -64,6 +64,7 @@ config AB8500_PWM bool "AB8500 PWM support" depends on AB8500_CORE && ARCH_U8500 select HAVE_PWM + depends on !PWM help This driver exports functions to enable/disble/config/free Pulse Width Modulation in the Analog Baseband Chip AB8500. diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 5227415f1855..0b2800fc1ca7 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -1,5 +1,6 @@ menuconfig PWM bool "PWM Support" + depends on !MACH_JZ4740 && !PUV3_PWM help This enables PWM support through the generic PWM framework. You only need to enable this, if you also want to enable -- cgit v1.2.3-59-g8ed1b From a9970e3becbbb095296afdf0bcaa1ce2b819360f Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Sun, 1 Jul 2012 08:27:23 +0800 Subject: pwm: Convert pwm-imx to use devm_* APIs Signed-off-by: Axel Lin Signed-off-by: Thierry Reding --- drivers/pwm/pwm-imx.c | 57 +++++++++------------------------------------------ 1 file changed, 10 insertions(+), 47 deletions(-) diff --git a/drivers/pwm/pwm-imx.c b/drivers/pwm/pwm-imx.c index 900d14562de8..2a0b35333972 100644 --- a/drivers/pwm/pwm-imx.c +++ b/drivers/pwm/pwm-imx.c @@ -159,18 +159,16 @@ static int __devinit imx_pwm_probe(struct platform_device *pdev) struct resource *r; int ret = 0; - imx = kzalloc(sizeof(*imx), GFP_KERNEL); + imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL); if (imx == NULL) { dev_err(&pdev->dev, "failed to allocate memory\n"); return -ENOMEM; } - imx->clk = clk_get(&pdev->dev, "pwm"); + imx->clk = devm_clk_get(&pdev->dev, "pwm"); - if (IS_ERR(imx->clk)) { - ret = PTR_ERR(imx->clk); - goto err_free; - } + if (IS_ERR(imx->clk)) + return PTR_ERR(imx->clk); imx->chip.ops = &imx_pwm_ops; imx->chip.dev = &pdev->dev; @@ -182,65 +180,30 @@ static int __devinit imx_pwm_probe(struct platform_device *pdev) r = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (r == NULL) { dev_err(&pdev->dev, "no memory resource defined\n"); - ret = -ENODEV; - goto err_free_clk; - } - - r = request_mem_region(r->start, resource_size(r), pdev->name); - if (r == NULL) { - dev_err(&pdev->dev, "failed to request memory resource\n"); - ret = -EBUSY; - goto err_free_clk; + return -ENODEV; } - imx->mmio_base = ioremap(r->start, resource_size(r)); - if (imx->mmio_base == NULL) { - dev_err(&pdev->dev, "failed to ioremap() registers\n"); - ret = -ENODEV; - goto err_free_mem; - } + imx->mmio_base = devm_request_and_ioremap(&pdev->dev, r); + if (imx->mmio_base == NULL) + return -EADDRNOTAVAIL; ret = pwmchip_add(&imx->chip); if (ret < 0) - goto err_iounmap; + return ret; platform_set_drvdata(pdev, imx); return 0; - -err_iounmap: - iounmap(imx->mmio_base); -err_free_mem: - release_mem_region(r->start, resource_size(r)); -err_free_clk: - clk_put(imx->clk); -err_free: - kfree(imx); - return ret; } static int __devexit imx_pwm_remove(struct platform_device *pdev) { struct imx_chip *imx; - struct resource *r; - int ret; imx = platform_get_drvdata(pdev); if (imx == NULL) return -ENODEV; - ret = pwmchip_remove(&imx->chip); - if (ret) - return ret; - - iounmap(imx->mmio_base); - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - release_mem_region(r->start, resource_size(r)); - - clk_put(imx->clk); - - kfree(imx); - return 0; + return pwmchip_remove(&imx->chip); } static struct platform_driver imx_pwm_driver = { -- cgit v1.2.3-59-g8ed1b From 261995dd305ff991ca1cfb462862a5311739333b Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Sun, 1 Jul 2012 08:29:28 +0800 Subject: pwm: Convert pwm-vt8500 to use devm_* APIs Signed-off-by: Axel Lin Signed-off-by: Thierry Reding --- drivers/pwm/pwm-vt8500.c | 47 ++++++++--------------------------------------- 1 file changed, 8 insertions(+), 39 deletions(-) diff --git a/drivers/pwm/pwm-vt8500.c b/drivers/pwm/pwm-vt8500.c index 3db0746f7200..548021439f0c 100644 --- a/drivers/pwm/pwm-vt8500.c +++ b/drivers/pwm/pwm-vt8500.c @@ -1,5 +1,5 @@ /* - * arch/arm/mach-vt8500/pwm.c + * drivers/pwm/pwm-vt8500.c * * Copyright (C) 2010 Alexey Charkov * @@ -113,7 +113,7 @@ static int __devinit pwm_probe(struct platform_device *pdev) struct resource *r; int ret; - chip = kzalloc(sizeof(*chip), GFP_KERNEL); + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); if (chip == NULL) { dev_err(&pdev->dev, "failed to allocate memory\n"); return -ENOMEM; @@ -127,61 +127,30 @@ static int __devinit pwm_probe(struct platform_device *pdev) r = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (r == NULL) { dev_err(&pdev->dev, "no memory resource defined\n"); - ret = -ENODEV; - goto err_free; + return -ENODEV; } - r = request_mem_region(r->start, resource_size(r), pdev->name); - if (r == NULL) { - dev_err(&pdev->dev, "failed to request memory resource\n"); - ret = -EBUSY; - goto err_free; - } - - chip->base = ioremap(r->start, resource_size(r)); - if (chip->base == NULL) { - dev_err(&pdev->dev, "failed to ioremap() registers\n"); - ret = -ENODEV; - goto err_free_mem; - } + chip->base = devm_request_and_ioremap(&pdev->dev, r); + if (chip->base == NULL) + return -EADDRNOTAVAIL; ret = pwmchip_add(&chip->chip); if (ret < 0) - goto err_unmap; + return ret; platform_set_drvdata(pdev, chip); return ret; - -err_unmap: - iounmap(chip->base); -err_free_mem: - release_mem_region(r->start, resource_size(r)); -err_free: - kfree(chip); - return ret; } static int __devexit pwm_remove(struct platform_device *pdev) { struct vt8500_chip *chip; - struct resource *r; - int err; chip = platform_get_drvdata(pdev); if (chip == NULL) return -ENODEV; - err = pwmchip_remove(&chip->chip); - if (err < 0) - return err; - - iounmap(chip->base); - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - release_mem_region(r->start, resource_size(r)); - - kfree(chip); - return 0; + return pwmchip_remove(&chip->chip); } static struct platform_driver pwm_driver = { -- cgit v1.2.3-59-g8ed1b From 45b301d2b188af6db332cbb14c36e20b5204223e Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Sun, 1 Jul 2012 18:02:14 +0800 Subject: pwm: Convert pwm-pxa to use devm_* APIs Signed-off-by: Axel Lin Signed-off-by: Thierry Reding --- drivers/pwm/pwm-pxa.c | 49 ++++++++++--------------------------------------- 1 file changed, 10 insertions(+), 39 deletions(-) diff --git a/drivers/pwm/pwm-pxa.c b/drivers/pwm/pwm-pxa.c index d5c6ce513e29..a2be966d550d 100644 --- a/drivers/pwm/pwm-pxa.c +++ b/drivers/pwm/pwm-pxa.c @@ -1,5 +1,5 @@ /* - * linux/arch/arm/mach-pxa/pwm.c + * drivers/pwm/pwm-pxa.c * * simple driver for PWM (Pulse Width Modulator) controller * @@ -145,17 +145,16 @@ static int __devinit pwm_probe(struct platform_device *pdev) struct resource *r; int ret = 0; - pwm = kzalloc(sizeof(*pwm), GFP_KERNEL); + pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); if (pwm == NULL) { dev_err(&pdev->dev, "failed to allocate memory\n"); return -ENOMEM; } - pwm->clk = clk_get(&pdev->dev, NULL); - if (IS_ERR(pwm->clk)) { - ret = PTR_ERR(pwm->clk); - goto err_free; - } + pwm->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(pwm->clk)) + return PTR_ERR(pwm->clk); + pwm->clk_enabled = 0; pwm->chip.dev = &pdev->dev; @@ -166,23 +165,12 @@ static int __devinit pwm_probe(struct platform_device *pdev) r = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (r == NULL) { dev_err(&pdev->dev, "no memory resource defined\n"); - ret = -ENODEV; - goto err_free_clk; - } - - r = request_mem_region(r->start, resource_size(r), pdev->name); - if (r == NULL) { - dev_err(&pdev->dev, "failed to request memory resource\n"); - ret = -EBUSY; - goto err_free_clk; + return -ENODEV; } - pwm->mmio_base = ioremap(r->start, resource_size(r)); - if (pwm->mmio_base == NULL) { - dev_err(&pdev->dev, "failed to ioremap() registers\n"); - ret = -ENODEV; - goto err_free_mem; - } + pwm->mmio_base = devm_request_and_ioremap(&pdev->dev, r); + if (pwm->mmio_base == NULL) + return -EADDRNOTAVAIL; ret = pwmchip_add(&pwm->chip); if (ret < 0) { @@ -192,34 +180,17 @@ static int __devinit pwm_probe(struct platform_device *pdev) platform_set_drvdata(pdev, pwm); return 0; - -err_free_mem: - release_mem_region(r->start, resource_size(r)); -err_free_clk: - clk_put(pwm->clk); -err_free: - kfree(pwm); - return ret; } static int __devexit pwm_remove(struct platform_device *pdev) { struct pxa_pwm_chip *chip; - struct resource *r; chip = platform_get_drvdata(pdev); if (chip == NULL) return -ENODEV; pwmchip_remove(&chip->chip); - - iounmap(chip->mmio_base); - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - release_mem_region(r->start, resource_size(r)); - - clk_put(chip->clk); - kfree(chip); return 0; } -- cgit v1.2.3-59-g8ed1b From abeaf75527386adea831914579f0b8711287d3f8 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 2 Jul 2012 21:32:33 +0200 Subject: pwm: pxa: Propagate pwmchip_remove() error If the pwmchip_remove() call fails, propagate the error to the driver's remove callback. This is required to prevent the module from being unloaded if a PWM provided by the driver is still in use. Signed-off-by: Thierry Reding --- drivers/pwm/pwm-pxa.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/pwm/pwm-pxa.c b/drivers/pwm/pwm-pxa.c index a2be966d550d..bd5867a1c700 100644 --- a/drivers/pwm/pwm-pxa.c +++ b/drivers/pwm/pwm-pxa.c @@ -190,8 +190,7 @@ static int __devexit pwm_remove(struct platform_device *pdev) if (chip == NULL) return -ENODEV; - pwmchip_remove(&chip->chip); - return 0; + return pwmchip_remove(&chip->chip); } static struct platform_driver pwm_driver = { -- cgit v1.2.3-59-g8ed1b From b50675dcc3402ea456a1d59855260fcfd800fb40 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Sun, 1 Jul 2012 12:57:00 +0800 Subject: pwm: pwm-bfin: Return proper error if pwmchip_remove() fails Signed-off-by: Axel Lin Signed-off-by: Thierry Reding --- drivers/pwm/pwm-bfin.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/pwm/pwm-bfin.c b/drivers/pwm/pwm-bfin.c index a0c6bf9070ea..d53c4e7941ef 100644 --- a/drivers/pwm/pwm-bfin.c +++ b/drivers/pwm/pwm-bfin.c @@ -146,9 +146,7 @@ static int __devexit bfin_pwm_remove(struct platform_device *pdev) { struct bfin_pwm_chip *pwm = platform_get_drvdata(pdev); - pwmchip_remove(&pwm->chip); - - return 0; + return pwmchip_remove(&pwm->chip); } static struct platform_driver bfin_pwm_driver = { -- cgit v1.2.3-59-g8ed1b From 457fd768ee1f7c78e85dfbe86329e6b24eb36514 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Sun, 1 Jul 2012 12:58:00 +0800 Subject: pwm: pwm-mxs: Return proper error if pwmchip_remove() fails Signed-off-by: Axel Lin Acked-by: Shawn Guo Signed-off-by: Thierry Reding --- drivers/pwm/pwm-mxs.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/pwm/pwm-mxs.c b/drivers/pwm/pwm-mxs.c index 269d7c2a87d6..e5852646f082 100644 --- a/drivers/pwm/pwm-mxs.c +++ b/drivers/pwm/pwm-mxs.c @@ -178,9 +178,7 @@ static int __devexit mxs_pwm_remove(struct platform_device *pdev) { struct mxs_pwm_chip *mxs = platform_get_drvdata(pdev); - pwmchip_remove(&mxs->chip); - - return 0; + return pwmchip_remove(&mxs->chip); } static struct of_device_id mxs_pwm_dt_ids[] = { -- cgit v1.2.3-59-g8ed1b From 0c8f527d8c80a027b46798127a6dd815b4bb99c2 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Sun, 1 Jul 2012 13:00:51 +0800 Subject: pwm: Convert pwm-tegra to use devm_clk_get() Also return proper error in tegra_pwm_remove() if pwmchip_remove() fails. Signed-off-by: Axel Lin Signed-off-by: Thierry Reding --- drivers/pwm/pwm-tegra.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c index 472b74e8221a..02ce18d5e49a 100644 --- a/drivers/pwm/pwm-tegra.c +++ b/drivers/pwm/pwm-tegra.c @@ -194,7 +194,7 @@ static int tegra_pwm_probe(struct platform_device *pdev) platform_set_drvdata(pdev, pwm); - pwm->clk = clk_get(&pdev->dev, NULL); + pwm->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(pwm->clk)) return PTR_ERR(pwm->clk); @@ -206,7 +206,6 @@ static int tegra_pwm_probe(struct platform_device *pdev) ret = pwmchip_add(&pwm->chip); if (ret < 0) { dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); - clk_put(pwm->clk); return ret; } @@ -233,10 +232,7 @@ static int __devexit tegra_pwm_remove(struct platform_device *pdev) clk_disable_unprepare(pc->clk); } - pwmchip_remove(&pc->chip); - clk_put(pc->clk); - - return 0; + return pwmchip_remove(&pc->chip); } #ifdef CONFIG_OF -- cgit v1.2.3-59-g8ed1b From 6192fa87447ed3c6c4005be640a088fa8e5733f8 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Fri, 29 Jun 2012 21:32:15 +0800 Subject: pwm: Convert pwm-samsung to use devm_* APIs Signed-off-by: Axel Lin Signed-off-by: Thierry Reding --- drivers/pwm/pwm-samsung.c | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/drivers/pwm/pwm-samsung.c b/drivers/pwm/pwm-samsung.c index c40c37e968e6..35fa0e8b0f87 100644 --- a/drivers/pwm/pwm-samsung.c +++ b/drivers/pwm/pwm-samsung.c @@ -1,4 +1,4 @@ -/* arch/arm/plat-s3c/pwm.c +/* drivers/pwm/pwm-samsung.c * * Copyright (c) 2007 Ben Dooks * Copyright (c) 2008 Simtec Electronics @@ -215,7 +215,7 @@ static int s3c_pwm_probe(struct platform_device *pdev) return -ENXIO; } - s3c = kzalloc(sizeof(*s3c), GFP_KERNEL); + s3c = devm_kzalloc(&pdev->dev, sizeof(*s3c), GFP_KERNEL); if (s3c == NULL) { dev_err(dev, "failed to allocate pwm_device\n"); return -ENOMEM; @@ -227,18 +227,16 @@ static int s3c_pwm_probe(struct platform_device *pdev) s3c->chip.base = -1; s3c->chip.npwm = 1; - s3c->clk = clk_get(dev, "pwm-tin"); + s3c->clk = devm_clk_get(dev, "pwm-tin"); if (IS_ERR(s3c->clk)) { dev_err(dev, "failed to get pwm tin clk\n"); - ret = PTR_ERR(s3c->clk); - goto err_alloc; + return PTR_ERR(s3c->clk); } - s3c->clk_div = clk_get(dev, "pwm-tdiv"); + s3c->clk_div = devm_clk_get(dev, "pwm-tdiv"); if (IS_ERR(s3c->clk_div)) { dev_err(dev, "failed to get pwm tdiv clk\n"); - ret = PTR_ERR(s3c->clk_div); - goto err_clk_tin; + return PTR_ERR(s3c->clk_div); } clk_enable(s3c->clk); @@ -272,13 +270,6 @@ static int s3c_pwm_probe(struct platform_device *pdev) err_clk_tdiv: clk_disable(s3c->clk_div); clk_disable(s3c->clk); - clk_put(s3c->clk_div); - - err_clk_tin: - clk_put(s3c->clk); - - err_alloc: - kfree(s3c); return ret; } @@ -293,9 +284,6 @@ static int __devexit s3c_pwm_remove(struct platform_device *pdev) clk_disable(s3c->clk_div); clk_disable(s3c->clk); - clk_put(s3c->clk_div); - clk_put(s3c->clk); - kfree(s3c); return 0; } -- cgit v1.2.3-59-g8ed1b From 2437b0d95c609365ce88039b96a1c020af71c6dc Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Fri, 6 Jul 2012 14:43:50 +0530 Subject: pwm: Use pr_* functions in pwm-samsung.c file Replace printk with pr_* functions to avoid checkpatch warnings. Signed-off-by: Sachin Kamat Signed-off-by: Thierry Reding --- drivers/pwm/pwm-samsung.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/pwm/pwm-samsung.c b/drivers/pwm/pwm-samsung.c index 35fa0e8b0f87..d10386528c9c 100644 --- a/drivers/pwm/pwm-samsung.c +++ b/drivers/pwm/pwm-samsung.c @@ -11,6 +11,8 @@ * the Free Software Foundation; either version 2 of the License. */ +#define pr_fmt(fmt) "pwm-samsung: " fmt + #include #include #include @@ -340,13 +342,13 @@ static int __init pwm_init(void) clk_scaler[1] = clk_get(NULL, "pwm-scaler1"); if (IS_ERR(clk_scaler[0]) || IS_ERR(clk_scaler[1])) { - printk(KERN_ERR "%s: failed to get scaler clocks\n", __func__); + pr_err("failed to get scaler clocks\n"); return -EINVAL; } ret = platform_driver_register(&s3c_pwm_driver); if (ret) - printk(KERN_ERR "%s: failed to add pwm driver\n", __func__); + pr_err("failed to add pwm driver\n"); return ret; } -- cgit v1.2.3-59-g8ed1b From 9fb978b12f9cc2b4df428764273d96877d0a262d Mon Sep 17 00:00:00 2001 From: Alexandre Courbot Date: Mon, 9 Jul 2012 15:04:23 +0900 Subject: pwm_backlight: pass correct brightness to callback pwm_backlight_update_status calls the notify() and notify_after() callbacks before and after applying the new PWM settings. However, if brightness levels are used, the brightness value will be changed from the index into the levels array to the PWM duty cycle length before being passed to notify_after(), which results in inconsistent behavior. Signed-off-by: Alexandre Courbot Signed-off-by: Thierry Reding --- drivers/video/backlight/pwm_bl.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/video/backlight/pwm_bl.c b/drivers/video/backlight/pwm_bl.c index 057389d69a51..995f0164c9b0 100644 --- a/drivers/video/backlight/pwm_bl.c +++ b/drivers/video/backlight/pwm_bl.c @@ -54,14 +54,18 @@ static int pwm_backlight_update_status(struct backlight_device *bl) pwm_config(pb->pwm, 0, pb->period); pwm_disable(pb->pwm); } else { + int duty_cycle; + if (pb->levels) { - brightness = pb->levels[brightness]; + duty_cycle = pb->levels[brightness]; max = pb->levels[max]; + } else { + duty_cycle = brightness; } - brightness = pb->lth_brightness + - (brightness * (pb->period - pb->lth_brightness) / max); - pwm_config(pb->pwm, brightness, pb->period); + duty_cycle = pb->lth_brightness + + (duty_cycle * (pb->period - pb->lth_brightness) / max); + pwm_config(pb->pwm, duty_cycle, pb->period); pwm_enable(pb->pwm); } -- cgit v1.2.3-59-g8ed1b From 2132fa8d95bc13b8b0e307553b04ee3517762ebe Mon Sep 17 00:00:00 2001 From: Alexandre Pereira da Silva Date: Tue, 10 Jul 2012 11:38:10 -0300 Subject: pwm: add lpc32xx PWM support Add lpc32xx SOC PWM driver. Signed-off-by: Alexandre Pereira da Silva Acked-by: Roland Stigge Signed-off-by: Thierry Reding --- .../devicetree/bindings/pwm/lpc32xx-pwm.txt | 12 ++ drivers/pwm/Kconfig | 10 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-lpc32xx.c | 148 +++++++++++++++++++++ 4 files changed, 171 insertions(+) create mode 100644 Documentation/devicetree/bindings/pwm/lpc32xx-pwm.txt create mode 100644 drivers/pwm/pwm-lpc32xx.c diff --git a/Documentation/devicetree/bindings/pwm/lpc32xx-pwm.txt b/Documentation/devicetree/bindings/pwm/lpc32xx-pwm.txt new file mode 100644 index 000000000000..cfe1db3bb6e9 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/lpc32xx-pwm.txt @@ -0,0 +1,12 @@ +LPC32XX PWM controller + +Required properties: +- compatible: should be "nxp,lpc3220-pwm" +- reg: physical base address and length of the controller's registers + +Examples: + +pwm@0x4005C000 { + compatible = "nxp,lpc3220-pwm"; + reg = <0x4005C000 0x8>; +}; diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 0b2800fc1ca7..94e176e26f4c 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -28,6 +28,16 @@ config PWM_IMX To compile this driver as a module, choose M here: the module will be called pwm-imx. +config PWM_LPC32XX + tristate "LPC32XX PWM support" + depends on ARCH_LPC32XX + help + Generic PWM framework driver for LPC32XX. The LPC32XX SOC has two + PWM controllers. + + To compile this driver as a module, choose M here: the module + will be called pwm-lpc32xx. + config PWM_MXS tristate "Freescale MXS PWM support" depends on ARCH_MXS && OF diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index cec250091cff..54597020350f 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -1,6 +1,7 @@ obj-$(CONFIG_PWM) += core.o obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o obj-$(CONFIG_PWM_IMX) += pwm-imx.o +obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.o obj-$(CONFIG_PWM_MXS) += pwm-mxs.o obj-$(CONFIG_PWM_PXA) += pwm-pxa.o obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o diff --git a/drivers/pwm/pwm-lpc32xx.c b/drivers/pwm/pwm-lpc32xx.c new file mode 100644 index 000000000000..adb87f0c1633 --- /dev/null +++ b/drivers/pwm/pwm-lpc32xx.c @@ -0,0 +1,148 @@ +/* + * Copyright 2012 Alexandre Pereira da Silva + * + * 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 + * the Free Software Foundation; version 2. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct lpc32xx_pwm_chip { + struct pwm_chip chip; + struct clk *clk; + void __iomem *base; +}; + +#define PWM_ENABLE (1 << 31) +#define PWM_RELOADV(x) (((x) & 0xFF) << 8) +#define PWM_DUTY(x) ((x) & 0xFF) + +#define to_lpc32xx_pwm_chip(_chip) \ + container_of(_chip, struct lpc32xx_pwm_chip, chip) + +static int lpc32xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct lpc32xx_pwm_chip *lpc32xx = to_lpc32xx_pwm_chip(chip); + unsigned long long c; + int period_cycles, duty_cycles; + + c = clk_get_rate(lpc32xx->clk) / 256; + c = c * period_ns; + do_div(c, NSEC_PER_SEC); + + /* Handle high and low extremes */ + if (c == 0) + c = 1; + if (c > 255) + c = 0; /* 0 set division by 256 */ + period_cycles = c; + + c = 256 * duty_ns; + do_div(c, period_ns); + duty_cycles = c; + + writel(PWM_ENABLE | PWM_RELOADV(period_cycles) | PWM_DUTY(duty_cycles), + lpc32xx->base + (pwm->hwpwm << 2)); + + return 0; +} + +static int lpc32xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct lpc32xx_pwm_chip *lpc32xx = to_lpc32xx_pwm_chip(chip); + + return clk_enable(lpc32xx->clk); +} + +static void lpc32xx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct lpc32xx_pwm_chip *lpc32xx = to_lpc32xx_pwm_chip(chip); + + writel(0, lpc32xx->base + (pwm->hwpwm << 2)); + clk_disable(lpc32xx->clk); +} + +static const struct pwm_ops lpc32xx_pwm_ops = { + .config = lpc32xx_pwm_config, + .enable = lpc32xx_pwm_enable, + .disable = lpc32xx_pwm_disable, + .owner = THIS_MODULE, +}; + +static int lpc32xx_pwm_probe(struct platform_device *pdev) +{ + struct lpc32xx_pwm_chip *lpc32xx; + struct resource *res; + int ret; + + lpc32xx = devm_kzalloc(&pdev->dev, sizeof(*lpc32xx), GFP_KERNEL); + if (!lpc32xx) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + + lpc32xx->base = devm_request_and_ioremap(&pdev->dev, res); + if (!lpc32xx->base) + return -EADDRNOTAVAIL; + + lpc32xx->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(lpc32xx->clk)) + return PTR_ERR(lpc32xx->clk); + + lpc32xx->chip.dev = &pdev->dev; + lpc32xx->chip.ops = &lpc32xx_pwm_ops; + lpc32xx->chip.npwm = 2; + + ret = pwmchip_add(&lpc32xx->chip); + if (ret < 0) { + dev_err(&pdev->dev, "failed to add PWM chip, error %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, lpc32xx); + + return 0; +} + +static int __devexit lpc32xx_pwm_remove(struct platform_device *pdev) +{ + struct lpc32xx_pwm_chip *lpc32xx = platform_get_drvdata(pdev); + + clk_disable(lpc32xx->clk); + return pwmchip_remove(&lpc32xx->chip); +} + +static struct of_device_id lpc32xx_pwm_dt_ids[] = { + { .compatible = "nxp,lpc3220-pwm", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, lpc32xx_pwm_dt_ids); + +static struct platform_driver lpc32xx_pwm_driver = { + .driver = { + .name = "lpc32xx-pwm", + .of_match_table = of_match_ptr(lpc32xx_pwm_dt_ids), + }, + .probe = lpc32xx_pwm_probe, + .remove = __devexit_p(lpc32xx_pwm_remove), +}; +module_platform_driver(lpc32xx_pwm_driver); + +MODULE_ALIAS("platform:lpc32xx-pwm"); +MODULE_AUTHOR("Alexandre Pereira da Silva "); +MODULE_DESCRIPTION("LPC32XX PWM Driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-59-g8ed1b From d295b129762bf6b2b7541243f496e363580de4a2 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 11 Jul 2012 08:05:05 +0200 Subject: pwm: fix used-uninitialized warning in pwm_get() Some versions of GCC don't seem no notice that the initialization of the index variable is tied to that of the chip variable and falsely report it as potentially being used uninitialized. However, to save anybody else from tripping over this, we now initialize the index variable unconditionally. Originally-by: Stephen Warren Signed-off-by: Thierry Reding --- drivers/pwm/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index dbab53005da8..ecb76909e946 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -529,9 +529,9 @@ struct pwm_device *pwm_get(struct device *dev, const char *con_id) struct pwm_device *pwm = ERR_PTR(-EPROBE_DEFER); const char *dev_id = dev ? dev_name(dev): NULL; struct pwm_chip *chip = NULL; + unsigned int index = 0; unsigned int best = 0; struct pwm_lookup *p; - unsigned int index; unsigned int match; /* look up via DT first */ -- cgit v1.2.3-59-g8ed1b From 8e0cb05b3b758885aab09883adf189f8dd8402b2 Mon Sep 17 00:00:00 2001 From: "Philip, Avinash" Date: Wed, 25 Jul 2012 16:58:18 +0530 Subject: pwm: pwm-tiecap: PWM driver support for ECAP APWM ECAP hardware on AM33XX SOC supports auxiliary PWM (APWM) feature. This commit adds PWM driver support for ECAP hardware on AM33XX SOC. In the ECAP hardware, each PWM pin can also be configured to be in capture mode. Current implementation only supports PWM mode of operation. Also, hardware supports sync between multiple PWM pins but the driver supports simple independent PWM functionality. Reviewed-by: Vaibhav Bedia Signed-off-by: Philip, Avinash Signed-off-by: Thierry Reding --- drivers/pwm/Kconfig | 10 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-tiecap.c | 232 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 243 insertions(+) create mode 100644 drivers/pwm/pwm-tiecap.c diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 94e176e26f4c..2b7db5d1f852 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -76,6 +76,16 @@ config PWM_TEGRA To compile this driver as a module, choose M here: the module will be called pwm-tegra. +config PWM_TIECAP + tristate "ECAP PWM support" + depends on SOC_AM33XX + help + PWM driver support for the ECAP APWM controller found on AM33XX + TI SOC + + To compile this driver as a module, choose M here: the module + will be called pwm-tiecap. + config PWM_VT8500 tristate "vt8500 pwm support" depends on ARCH_VT8500 diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 54597020350f..35fee430b9dd 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -6,4 +6,5 @@ obj-$(CONFIG_PWM_MXS) += pwm-mxs.o obj-$(CONFIG_PWM_PXA) += pwm-pxa.o obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o +obj-$(CONFIG_PWM_TIECAP) += pwm-tiecap.o obj-$(CONFIG_PWM_VT8500) += pwm-vt8500.o diff --git a/drivers/pwm/pwm-tiecap.c b/drivers/pwm/pwm-tiecap.c new file mode 100644 index 000000000000..3c2ad284ee3e --- /dev/null +++ b/drivers/pwm/pwm-tiecap.c @@ -0,0 +1,232 @@ +/* + * ECAP PWM driver + * + * Copyright (C) 2012 Texas Instruments, Inc. - http://www.ti.com/ + * + * 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +/* ECAP registers and bits definitions */ +#define CAP1 0x08 +#define CAP2 0x0C +#define CAP3 0x10 +#define CAP4 0x14 +#define ECCTL2 0x2A +#define ECCTL2_APWM_MODE BIT(9) +#define ECCTL2_SYNC_SEL_DISA (BIT(7) | BIT(6)) +#define ECCTL2_TSCTR_FREERUN BIT(4) + +struct ecap_pwm_chip { + struct pwm_chip chip; + unsigned int clk_rate; + void __iomem *mmio_base; +}; + +static inline struct ecap_pwm_chip *to_ecap_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct ecap_pwm_chip, chip); +} + +/* + * period_ns = 10^9 * period_cycles / PWM_CLK_RATE + * duty_ns = 10^9 * duty_cycles / PWM_CLK_RATE + */ +static int ecap_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip); + unsigned long long c; + unsigned long period_cycles, duty_cycles; + unsigned int reg_val; + + if (period_ns < 0 || duty_ns < 0 || period_ns > NSEC_PER_SEC) + return -ERANGE; + + c = pc->clk_rate; + c = c * period_ns; + do_div(c, NSEC_PER_SEC); + period_cycles = (unsigned long)c; + + if (period_cycles < 1) { + period_cycles = 1; + duty_cycles = 1; + } else { + c = pc->clk_rate; + c = c * duty_ns; + do_div(c, NSEC_PER_SEC); + duty_cycles = (unsigned long)c; + } + + pm_runtime_get_sync(pc->chip.dev); + + reg_val = readw(pc->mmio_base + ECCTL2); + + /* Configure APWM mode & disable sync option */ + reg_val |= ECCTL2_APWM_MODE | ECCTL2_SYNC_SEL_DISA; + + writew(reg_val, pc->mmio_base + ECCTL2); + + if (!test_bit(PWMF_ENABLED, &pwm->flags)) { + /* Update active registers if not running */ + writel(duty_cycles, pc->mmio_base + CAP2); + writel(period_cycles, pc->mmio_base + CAP1); + } else { + /* + * Update shadow registers to configure period and + * compare values. This helps current PWM period to + * complete on reconfiguring + */ + writel(duty_cycles, pc->mmio_base + CAP4); + writel(period_cycles, pc->mmio_base + CAP3); + } + + pm_runtime_put_sync(pc->chip.dev); + return 0; +} + +static int ecap_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip); + unsigned int reg_val; + + /* Leave clock enabled on enabling PWM */ + pm_runtime_get_sync(pc->chip.dev); + + /* + * Enable 'Free run Time stamp counter mode' to start counter + * and 'APWM mode' to enable APWM output + */ + reg_val = readw(pc->mmio_base + ECCTL2); + reg_val |= ECCTL2_TSCTR_FREERUN | ECCTL2_APWM_MODE; + writew(reg_val, pc->mmio_base + ECCTL2); + return 0; +} + +static void ecap_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip); + unsigned int reg_val; + + /* + * Disable 'Free run Time stamp counter mode' to stop counter + * and 'APWM mode' to put APWM output to low + */ + reg_val = readw(pc->mmio_base + ECCTL2); + reg_val &= ~(ECCTL2_TSCTR_FREERUN | ECCTL2_APWM_MODE); + writew(reg_val, pc->mmio_base + ECCTL2); + + /* Disable clock on PWM disable */ + pm_runtime_put_sync(pc->chip.dev); +} + +static void ecap_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + if (test_bit(PWMF_ENABLED, &pwm->flags)) { + dev_warn(chip->dev, "Removing PWM device without disabling\n"); + pm_runtime_put_sync(chip->dev); + } +} + +static const struct pwm_ops ecap_pwm_ops = { + .free = ecap_pwm_free, + .config = ecap_pwm_config, + .enable = ecap_pwm_enable, + .disable = ecap_pwm_disable, + .owner = THIS_MODULE, +}; + +static int __devinit ecap_pwm_probe(struct platform_device *pdev) +{ + int ret; + struct resource *r; + struct clk *clk; + struct ecap_pwm_chip *pc; + + pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); + if (!pc) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + clk = devm_clk_get(&pdev->dev, "fck"); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "failed to get clock\n"); + return PTR_ERR(clk); + } + + pc->clk_rate = clk_get_rate(clk); + if (!pc->clk_rate) { + dev_err(&pdev->dev, "failed to get clock rate\n"); + return -EINVAL; + } + + pc->chip.dev = &pdev->dev; + pc->chip.ops = &ecap_pwm_ops; + pc->chip.base = -1; + pc->chip.npwm = 1; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + dev_err(&pdev->dev, "no memory resource defined\n"); + return -ENODEV; + } + + pc->mmio_base = devm_request_and_ioremap(&pdev->dev, r); + if (!pc->mmio_base) { + dev_err(&pdev->dev, "failed to ioremap() registers\n"); + return -EADDRNOTAVAIL; + } + + ret = pwmchip_add(&pc->chip); + if (ret < 0) { + dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); + return ret; + } + + pm_runtime_enable(&pdev->dev); + platform_set_drvdata(pdev, pc); + return 0; +} + +static int __devexit ecap_pwm_remove(struct platform_device *pdev) +{ + struct ecap_pwm_chip *pc = platform_get_drvdata(pdev); + + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + return pwmchip_remove(&pc->chip); +} + +static struct platform_driver ecap_pwm_driver = { + .driver = { + .name = "ecap", + }, + .probe = ecap_pwm_probe, + .remove = __devexit_p(ecap_pwm_remove), +}; + +module_platform_driver(ecap_pwm_driver); + +MODULE_DESCRIPTION("ECAP PWM driver"); +MODULE_AUTHOR("Texas Instruments"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-59-g8ed1b From 19891b20e7c275feb92d669f4b1879861f7e8c25 Mon Sep 17 00:00:00 2001 From: "Philip, Avinash" Date: Wed, 25 Jul 2012 16:58:19 +0530 Subject: pwm: pwm-tiehrpwm: PWM driver support for EHRPWM Enhanced high resolution PWM module (EHRPWM) hardware can be used to generate PWM output over 2 channels. This commit adds PWM driver support for EHRPWM device present on AM33XX SOC. Current implementation supports simple PWM functionality. Reviewed-by: Vaibhav Bedia Signed-off-by: Philip, Avinash Signed-off-by: Thierry Reding --- drivers/pwm/Kconfig | 10 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-tiehrpwm.c | 411 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 422 insertions(+) create mode 100644 drivers/pwm/pwm-tiehrpwm.c diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 2b7db5d1f852..8fc3808d7a3e 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -86,6 +86,16 @@ config PWM_TIECAP To compile this driver as a module, choose M here: the module will be called pwm-tiecap. +config PWM_TIEHRPWM + tristate "EHRPWM PWM support" + depends on SOC_AM33XX + help + PWM driver support for the EHRPWM controller found on AM33XX + TI SOC + + To compile this driver as a module, choose M here: the module + will be called pwm-tiehrpwm. + config PWM_VT8500 tristate "vt8500 pwm support" depends on ARCH_VT8500 diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 35fee430b9dd..e4b2c898964d 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -7,4 +7,5 @@ obj-$(CONFIG_PWM_PXA) += pwm-pxa.o obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o obj-$(CONFIG_PWM_TIECAP) += pwm-tiecap.o +obj-$(CONFIG_PWM_TIEHRPWM) += pwm-tiehrpwm.o obj-$(CONFIG_PWM_VT8500) += pwm-vt8500.o diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c new file mode 100644 index 000000000000..010d232cb0c8 --- /dev/null +++ b/drivers/pwm/pwm-tiehrpwm.c @@ -0,0 +1,411 @@ +/* + * EHRPWM PWM driver + * + * Copyright (C) 2012 Texas Instruments, Inc. - http://www.ti.com/ + * + * 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +/* EHRPWM registers and bits definitions */ + +/* Time base module registers */ +#define TBCTL 0x00 +#define TBPRD 0x0A + +#define TBCTL_RUN_MASK (BIT(15) | BIT(14)) +#define TBCTL_STOP_NEXT 0 +#define TBCTL_STOP_ON_CYCLE BIT(14) +#define TBCTL_FREE_RUN (BIT(15) | BIT(14)) +#define TBCTL_PRDLD_MASK BIT(3) +#define TBCTL_PRDLD_SHDW 0 +#define TBCTL_PRDLD_IMDT BIT(3) +#define TBCTL_CLKDIV_MASK (BIT(12) | BIT(11) | BIT(10) | BIT(9) | \ + BIT(8) | BIT(7)) +#define TBCTL_CTRMODE_MASK (BIT(1) | BIT(0)) +#define TBCTL_CTRMODE_UP 0 +#define TBCTL_CTRMODE_DOWN BIT(0) +#define TBCTL_CTRMODE_UPDOWN BIT(1) +#define TBCTL_CTRMODE_FREEZE (BIT(1) | BIT(0)) + +#define TBCTL_HSPCLKDIV_SHIFT 7 +#define TBCTL_CLKDIV_SHIFT 10 + +#define CLKDIV_MAX 7 +#define HSPCLKDIV_MAX 7 +#define PERIOD_MAX 0xFFFF + +/* compare module registers */ +#define CMPA 0x12 +#define CMPB 0x14 + +/* Action qualifier module registers */ +#define AQCTLA 0x16 +#define AQCTLB 0x18 +#define AQSFRC 0x1A +#define AQCSFRC 0x1C + +#define AQCTL_CBU_MASK (BIT(9) | BIT(8)) +#define AQCTL_CBU_FRCLOW BIT(8) +#define AQCTL_CBU_FRCHIGH BIT(9) +#define AQCTL_CBU_FRCTOGGLE (BIT(9) | BIT(8)) +#define AQCTL_CAU_MASK (BIT(5) | BIT(4)) +#define AQCTL_CAU_FRCLOW BIT(4) +#define AQCTL_CAU_FRCHIGH BIT(5) +#define AQCTL_CAU_FRCTOGGLE (BIT(5) | BIT(4)) +#define AQCTL_PRD_MASK (BIT(3) | BIT(2)) +#define AQCTL_PRD_FRCLOW BIT(2) +#define AQCTL_PRD_FRCHIGH BIT(3) +#define AQCTL_PRD_FRCTOGGLE (BIT(3) | BIT(2)) +#define AQCTL_ZRO_MASK (BIT(1) | BIT(0)) +#define AQCTL_ZRO_FRCLOW BIT(0) +#define AQCTL_ZRO_FRCHIGH BIT(1) +#define AQCTL_ZRO_FRCTOGGLE (BIT(1) | BIT(0)) + +#define AQSFRC_RLDCSF_MASK (BIT(7) | BIT(6)) +#define AQSFRC_RLDCSF_ZRO 0 +#define AQSFRC_RLDCSF_PRD BIT(6) +#define AQSFRC_RLDCSF_ZROPRD BIT(7) +#define AQSFRC_RLDCSF_IMDT (BIT(7) | BIT(6)) + +#define AQCSFRC_CSFB_MASK (BIT(3) | BIT(2)) +#define AQCSFRC_CSFB_FRCDIS 0 +#define AQCSFRC_CSFB_FRCLOW BIT(2) +#define AQCSFRC_CSFB_FRCHIGH BIT(3) +#define AQCSFRC_CSFB_DISSWFRC (BIT(3) | BIT(2)) +#define AQCSFRC_CSFA_MASK (BIT(1) | BIT(0)) +#define AQCSFRC_CSFA_FRCDIS 0 +#define AQCSFRC_CSFA_FRCLOW BIT(0) +#define AQCSFRC_CSFA_FRCHIGH BIT(1) +#define AQCSFRC_CSFA_DISSWFRC (BIT(1) | BIT(0)) + +#define NUM_PWM_CHANNEL 2 /* EHRPWM channels */ + +struct ehrpwm_pwm_chip { + struct pwm_chip chip; + unsigned int clk_rate; + void __iomem *mmio_base; +}; + +static inline struct ehrpwm_pwm_chip *to_ehrpwm_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct ehrpwm_pwm_chip, chip); +} + +static void ehrpwm_write(void *base, int offset, unsigned int val) +{ + writew(val & 0xFFFF, base + offset); +} + +static void ehrpwm_modify(void *base, int offset, + unsigned short mask, unsigned short val) +{ + unsigned short regval; + + regval = readw(base + offset); + regval &= ~mask; + regval |= val & mask; + writew(regval, base + offset); +} + +/** + * set_prescale_div - Set up the prescaler divider function + * @rqst_prescaler: prescaler value min + * @prescale_div: prescaler value set + * @tb_clk_div: Time Base Control prescaler bits + */ +static int set_prescale_div(unsigned long rqst_prescaler, + unsigned short *prescale_div, unsigned short *tb_clk_div) +{ + unsigned int clkdiv, hspclkdiv; + + for (clkdiv = 0; clkdiv <= CLKDIV_MAX; clkdiv++) { + for (hspclkdiv = 0; hspclkdiv <= HSPCLKDIV_MAX; hspclkdiv++) { + + /* + * calculations for prescaler value : + * prescale_div = HSPCLKDIVIDER * CLKDIVIDER. + * HSPCLKDIVIDER = 2 ** hspclkdiv + * CLKDIVIDER = (1), if clkdiv == 0 *OR* + * (2 * clkdiv), if clkdiv != 0 + * + * Configure prescale_div value such that period + * register value is less than 65535. + */ + + *prescale_div = (1 << clkdiv) * + (hspclkdiv ? (hspclkdiv * 2) : 1); + if (*prescale_div > rqst_prescaler) { + *tb_clk_div = (clkdiv << TBCTL_CLKDIV_SHIFT) | + (hspclkdiv << TBCTL_HSPCLKDIV_SHIFT); + return 0; + } + } + } + return 1; +} + +static void configure_chans(struct ehrpwm_pwm_chip *pc, int chan, + unsigned long duty_cycles) +{ + int cmp_reg, aqctl_reg; + unsigned short aqctl_val, aqctl_mask; + + /* + * Channels can be configured from action qualifier module. + * Channel 0 configured with compare A register and for + * up-counter mode. + * Channel 1 configured with compare B register and for + * up-counter mode. + */ + if (chan == 1) { + aqctl_reg = AQCTLB; + cmp_reg = CMPB; + /* Configure PWM Low from compare B value */ + aqctl_val = AQCTL_CBU_FRCLOW; + aqctl_mask = AQCTL_CBU_MASK; + } else { + cmp_reg = CMPA; + aqctl_reg = AQCTLA; + /* Configure PWM Low from compare A value*/ + aqctl_val = AQCTL_CAU_FRCLOW; + aqctl_mask = AQCTL_CAU_MASK; + } + + /* Configure PWM High from period value and zero value */ + aqctl_val |= AQCTL_PRD_FRCHIGH | AQCTL_ZRO_FRCHIGH; + aqctl_mask |= AQCTL_PRD_MASK | AQCTL_ZRO_MASK; + ehrpwm_modify(pc->mmio_base, aqctl_reg, aqctl_mask, aqctl_val); + + ehrpwm_write(pc->mmio_base, cmp_reg, duty_cycles); +} + +/* + * period_ns = 10^9 * (ps_divval * period_cycles) / PWM_CLK_RATE + * duty_ns = 10^9 * (ps_divval * duty_cycles) / PWM_CLK_RATE + */ +static int ehrpwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip); + unsigned long long c; + unsigned long period_cycles, duty_cycles; + unsigned short ps_divval, tb_divval; + + if (period_ns < 0 || duty_ns < 0 || period_ns > NSEC_PER_SEC) + return -ERANGE; + + c = pc->clk_rate; + c = c * period_ns; + do_div(c, NSEC_PER_SEC); + period_cycles = (unsigned long)c; + + if (period_cycles < 1) { + period_cycles = 1; + duty_cycles = 1; + } else { + c = pc->clk_rate; + c = c * duty_ns; + do_div(c, NSEC_PER_SEC); + duty_cycles = (unsigned long)c; + } + + /* Configure clock prescaler to support Low frequency PWM wave */ + if (set_prescale_div(period_cycles/PERIOD_MAX, &ps_divval, + &tb_divval)) { + dev_err(chip->dev, "Unsupported values\n"); + return -EINVAL; + } + + pm_runtime_get_sync(chip->dev); + + /* Update clock prescaler values */ + ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_CLKDIV_MASK, tb_divval); + + /* Update period & duty cycle with presacler division */ + period_cycles = period_cycles / ps_divval; + duty_cycles = duty_cycles / ps_divval; + + /* Configure shadow loading on Period register */ + ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_PRDLD_MASK, TBCTL_PRDLD_SHDW); + + ehrpwm_write(pc->mmio_base, TBPRD, period_cycles); + + /* Configure ehrpwm counter for up-count mode */ + ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_CTRMODE_MASK, + TBCTL_CTRMODE_UP); + + /* Configure the channel for duty cycle */ + configure_chans(pc, pwm->hwpwm, duty_cycles); + pm_runtime_put_sync(chip->dev); + return 0; +} + +static int ehrpwm_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip); + unsigned short aqcsfrc_val, aqcsfrc_mask; + + /* Leave clock enabled on enabling PWM */ + pm_runtime_get_sync(chip->dev); + + /* Disabling Action Qualifier on PWM output */ + if (pwm->hwpwm) { + aqcsfrc_val = AQCSFRC_CSFB_FRCDIS; + aqcsfrc_mask = AQCSFRC_CSFB_MASK; + } else { + aqcsfrc_val = AQCSFRC_CSFA_FRCDIS; + aqcsfrc_mask = AQCSFRC_CSFA_MASK; + } + + /* Changes to shadow mode */ + ehrpwm_modify(pc->mmio_base, AQSFRC, AQSFRC_RLDCSF_MASK, + AQSFRC_RLDCSF_ZRO); + + ehrpwm_modify(pc->mmio_base, AQCSFRC, aqcsfrc_mask, aqcsfrc_val); + + /* Enable time counter for free_run */ + ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_RUN_MASK, TBCTL_FREE_RUN); + return 0; +} + +static void ehrpwm_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip); + unsigned short aqcsfrc_val, aqcsfrc_mask; + + /* Action Qualifier puts PWM output low forcefully */ + if (pwm->hwpwm) { + aqcsfrc_val = AQCSFRC_CSFB_FRCLOW; + aqcsfrc_mask = AQCSFRC_CSFB_MASK; + } else { + aqcsfrc_val = AQCSFRC_CSFA_FRCLOW; + aqcsfrc_mask = AQCSFRC_CSFA_MASK; + } + + /* + * Changes to immediate action on Action Qualifier. This puts + * Action Qualifier control on PWM output from next TBCLK + */ + ehrpwm_modify(pc->mmio_base, AQSFRC, AQSFRC_RLDCSF_MASK, + AQSFRC_RLDCSF_IMDT); + + ehrpwm_modify(pc->mmio_base, AQCSFRC, aqcsfrc_mask, aqcsfrc_val); + + /* Stop Time base counter */ + ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_RUN_MASK, TBCTL_STOP_NEXT); + + /* Disable clock on PWM disable */ + pm_runtime_put_sync(chip->dev); +} + +static void ehrpwm_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + if (test_bit(PWMF_ENABLED, &pwm->flags)) { + dev_warn(chip->dev, "Removing PWM device without disabling\n"); + pm_runtime_put_sync(chip->dev); + } +} + +static const struct pwm_ops ehrpwm_pwm_ops = { + .free = ehrpwm_pwm_free, + .config = ehrpwm_pwm_config, + .enable = ehrpwm_pwm_enable, + .disable = ehrpwm_pwm_disable, + .owner = THIS_MODULE, +}; + +static int __devinit ehrpwm_pwm_probe(struct platform_device *pdev) +{ + int ret; + struct resource *r; + struct clk *clk; + struct ehrpwm_pwm_chip *pc; + + pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); + if (!pc) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + clk = devm_clk_get(&pdev->dev, "fck"); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "failed to get clock\n"); + return PTR_ERR(clk); + } + + pc->clk_rate = clk_get_rate(clk); + if (!pc->clk_rate) { + dev_err(&pdev->dev, "failed to get clock rate\n"); + return -EINVAL; + } + + pc->chip.dev = &pdev->dev; + pc->chip.ops = &ehrpwm_pwm_ops; + pc->chip.base = -1; + pc->chip.npwm = NUM_PWM_CHANNEL; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + dev_err(&pdev->dev, "no memory resource defined\n"); + return -ENODEV; + } + + pc->mmio_base = devm_request_and_ioremap(&pdev->dev, r); + if (!pc->mmio_base) { + dev_err(&pdev->dev, "failed to ioremap() registers\n"); + return -EADDRNOTAVAIL; + } + + ret = pwmchip_add(&pc->chip); + if (ret < 0) { + dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); + return ret; + } + + pm_runtime_enable(&pdev->dev); + platform_set_drvdata(pdev, pc); + return 0; +} + +static int __devexit ehrpwm_pwm_remove(struct platform_device *pdev) +{ + struct ehrpwm_pwm_chip *pc = platform_get_drvdata(pdev); + + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + return pwmchip_remove(&pc->chip); +} + +static struct platform_driver ehrpwm_pwm_driver = { + .driver = { + .name = "ehrpwm", + }, + .probe = ehrpwm_pwm_probe, + .remove = __devexit_p(ehrpwm_pwm_remove), +}; + +module_platform_driver(ehrpwm_pwm_driver); + +MODULE_DESCRIPTION("EHRPWM PWM driver"); +MODULE_AUTHOR("Texas Instruments"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-59-g8ed1b