diff options
Diffstat (limited to 'drivers/pwm')
32 files changed, 9855 insertions, 0 deletions
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig new file mode 100644 index 00000000000..4ad7b89a4cb --- /dev/null +++ b/drivers/pwm/Kconfig @@ -0,0 +1,295 @@ +menuconfig PWM +	bool "Pulse-Width Modulation (PWM) Support" +	help +	  Generic Pulse-Width Modulation (PWM) support. + +	  In Pulse-Width Modulation, a variation of the width of pulses +	  in a rectangular pulse signal is used as a means to alter the +	  average power of the signal. Applications include efficient +	  power delivery and voltage regulation. In computer systems, +	  PWMs are commonly used to control fans or the brightness of +	  display backlights. + +	  This framework provides a generic interface to PWM devices +	  within the Linux kernel. On the driver side it provides an API +	  to register and unregister a PWM chip, an abstraction of a PWM +	  controller, that supports one or more PWM devices. Client +	  drivers can request PWM devices and use the generic framework +	  to configure as well as enable and disable them. + +	  This generic framework replaces the legacy PWM framework which +	  allows only a single driver implementing the required API. Not +	  all legacy implementations have been ported to the framework +	  yet. The framework provides an API that is backward compatible +	  with the legacy framework so that existing client drivers +	  continue to work as expected. + +	  If unsure, say no. + +if PWM + +config PWM_SYSFS +	bool +	default y if SYSFS + +config PWM_AB8500 +	tristate "AB8500 PWM support" +	depends on AB8500_CORE && ARCH_U8500 +	help +	  Generic PWM framework driver for Analog Baseband AB8500. + +	  To compile this driver as a module, choose M here: the module +	  will be called pwm-ab8500. + +config PWM_ATMEL +	tristate "Atmel PWM support" +	depends on ARCH_AT91 +	help +	  Generic PWM framework driver for Atmel SoC. + +	  To compile this driver as a module, choose M here: the module +	  will be called pwm-atmel. + +config PWM_ATMEL_TCB +	tristate "Atmel TC Block PWM support" +	depends on ATMEL_TCLIB && OF +	help +	  Generic PWM framework driver for Atmel Timer Counter Block. + +	  A Timer Counter Block provides 6 PWM devices grouped by 2. +	  Devices in a given group must have the same period. + +	  To compile this driver as a module, choose M here: the module +	  will be called pwm-atmel-tcb. + +config PWM_BCM_KONA +	tristate "Kona PWM support" +	depends on ARCH_BCM_MOBILE +	help +	  Generic PWM framework driver for Broadcom Kona PWM block. + +	  To compile this driver as a module, choose M here: the module +	  will be called pwm-bcm-kona. + +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_CLPS711X +	tristate "CLPS711X PWM support" +	depends on ARCH_CLPS711X || COMPILE_TEST +	help +	  Generic PWM framework driver for Cirrus Logic CLPS711X. + +	  To compile this driver as a module, choose M here: the module +	  will be called pwm-clps711x. + +config PWM_EP93XX +	tristate "Cirrus Logic EP93xx PWM support" +	depends on ARCH_EP93XX +	help +	  Generic PWM framework driver for Cirrus Logic EP93xx. + +	  To compile this driver as a module, choose M here: the module +	  will be called pwm-ep93xx. + +config PWM_FSL_FTM +	tristate "Freescale FlexTimer Module (FTM) PWM support" +	depends on OF +	help +	  Generic FTM PWM framework driver for Freescale VF610 and +	  Layerscape LS-1 SoCs. + +	  To compile this driver as a module, choose M here: the module +	  will be called pwm-fsl-ftm. + +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_JZ4740 +	tristate "Ingenic JZ4740 PWM support" +	depends on MACH_JZ4740 +	help +	  Generic PWM framework driver for Ingenic JZ4740 based +	  machines. + +	  To compile this driver as a module, choose M here: the module +	  will be called pwm-jz4740. + +config PWM_LP3943 +	tristate "TI/National Semiconductor LP3943 PWM support" +	depends on MFD_LP3943 +	help +	  Generic PWM framework driver for LP3943 which supports two PWM +	  channels. + +	  To compile this driver as a module, choose M here: the module +	  will be called pwm-lp3943. + +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_LPSS +	tristate "Intel LPSS PWM support" +	depends on ACPI +	help +	  Generic PWM framework driver for Intel Low Power Subsystem PWM +	  controller. + +	  To compile this driver as a module, choose M here: the module +	  will be called pwm-lpss. + +config PWM_MXS +	tristate "Freescale MXS PWM support" +	depends on ARCH_MXS && OF +	select STMP_DEVICE +	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_PCA9685 +	tristate "NXP PCA9685 PWM driver" +	depends on OF && I2C +	select REGMAP_I2C +	help +	  Generic PWM framework driver for NXP PCA9685 LED controller. + +	  To compile this driver as a module, choose M here: the module +	  will be called pwm-pca9685. + +config PWM_PUV3 +	tristate "PKUnity NetBook-0916 PWM support" +	depends on ARCH_PUV3 +	help +	  Generic PWM framework driver for PKUnity NetBook-0916. + +	  To compile this driver as a module, choose M here: the module +	  will be called pwm-puv3. + +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_RENESAS_TPU +	tristate "Renesas TPU PWM support" +	depends on ARCH_SHMOBILE || COMPILE_TEST +	depends on HAS_IOMEM +	help +	  This driver exposes the Timer Pulse Unit (TPU) PWM controller found +	  in Renesas chips through the PWM API. + +	  To compile this driver as a module, choose M here: the module +	  will be called pwm-renesas-tpu. + +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_SPEAR +	tristate "STMicroelectronics SPEAr PWM support" +	depends on PLAT_SPEAR +	depends on OF +	help +	  Generic PWM framework driver for the PWM controller on ST +	  SPEAr SoCs. + +	  To compile this driver as a module, choose M here: the module +	  will be called pwm-spear. + +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. + +config  PWM_TIECAP +	tristate "ECAP PWM support" +	depends on SOC_AM33XX || ARCH_DAVINCI_DA8XX +	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_TIEHRPWM +	tristate "EHRPWM PWM support" +	depends on SOC_AM33XX || ARCH_DAVINCI_DA8XX +	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_TIPWMSS +	bool +	default y if SOC_AM33XX && (PWM_TIECAP || PWM_TIEHRPWM) +	help +	  PWM Subsystem driver support for AM33xx SOC. + +	  PWM submodules require PWM config space access from submodule +	  drivers and require common parent driver support. + +config PWM_TWL +	tristate "TWL4030/6030 PWM support" +	depends on TWL4030_CORE +	help +	  Generic PWM framework driver for TWL4030/6030. + +	  To compile this driver as a module, choose M here: the module +	  will be called pwm-twl. + +config PWM_TWL_LED +	tristate "TWL4030/6030 PWM support for LED drivers" +	depends on TWL4030_CORE +	help +	  Generic PWM framework driver for TWL4030/6030 LED terminals. + +	  To compile this driver as a module, choose M here: the module +	  will be called pwm-twl-led. + +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 new file mode 100644 index 00000000000..5c86a19d5d3 --- /dev/null +++ b/drivers/pwm/Makefile @@ -0,0 +1,29 @@ +obj-$(CONFIG_PWM)		+= core.o +obj-$(CONFIG_PWM_SYSFS)		+= sysfs.o +obj-$(CONFIG_PWM_AB8500)	+= pwm-ab8500.o +obj-$(CONFIG_PWM_ATMEL)		+= pwm-atmel.o +obj-$(CONFIG_PWM_ATMEL_TCB)	+= pwm-atmel-tcb.o +obj-$(CONFIG_PWM_BCM_KONA)	+= pwm-bcm-kona.o +obj-$(CONFIG_PWM_BFIN)		+= pwm-bfin.o +obj-$(CONFIG_PWM_CLPS711X)	+= pwm-clps711x.o +obj-$(CONFIG_PWM_EP93XX)	+= pwm-ep93xx.o +obj-$(CONFIG_PWM_FSL_FTM)	+= pwm-fsl-ftm.o +obj-$(CONFIG_PWM_IMX)		+= pwm-imx.o +obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o +obj-$(CONFIG_PWM_LP3943)	+= pwm-lp3943.o +obj-$(CONFIG_PWM_LPC32XX)	+= pwm-lpc32xx.o +obj-$(CONFIG_PWM_LPSS)		+= pwm-lpss.o +obj-$(CONFIG_PWM_MXS)		+= pwm-mxs.o +obj-$(CONFIG_PWM_PCA9685)	+= pwm-pca9685.o +obj-$(CONFIG_PWM_PUV3)		+= pwm-puv3.o +obj-$(CONFIG_PWM_PXA)		+= pwm-pxa.o +obj-$(CONFIG_PWM_RENESAS_TPU)	+= pwm-renesas-tpu.o +obj-$(CONFIG_PWM_SAMSUNG)	+= pwm-samsung.o +obj-$(CONFIG_PWM_SPEAR)		+= pwm-spear.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_TIPWMSS)	+= pwm-tipwmss.o +obj-$(CONFIG_PWM_TWL)		+= pwm-twl.o +obj-$(CONFIG_PWM_TWL_LED)	+= pwm-twl-led.o +obj-$(CONFIG_PWM_VT8500)	+= pwm-vt8500.o diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c new file mode 100644 index 00000000000..4b66bf09ee5 --- /dev/null +++ b/drivers/pwm/core.c @@ -0,0 +1,892 @@ +/* + * Generic pwmlib implementation + * + * Copyright (C) 2011 Sascha Hauer <s.hauer@pengutronix.de> + * Copyright (C) 2011-2012 Avionic Design GmbH + * + *  This program is free software; you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  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 <linux/module.h> +#include <linux/pwm.h> +#include <linux/radix-tree.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> + +#include <dt-bindings/pwm/pwm.h> + +#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); +static RADIX_TREE(pwm_tree, GFP_KERNEL); + +static struct pwm_device *pwm_to_device(unsigned int 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; + +	start = bitmap_find_next_zero_area(allocated_pwms, MAX_PWMS, from, +					   count, 0); + +	if (pwm >= 0 && start != pwm) +		return -EEXIST; + +	if (start + count > MAX_PWMS) +		return -ENOSPC; + +	return start; +} + +static void free_pwms(struct pwm_chip *chip) +{ +	unsigned int i; + +	for (i = 0; i < chip->npwm; i++) { +		struct pwm_device *pwm = &chip->pwms[i]; +		radix_tree_delete(&pwm_tree, pwm->pwm); +	} + +	bitmap_clear(allocated_pwms, chip->base, chip->npwm); + +	kfree(chip->pwms); +	chip->pwms = NULL; +} + +static 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; + +	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; +} + +struct pwm_device * +of_pwm_xlate_with_flags(struct pwm_chip *pc, const struct of_phandle_args *args) +{ +	struct pwm_device *pwm; + +	if (pc->of_pwm_n_cells < 3) +		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]); + +	if (args->args[2] & PWM_POLARITY_INVERTED) +		pwm_set_polarity(pwm, PWM_POLARITY_INVERSED); +	else +		pwm_set_polarity(pwm, PWM_POLARITY_NORMAL); + +	return pwm; +} +EXPORT_SYMBOL_GPL(of_pwm_xlate_with_flags); + +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; +} + +static 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); +} + +static 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 + * @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; +} +EXPORT_SYMBOL_GPL(pwm_set_chip_data); + +/** + * 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; +} +EXPORT_SYMBOL_GPL(pwm_get_chip_data); + +/** + * 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; +	unsigned int i; +	int ret; + +	if (!chip || !chip->dev || !chip->ops || !chip->ops->config || +	    !chip->ops->enable || !chip->ops->disable) +		return -EINVAL; + +	mutex_lock(&pwm_lock); + +	ret = alloc_pwms(chip->base, chip->npwm); +	if (ret < 0) +		goto out; + +	chip->pwms = kzalloc(chip->npwm * sizeof(*pwm), GFP_KERNEL); +	if (!chip->pwms) { +		ret = -ENOMEM; +		goto out; +	} + +	chip->base = ret; + +	for (i = 0; i < chip->npwm; i++) { +		pwm = &chip->pwms[i]; + +		pwm->chip = chip; +		pwm->pwm = chip->base + i; +		pwm->hwpwm = i; + +		radix_tree_insert(&pwm_tree, pwm->pwm, pwm); +	} + +	bitmap_set(allocated_pwms, chip->base, chip->npwm); + +	INIT_LIST_HEAD(&chip->list); +	list_add(&chip->list, &pwm_chips); + +	ret = 0; + +	if (IS_ENABLED(CONFIG_OF)) +		of_pwmchip_add(chip); + +	pwmchip_sysfs_export(chip); + +out: +	mutex_unlock(&pwm_lock); +	return ret; +} +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) +{ +	unsigned int i; +	int ret = 0; + +	mutex_lock(&pwm_lock); + +	for (i = 0; i < chip->npwm; i++) { +		struct pwm_device *pwm = &chip->pwms[i]; + +		if (test_bit(PWMF_REQUESTED, &pwm->flags)) { +			ret = -EBUSY; +			goto out; +		} +	} + +	list_del_init(&chip->list); + +	if (IS_ENABLED(CONFIG_OF)) +		of_pwmchip_remove(chip); + +	free_pwms(chip); + +	pwmchip_sysfs_unexport(chip); + +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 + * + * This function is deprecated, use pwm_get() instead. + */ +struct pwm_device *pwm_request(int pwm, const char *label) +{ +	struct pwm_device *dev; +	int err; + +	if (pwm < 0 || pwm >= MAX_PWMS) +		return ERR_PTR(-EINVAL); + +	mutex_lock(&pwm_lock); + +	dev = pwm_to_device(pwm); +	if (!dev) { +		dev = ERR_PTR(-EPROBE_DEFER); +		goto out; +	} + +	err = pwm_device_request(dev, label); +	if (err < 0) +		dev = ERR_PTR(err); + +out: +	mutex_unlock(&pwm_lock); + +	return dev; +} +EXPORT_SYMBOL_GPL(pwm_request); + +/** + * 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; + +	if (!chip || index >= chip->npwm) +		return ERR_PTR(-EINVAL); + +	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_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) +{ +	pwm_put(pwm); +} +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) +{ +	int err; + +	if (!pwm || duty_ns < 0 || period_ns <= 0 || duty_ns > period_ns) +		return -EINVAL; + +	err = pwm->chip->ops->config(pwm->chip, pwm, duty_ns, period_ns); +	if (err) +		return err; + +	pwm->duty_cycle = duty_ns; +	pwm->period = period_ns; + +	return 0; +} +EXPORT_SYMBOL_GPL(pwm_config); + +/** + * pwm_set_polarity() - configure the polarity of a PWM signal + * @pwm: PWM device + * @polarity: new polarity of the PWM signal + * + * Note that the polarity cannot be configured while the PWM device is enabled + */ +int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity) +{ +	int err; + +	if (!pwm || !pwm->chip->ops) +		return -EINVAL; + +	if (!pwm->chip->ops->set_polarity) +		return -ENOSYS; + +	if (test_bit(PWMF_ENABLED, &pwm->flags)) +		return -EBUSY; + +	err = pwm->chip->ops->set_polarity(pwm->chip, pwm, polarity); +	if (err) +		return err; + +	pwm->polarity = polarity; + +	return 0; +} +EXPORT_SYMBOL_GPL(pwm_set_polarity); + +/** + * pwm_enable() - start a PWM output toggling + * @pwm: PWM device + */ +int pwm_enable(struct pwm_device *pwm) +{ +	if (pwm && !test_and_set_bit(PWMF_ENABLED, &pwm->flags)) +		return pwm->chip->ops->enable(pwm->chip, pwm); + +	return pwm ? 0 : -EINVAL; +} +EXPORT_SYMBOL_GPL(pwm_enable); + +/** + * pwm_disable() - stop a PWM output toggling + * @pwm: PWM device + */ +void pwm_disable(struct pwm_device *pwm) +{ +	if (pwm && test_and_clear_bit(PWMF_ENABLED, &pwm->flags)) +		pwm->chip->ops->disable(pwm->chip, 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_get() - 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. + */ +struct pwm_device *of_pwm_get(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; +} +EXPORT_SYMBOL_GPL(of_pwm_get); + +/** + * 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 + * + * 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. + */ +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 match; + +	/* look up via DT first */ +	if (IS_ENABLED(CONFIG_OF) && dev && dev->of_node) +		return of_pwm_get(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 +	 * 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; +		} +	} + +	mutex_unlock(&pwm_lookup_lock); + +	if (chip) +		pwm = pwm_request_from_chip(chip, index, con_id ?: dev_id); +	if (IS_ERR(pwm)) +		return pwm; + +	pwm_set_period(pwm, p->period); +	pwm_set_polarity(pwm, p->polarity); + + +	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_warn("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); + +static void devm_pwm_release(struct device *dev, void *res) +{ +	pwm_put(*(struct pwm_device **)res); +} + +/** + * devm_pwm_get() - resource managed pwm_get() + * @dev: device for PWM consumer + * @con_id: consumer name + * + * This function performs like pwm_get() but the acquired PWM device will + * automatically be released on driver detach. + */ +struct pwm_device *devm_pwm_get(struct device *dev, const char *con_id) +{ +	struct pwm_device **ptr, *pwm; + +	ptr = devres_alloc(devm_pwm_release, sizeof(*ptr), GFP_KERNEL); +	if (!ptr) +		return ERR_PTR(-ENOMEM); + +	pwm = pwm_get(dev, con_id); +	if (!IS_ERR(pwm)) { +		*ptr = pwm; +		devres_add(dev, ptr); +	} else { +		devres_free(ptr); +	} + +	return pwm; +} +EXPORT_SYMBOL_GPL(devm_pwm_get); + +/** + * devm_of_pwm_get() - resource managed of_pwm_get() + * @dev: device for PWM consumer + * @np: device node to get the PWM from + * @con_id: consumer name + * + * This function performs like of_pwm_get() but the acquired PWM device will + * automatically be released on driver detach. + */ +struct pwm_device *devm_of_pwm_get(struct device *dev, struct device_node *np, +				   const char *con_id) +{ +	struct pwm_device **ptr, *pwm; + +	ptr = devres_alloc(devm_pwm_release, sizeof(*ptr), GFP_KERNEL); +	if (!ptr) +		return ERR_PTR(-ENOMEM); + +	pwm = of_pwm_get(np, con_id); +	if (!IS_ERR(pwm)) { +		*ptr = pwm; +		devres_add(dev, ptr); +	} else { +		devres_free(ptr); +	} + +	return pwm; +} +EXPORT_SYMBOL_GPL(devm_of_pwm_get); + +static int devm_pwm_match(struct device *dev, void *res, void *data) +{ +	struct pwm_device **p = res; + +	if (WARN_ON(!p || !*p)) +		return 0; + +	return *p == data; +} + +/** + * devm_pwm_put() - resource managed pwm_put() + * @dev: device for PWM consumer + * @pwm: PWM device + * + * Release a PWM previously allocated using devm_pwm_get(). Calling this + * function is usually not needed because devm-allocated resources are + * automatically released on driver detach. + */ +void devm_pwm_put(struct device *dev, struct pwm_device *pwm) +{ +	WARN_ON(devres_release(dev, devm_pwm_release, devm_pwm_match, pwm)); +} +EXPORT_SYMBOL_GPL(devm_pwm_put); + +/** +  * pwm_can_sleep() - report whether PWM access will sleep +  * @pwm: PWM device +  * +  * It returns true if accessing the PWM can sleep, false otherwise. +  */ +bool pwm_can_sleep(struct pwm_device *pwm) +{ +	return pwm->chip->can_sleep; +} +EXPORT_SYMBOL_GPL(pwm_can_sleep); + +#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_puts(s, " requested"); + +		if (test_bit(PWMF_ENABLED, &pwm->flags)) +			seq_puts(s, " enabled"); + +		seq_puts(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/drivers/pwm/pwm-ab8500.c b/drivers/pwm/pwm-ab8500.c new file mode 100644 index 00000000000..4c07a8420b3 --- /dev/null +++ b/drivers/pwm/pwm-ab8500.c @@ -0,0 +1,144 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Arun R Murthy <arun.murthy@stericsson.com> + * License terms: GNU General Public License (GPL) version 2 + */ +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/pwm.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab8500.h> +#include <linux/module.h> + +/* + * PWM Out generators + * Bank: 0x10 + */ +#define AB8500_PWM_OUT_CTRL1_REG	0x60 +#define AB8500_PWM_OUT_CTRL2_REG	0x61 +#define AB8500_PWM_OUT_CTRL7_REG	0x66 + +struct ab8500_pwm_chip { +	struct pwm_chip chip; +}; + +static int ab8500_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, +			     int duty_ns, int period_ns) +{ +	int ret = 0; +	unsigned int higher_val, lower_val; +	u8 reg; + +	/* +	 * get the first 8 bits that are be written to +	 * AB8500_PWM_OUT_CTRL1_REG[0:7] +	 */ +	lower_val = duty_ns & 0x00FF; +	/* +	 * get bits [9:10] that are to be written to +	 * AB8500_PWM_OUT_CTRL2_REG[0:1] +	 */ +	higher_val = ((duty_ns & 0x0300) >> 8); + +	reg = AB8500_PWM_OUT_CTRL1_REG + ((chip->base - 1) * 2); + +	ret = abx500_set_register_interruptible(chip->dev, AB8500_MISC, +			reg, (u8)lower_val); +	if (ret < 0) +		return ret; +	ret = abx500_set_register_interruptible(chip->dev, AB8500_MISC, +			(reg + 1), (u8)higher_val); + +	return ret; +} + +static int ab8500_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	int ret; + +	ret = abx500_mask_and_set_register_interruptible(chip->dev, +				AB8500_MISC, AB8500_PWM_OUT_CTRL7_REG, +				1 << (chip->base - 1), 1 << (chip->base - 1)); +	if (ret < 0) +		dev_err(chip->dev, "%s: Failed to enable PWM, Error %d\n", +							pwm->label, ret); +	return ret; +} + +static void ab8500_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	int ret; + +	ret = abx500_mask_and_set_register_interruptible(chip->dev, +				AB8500_MISC, AB8500_PWM_OUT_CTRL7_REG, +				1 << (chip->base - 1), 0); +	if (ret < 0) +		dev_err(chip->dev, "%s: Failed to disable PWM, Error %d\n", +							pwm->label, ret); +} + +static const struct pwm_ops ab8500_pwm_ops = { +	.config = ab8500_pwm_config, +	.enable = ab8500_pwm_enable, +	.disable = ab8500_pwm_disable, +	.owner = THIS_MODULE, +}; + +static int ab8500_pwm_probe(struct platform_device *pdev) +{ +	struct ab8500_pwm_chip *ab8500; +	int err; + +	/* +	 * Nothing to be done in probe, this is required to get the +	 * device which is required for ab8500 read and write +	 */ +	ab8500 = devm_kzalloc(&pdev->dev, sizeof(*ab8500), GFP_KERNEL); +	if (ab8500 == NULL) +		return -ENOMEM; + +	ab8500->chip.dev = &pdev->dev; +	ab8500->chip.ops = &ab8500_pwm_ops; +	ab8500->chip.base = pdev->id; +	ab8500->chip.npwm = 1; + +	err = pwmchip_add(&ab8500->chip); +	if (err < 0) +		return err; + +	dev_dbg(&pdev->dev, "pwm probe successful\n"); +	platform_set_drvdata(pdev, ab8500); + +	return 0; +} + +static int ab8500_pwm_remove(struct platform_device *pdev) +{ +	struct ab8500_pwm_chip *ab8500 = platform_get_drvdata(pdev); +	int err; + +	err = pwmchip_remove(&ab8500->chip); +	if (err < 0) +		return err; + +	dev_dbg(&pdev->dev, "pwm driver removed\n"); + +	return 0; +} + +static struct platform_driver ab8500_pwm_driver = { +	.driver = { +		.name = "ab8500-pwm", +		.owner = THIS_MODULE, +	}, +	.probe = ab8500_pwm_probe, +	.remove = ab8500_pwm_remove, +}; +module_platform_driver(ab8500_pwm_driver); + +MODULE_AUTHOR("Arun MURTHY <arun.murthy@stericsson.com>"); +MODULE_DESCRIPTION("AB8500 Pulse Width Modulation Driver"); +MODULE_ALIAS("platform:ab8500-pwm"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-atmel-tcb.c b/drivers/pwm/pwm-atmel-tcb.c new file mode 100644 index 00000000000..f3dcd02390f --- /dev/null +++ b/drivers/pwm/pwm-atmel-tcb.c @@ -0,0 +1,449 @@ +/* + * Copyright (C) Overkiz SAS 2012 + * + * Author: Boris BREZILLON <b.brezillon@overkiz.com> + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/clocksource.h> +#include <linux/clockchips.h> +#include <linux/interrupt.h> +#include <linux/irq.h> + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/ioport.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/atmel_tc.h> +#include <linux/pwm.h> +#include <linux/of_device.h> +#include <linux/slab.h> + +#define NPWM	6 + +#define ATMEL_TC_ACMR_MASK	(ATMEL_TC_ACPA | ATMEL_TC_ACPC |	\ +				 ATMEL_TC_AEEVT | ATMEL_TC_ASWTRG) + +#define ATMEL_TC_BCMR_MASK	(ATMEL_TC_BCPB | ATMEL_TC_BCPC |	\ +				 ATMEL_TC_BEEVT | ATMEL_TC_BSWTRG) + +struct atmel_tcb_pwm_device { +	enum pwm_polarity polarity;	/* PWM polarity */ +	unsigned div;			/* PWM clock divider */ +	unsigned duty;			/* PWM duty expressed in clk cycles */ +	unsigned period;		/* PWM period expressed in clk cycles */ +}; + +struct atmel_tcb_pwm_chip { +	struct pwm_chip chip; +	spinlock_t lock; +	struct atmel_tc *tc; +	struct atmel_tcb_pwm_device *pwms[NPWM]; +}; + +static inline struct atmel_tcb_pwm_chip *to_tcb_chip(struct pwm_chip *chip) +{ +	return container_of(chip, struct atmel_tcb_pwm_chip, chip); +} + +static int atmel_tcb_pwm_set_polarity(struct pwm_chip *chip, +				      struct pwm_device *pwm, +				      enum pwm_polarity polarity) +{ +	struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm); + +	tcbpwm->polarity = polarity; + +	return 0; +} + +static int atmel_tcb_pwm_request(struct pwm_chip *chip, +				 struct pwm_device *pwm) +{ +	struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip); +	struct atmel_tcb_pwm_device *tcbpwm; +	struct atmel_tc *tc = tcbpwmc->tc; +	void __iomem *regs = tc->regs; +	unsigned group = pwm->hwpwm / 2; +	unsigned index = pwm->hwpwm % 2; +	unsigned cmr; +	int ret; + +	tcbpwm = devm_kzalloc(chip->dev, sizeof(*tcbpwm), GFP_KERNEL); +	if (!tcbpwm) +		return -ENOMEM; + +	ret = clk_prepare_enable(tc->clk[group]); +	if (ret) { +		devm_kfree(chip->dev, tcbpwm); +		return ret; +	} + +	pwm_set_chip_data(pwm, tcbpwm); +	tcbpwm->polarity = PWM_POLARITY_NORMAL; +	tcbpwm->duty = 0; +	tcbpwm->period = 0; +	tcbpwm->div = 0; + +	spin_lock(&tcbpwmc->lock); +	cmr = __raw_readl(regs + ATMEL_TC_REG(group, CMR)); +	/* +	 * Get init config from Timer Counter registers if +	 * Timer Counter is already configured as a PWM generator. +	 */ +	if (cmr & ATMEL_TC_WAVE) { +		if (index == 0) +			tcbpwm->duty = +				__raw_readl(regs + ATMEL_TC_REG(group, RA)); +		else +			tcbpwm->duty = +				__raw_readl(regs + ATMEL_TC_REG(group, RB)); + +		tcbpwm->div = cmr & ATMEL_TC_TCCLKS; +		tcbpwm->period = __raw_readl(regs + ATMEL_TC_REG(group, RC)); +		cmr &= (ATMEL_TC_TCCLKS | ATMEL_TC_ACMR_MASK | +			ATMEL_TC_BCMR_MASK); +	} else +		cmr = 0; + +	cmr |= ATMEL_TC_WAVE | ATMEL_TC_WAVESEL_UP_AUTO | ATMEL_TC_EEVT_XC0; +	__raw_writel(cmr, regs + ATMEL_TC_REG(group, CMR)); +	spin_unlock(&tcbpwmc->lock); + +	tcbpwmc->pwms[pwm->hwpwm] = tcbpwm; + +	return 0; +} + +static void atmel_tcb_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip); +	struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm); +	struct atmel_tc *tc = tcbpwmc->tc; + +	clk_disable_unprepare(tc->clk[pwm->hwpwm / 2]); +	tcbpwmc->pwms[pwm->hwpwm] = NULL; +	devm_kfree(chip->dev, tcbpwm); +} + +static void atmel_tcb_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip); +	struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm); +	struct atmel_tc *tc = tcbpwmc->tc; +	void __iomem *regs = tc->regs; +	unsigned group = pwm->hwpwm / 2; +	unsigned index = pwm->hwpwm % 2; +	unsigned cmr; +	enum pwm_polarity polarity = tcbpwm->polarity; + +	/* +	 * If duty is 0 the timer will be stopped and we have to +	 * configure the output correctly on software trigger: +	 *  - set output to high if PWM_POLARITY_INVERSED +	 *  - set output to low if PWM_POLARITY_NORMAL +	 * +	 * This is why we're reverting polarity in this case. +	 */ +	if (tcbpwm->duty == 0) +		polarity = !polarity; + +	spin_lock(&tcbpwmc->lock); +	cmr = __raw_readl(regs + ATMEL_TC_REG(group, CMR)); + +	/* flush old setting and set the new one */ +	if (index == 0) { +		cmr &= ~ATMEL_TC_ACMR_MASK; +		if (polarity == PWM_POLARITY_INVERSED) +			cmr |= ATMEL_TC_ASWTRG_CLEAR; +		else +			cmr |= ATMEL_TC_ASWTRG_SET; +	} else { +		cmr &= ~ATMEL_TC_BCMR_MASK; +		if (polarity == PWM_POLARITY_INVERSED) +			cmr |= ATMEL_TC_BSWTRG_CLEAR; +		else +			cmr |= ATMEL_TC_BSWTRG_SET; +	} + +	__raw_writel(cmr, regs + ATMEL_TC_REG(group, CMR)); + +	/* +	 * Use software trigger to apply the new setting. +	 * If both PWM devices in this group are disabled we stop the clock. +	 */ +	if (!(cmr & (ATMEL_TC_ACPC | ATMEL_TC_BCPC))) +		__raw_writel(ATMEL_TC_SWTRG | ATMEL_TC_CLKDIS, +			     regs + ATMEL_TC_REG(group, CCR)); +	else +		__raw_writel(ATMEL_TC_SWTRG, regs + +			     ATMEL_TC_REG(group, CCR)); + +	spin_unlock(&tcbpwmc->lock); +} + +static int atmel_tcb_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip); +	struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm); +	struct atmel_tc *tc = tcbpwmc->tc; +	void __iomem *regs = tc->regs; +	unsigned group = pwm->hwpwm / 2; +	unsigned index = pwm->hwpwm % 2; +	u32 cmr; +	enum pwm_polarity polarity = tcbpwm->polarity; + +	/* +	 * If duty is 0 the timer will be stopped and we have to +	 * configure the output correctly on software trigger: +	 *  - set output to high if PWM_POLARITY_INVERSED +	 *  - set output to low if PWM_POLARITY_NORMAL +	 * +	 * This is why we're reverting polarity in this case. +	 */ +	if (tcbpwm->duty == 0) +		polarity = !polarity; + +	spin_lock(&tcbpwmc->lock); +	cmr = __raw_readl(regs + ATMEL_TC_REG(group, CMR)); + +	/* flush old setting and set the new one */ +	cmr &= ~ATMEL_TC_TCCLKS; + +	if (index == 0) { +		cmr &= ~ATMEL_TC_ACMR_MASK; + +		/* Set CMR flags according to given polarity */ +		if (polarity == PWM_POLARITY_INVERSED) +			cmr |= ATMEL_TC_ASWTRG_CLEAR; +		else +			cmr |= ATMEL_TC_ASWTRG_SET; +	} else { +		cmr &= ~ATMEL_TC_BCMR_MASK; +		if (polarity == PWM_POLARITY_INVERSED) +			cmr |= ATMEL_TC_BSWTRG_CLEAR; +		else +			cmr |= ATMEL_TC_BSWTRG_SET; +	} + +	/* +	 * If duty is 0 or equal to period there's no need to register +	 * a specific action on RA/RB and RC compare. +	 * The output will be configured on software trigger and keep +	 * this config till next config call. +	 */ +	if (tcbpwm->duty != tcbpwm->period && tcbpwm->duty > 0) { +		if (index == 0) { +			if (polarity == PWM_POLARITY_INVERSED) +				cmr |= ATMEL_TC_ACPA_SET | ATMEL_TC_ACPC_CLEAR; +			else +				cmr |= ATMEL_TC_ACPA_CLEAR | ATMEL_TC_ACPC_SET; +		} else { +			if (polarity == PWM_POLARITY_INVERSED) +				cmr |= ATMEL_TC_BCPB_SET | ATMEL_TC_BCPC_CLEAR; +			else +				cmr |= ATMEL_TC_BCPB_CLEAR | ATMEL_TC_BCPC_SET; +		} +	} + +	cmr |= (tcbpwm->div & ATMEL_TC_TCCLKS); + +	__raw_writel(cmr, regs + ATMEL_TC_REG(group, CMR)); + +	if (index == 0) +		__raw_writel(tcbpwm->duty, regs + ATMEL_TC_REG(group, RA)); +	else +		__raw_writel(tcbpwm->duty, regs + ATMEL_TC_REG(group, RB)); + +	__raw_writel(tcbpwm->period, regs + ATMEL_TC_REG(group, RC)); + +	/* Use software trigger to apply the new setting */ +	__raw_writel(ATMEL_TC_CLKEN | ATMEL_TC_SWTRG, +		     regs + ATMEL_TC_REG(group, CCR)); +	spin_unlock(&tcbpwmc->lock); +	return 0; +} + +static int atmel_tcb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, +				int duty_ns, int period_ns) +{ +	struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip); +	struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm); +	unsigned group = pwm->hwpwm / 2; +	unsigned index = pwm->hwpwm % 2; +	struct atmel_tcb_pwm_device *atcbpwm = NULL; +	struct atmel_tc *tc = tcbpwmc->tc; +	int i; +	int slowclk = 0; +	unsigned period; +	unsigned duty; +	unsigned rate = clk_get_rate(tc->clk[group]); +	unsigned long long min; +	unsigned long long max; + +	/* +	 * Find best clk divisor: +	 * the smallest divisor which can fulfill the period_ns requirements. +	 */ +	for (i = 0; i < 5; ++i) { +		if (atmel_tc_divisors[i] == 0) { +			slowclk = i; +			continue; +		} +		min = div_u64((u64)NSEC_PER_SEC * atmel_tc_divisors[i], rate); +		max = min << tc->tcb_config->counter_width; +		if (max >= period_ns) +			break; +	} + +	/* +	 * If none of the divisor are small enough to represent period_ns +	 * take slow clock (32KHz). +	 */ +	if (i == 5) { +		i = slowclk; +		rate = 32768; +		min = div_u64(NSEC_PER_SEC, rate); +		max = min << tc->tcb_config->counter_width; + +		/* If period is too big return ERANGE error */ +		if (max < period_ns) +			return -ERANGE; +	} + +	duty = div_u64(duty_ns, min); +	period = div_u64(period_ns, min); + +	if (index == 0) +		atcbpwm = tcbpwmc->pwms[pwm->hwpwm + 1]; +	else +		atcbpwm = tcbpwmc->pwms[pwm->hwpwm - 1]; + +	/* +	 * PWM devices provided by TCB driver are grouped by 2: +	 * - group 0: PWM 0 & 1 +	 * - group 1: PWM 2 & 3 +	 * - group 2: PWM 4 & 5 +	 * +	 * PWM devices in a given group must be configured with the +	 * same period_ns. +	 * +	 * We're checking the period value of the second PWM device +	 * in this group before applying the new config. +	 */ +	if ((atcbpwm && atcbpwm->duty > 0 && +			atcbpwm->duty != atcbpwm->period) && +		(atcbpwm->div != i || atcbpwm->period != period)) { +		dev_err(chip->dev, +			"failed to configure period_ns: PWM group already configured with a different value\n"); +		return -EINVAL; +	} + +	tcbpwm->period = period; +	tcbpwm->div = i; +	tcbpwm->duty = duty; + +	/* If the PWM is enabled, call enable to apply the new conf */ +	if (test_bit(PWMF_ENABLED, &pwm->flags)) +		atmel_tcb_pwm_enable(chip, pwm); + +	return 0; +} + +static const struct pwm_ops atmel_tcb_pwm_ops = { +	.request = atmel_tcb_pwm_request, +	.free = atmel_tcb_pwm_free, +	.config = atmel_tcb_pwm_config, +	.set_polarity = atmel_tcb_pwm_set_polarity, +	.enable = atmel_tcb_pwm_enable, +	.disable = atmel_tcb_pwm_disable, +	.owner = THIS_MODULE, +}; + +static int atmel_tcb_pwm_probe(struct platform_device *pdev) +{ +	struct atmel_tcb_pwm_chip *tcbpwm; +	struct device_node *np = pdev->dev.of_node; +	struct atmel_tc *tc; +	int err; +	int tcblock; + +	err = of_property_read_u32(np, "tc-block", &tcblock); +	if (err < 0) { +		dev_err(&pdev->dev, +			"failed to get Timer Counter Block number from device tree (error: %d)\n", +			err); +		return err; +	} + +	tc = atmel_tc_alloc(tcblock, "tcb-pwm"); +	if (tc == NULL) { +		dev_err(&pdev->dev, "failed to allocate Timer Counter Block\n"); +		return -ENOMEM; +	} + +	tcbpwm = devm_kzalloc(&pdev->dev, sizeof(*tcbpwm), GFP_KERNEL); +	if (tcbpwm == NULL) { +		atmel_tc_free(tc); +		dev_err(&pdev->dev, "failed to allocate memory\n"); +		return -ENOMEM; +	} + +	tcbpwm->chip.dev = &pdev->dev; +	tcbpwm->chip.ops = &atmel_tcb_pwm_ops; +	tcbpwm->chip.of_xlate = of_pwm_xlate_with_flags; +	tcbpwm->chip.of_pwm_n_cells = 3; +	tcbpwm->chip.base = -1; +	tcbpwm->chip.npwm = NPWM; +	tcbpwm->tc = tc; + +	spin_lock_init(&tcbpwm->lock); + +	err = pwmchip_add(&tcbpwm->chip); +	if (err < 0) { +		atmel_tc_free(tc); +		return err; +	} + +	platform_set_drvdata(pdev, tcbpwm); + +	return 0; +} + +static int atmel_tcb_pwm_remove(struct platform_device *pdev) +{ +	struct atmel_tcb_pwm_chip *tcbpwm = platform_get_drvdata(pdev); +	int err; + +	err = pwmchip_remove(&tcbpwm->chip); +	if (err < 0) +		return err; + +	atmel_tc_free(tcbpwm->tc); + +	return 0; +} + +static const struct of_device_id atmel_tcb_pwm_dt_ids[] = { +	{ .compatible = "atmel,tcb-pwm", }, +	{ /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, atmel_tcb_pwm_dt_ids); + +static struct platform_driver atmel_tcb_pwm_driver = { +	.driver = { +		.name = "atmel-tcb-pwm", +		.owner = THIS_MODULE, +		.of_match_table = atmel_tcb_pwm_dt_ids, +	}, +	.probe = atmel_tcb_pwm_probe, +	.remove = atmel_tcb_pwm_remove, +}; +module_platform_driver(atmel_tcb_pwm_driver); + +MODULE_AUTHOR("Boris BREZILLON <b.brezillon@overkiz.com>"); +MODULE_DESCRIPTION("Atmel Timer Counter Pulse Width Modulation Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-atmel.c b/drivers/pwm/pwm-atmel.c new file mode 100644 index 00000000000..6e700a541ca --- /dev/null +++ b/drivers/pwm/pwm-atmel.c @@ -0,0 +1,401 @@ +/* + * Driver for Atmel Pulse Width Modulation Controller + * + * Copyright (C) 2013 Atmel Corporation + *		 Bo Shen <voice.shen@atmel.com> + * + * Licensed under GPLv2. + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> + +/* The following is global registers for PWM controller */ +#define PWM_ENA			0x04 +#define PWM_DIS			0x08 +#define PWM_SR			0x0C +/* Bit field in SR */ +#define PWM_SR_ALL_CH_ON	0x0F + +/* The following register is PWM channel related registers */ +#define PWM_CH_REG_OFFSET	0x200 +#define PWM_CH_REG_SIZE		0x20 + +#define PWM_CMR			0x0 +/* Bit field in CMR */ +#define PWM_CMR_CPOL		(1 << 9) +#define PWM_CMR_UPD_CDTY	(1 << 10) +#define PWM_CMR_CPRE_MSK	0xF + +/* The following registers for PWM v1 */ +#define PWMV1_CDTY		0x04 +#define PWMV1_CPRD		0x08 +#define PWMV1_CUPD		0x10 + +/* The following registers for PWM v2 */ +#define PWMV2_CDTY		0x04 +#define PWMV2_CDTYUPD		0x08 +#define PWMV2_CPRD		0x0C +#define PWMV2_CPRDUPD		0x10 + +/* + * Max value for duty and period + * + * Although the duty and period register is 32 bit, + * however only the LSB 16 bits are significant. + */ +#define PWM_MAX_DTY		0xFFFF +#define PWM_MAX_PRD		0xFFFF +#define PRD_MAX_PRES		10 + +struct atmel_pwm_chip { +	struct pwm_chip chip; +	struct clk *clk; +	void __iomem *base; + +	void (*config)(struct pwm_chip *chip, struct pwm_device *pwm, +		       unsigned long dty, unsigned long prd); +}; + +static inline struct atmel_pwm_chip *to_atmel_pwm_chip(struct pwm_chip *chip) +{ +	return container_of(chip, struct atmel_pwm_chip, chip); +} + +static inline u32 atmel_pwm_readl(struct atmel_pwm_chip *chip, +				  unsigned long offset) +{ +	return readl_relaxed(chip->base + offset); +} + +static inline void atmel_pwm_writel(struct atmel_pwm_chip *chip, +				    unsigned long offset, unsigned long val) +{ +	writel_relaxed(val, chip->base + offset); +} + +static inline u32 atmel_pwm_ch_readl(struct atmel_pwm_chip *chip, +				     unsigned int ch, unsigned long offset) +{ +	unsigned long base = PWM_CH_REG_OFFSET + ch * PWM_CH_REG_SIZE; + +	return readl_relaxed(chip->base + base + offset); +} + +static inline void atmel_pwm_ch_writel(struct atmel_pwm_chip *chip, +				       unsigned int ch, unsigned long offset, +				       unsigned long val) +{ +	unsigned long base = PWM_CH_REG_OFFSET + ch * PWM_CH_REG_SIZE; + +	writel_relaxed(val, chip->base + base + offset); +} + +static int atmel_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, +			    int duty_ns, int period_ns) +{ +	struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip); +	unsigned long clk_rate, prd, dty; +	unsigned long long div; +	unsigned int pres = 0; +	u32 val; +	int ret; + +	if (test_bit(PWMF_ENABLED, &pwm->flags) && (period_ns != pwm->period)) { +		dev_err(chip->dev, "cannot change PWM period while enabled\n"); +		return -EBUSY; +	} + +	clk_rate = clk_get_rate(atmel_pwm->clk); +	div = clk_rate; + +	/* Calculate the period cycles */ +	while (div > PWM_MAX_PRD) { +		div = clk_rate / (1 << pres); +		div = div * period_ns; +		/* 1/Hz = 100000000 ns */ +		do_div(div, 1000000000); + +		if (pres++ > PRD_MAX_PRES) { +			dev_err(chip->dev, "pres exceeds the maximum value\n"); +			return -EINVAL; +		} +	} + +	/* Calculate the duty cycles */ +	prd = div; +	div *= duty_ns; +	do_div(div, period_ns); +	dty = prd - div; + +	ret = clk_enable(atmel_pwm->clk); +	if (ret) { +		dev_err(chip->dev, "failed to enable PWM clock\n"); +		return ret; +	} + +	/* It is necessary to preserve CPOL, inside CMR */ +	val = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, PWM_CMR); +	val = (val & ~PWM_CMR_CPRE_MSK) | (pres & PWM_CMR_CPRE_MSK); +	atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val); +	atmel_pwm->config(chip, pwm, dty, prd); + +	clk_disable(atmel_pwm->clk); +	return ret; +} + +static void atmel_pwm_config_v1(struct pwm_chip *chip, struct pwm_device *pwm, +				unsigned long dty, unsigned long prd) +{ +	struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip); +	unsigned int val; + +	if (test_bit(PWMF_ENABLED, &pwm->flags)) { +		/* +		 * If the PWM channel is enabled, using the update register, +		 * it needs to set bit 10 of CMR to 0 +		 */ +		atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV1_CUPD, dty); + +		val = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, PWM_CMR); +		val &= ~PWM_CMR_UPD_CDTY; +		atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val); +	} else { +		/* +		 * If the PWM channel is disabled, write value to duty and +		 * period registers directly. +		 */ +		atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV1_CDTY, dty); +		atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV1_CPRD, prd); +	} +} + +static void atmel_pwm_config_v2(struct pwm_chip *chip, struct pwm_device *pwm, +				unsigned long dty, unsigned long prd) +{ +	struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip); + +	if (test_bit(PWMF_ENABLED, &pwm->flags)) { +		/* +		 * If the PWM channel is enabled, using the duty update register +		 * to update the value. +		 */ +		atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV2_CDTYUPD, dty); +	} else { +		/* +		 * If the PWM channel is disabled, write value to duty and +		 * period registers directly. +		 */ +		atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV2_CDTY, dty); +		atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWMV2_CPRD, prd); +	} +} + +static int atmel_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm, +				  enum pwm_polarity polarity) +{ +	struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip); +	u32 val; +	int ret; + +	val = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, PWM_CMR); + +	if (polarity == PWM_POLARITY_NORMAL) +		val &= ~PWM_CMR_CPOL; +	else +		val |= PWM_CMR_CPOL; + +	ret = clk_enable(atmel_pwm->clk); +	if (ret) { +		dev_err(chip->dev, "failed to enable PWM clock\n"); +		return ret; +	} + +	atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val); + +	clk_disable(atmel_pwm->clk); + +	return 0; +} + +static int atmel_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip); +	int ret; + +	ret = clk_enable(atmel_pwm->clk); +	if (ret) { +		dev_err(chip->dev, "failed to enable PWM clock\n"); +		return ret; +	} + +	atmel_pwm_writel(atmel_pwm, PWM_ENA, 1 << pwm->hwpwm); + +	return 0; +} + +static void atmel_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip); + +	atmel_pwm_writel(atmel_pwm, PWM_DIS, 1 << pwm->hwpwm); + +	clk_disable(atmel_pwm->clk); +} + +static const struct pwm_ops atmel_pwm_ops = { +	.config = atmel_pwm_config, +	.set_polarity = atmel_pwm_set_polarity, +	.enable = atmel_pwm_enable, +	.disable = atmel_pwm_disable, +	.owner = THIS_MODULE, +}; + +struct atmel_pwm_data { +	void (*config)(struct pwm_chip *chip, struct pwm_device *pwm, +		       unsigned long dty, unsigned long prd); +}; + +static const struct atmel_pwm_data atmel_pwm_data_v1 = { +	.config = atmel_pwm_config_v1, +}; + +static const struct atmel_pwm_data atmel_pwm_data_v2 = { +	.config = atmel_pwm_config_v2, +}; + +static const struct platform_device_id atmel_pwm_devtypes[] = { +	{ +		.name = "at91sam9rl-pwm", +		.driver_data = (kernel_ulong_t)&atmel_pwm_data_v1, +	}, { +		.name = "sama5d3-pwm", +		.driver_data = (kernel_ulong_t)&atmel_pwm_data_v2, +	}, { +		/* sentinel */ +	}, +}; +MODULE_DEVICE_TABLE(platform, atmel_pwm_devtypes); + +static const struct of_device_id atmel_pwm_dt_ids[] = { +	{ +		.compatible = "atmel,at91sam9rl-pwm", +		.data = &atmel_pwm_data_v1, +	}, { +		.compatible = "atmel,sama5d3-pwm", +		.data = &atmel_pwm_data_v2, +	}, { +		/* sentinel */ +	}, +}; +MODULE_DEVICE_TABLE(of, atmel_pwm_dt_ids); + +static inline const struct atmel_pwm_data * +atmel_pwm_get_driver_data(struct platform_device *pdev) +{ +	if (pdev->dev.of_node) { +		const struct of_device_id *match; + +		match = of_match_device(atmel_pwm_dt_ids, &pdev->dev); +		if (!match) +			return NULL; + +		return match->data; +	} else { +		const struct platform_device_id *id; + +		id = platform_get_device_id(pdev); + +		return (struct atmel_pwm_data *)id->driver_data; +	} +} + +static int atmel_pwm_probe(struct platform_device *pdev) +{ +	const struct atmel_pwm_data *data; +	struct atmel_pwm_chip *atmel_pwm; +	struct resource *res; +	int ret; + +	data = atmel_pwm_get_driver_data(pdev); +	if (!data) +		return -ENODEV; + +	atmel_pwm = devm_kzalloc(&pdev->dev, sizeof(*atmel_pwm), GFP_KERNEL); +	if (!atmel_pwm) +		return -ENOMEM; + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	atmel_pwm->base = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(atmel_pwm->base)) +		return PTR_ERR(atmel_pwm->base); + +	atmel_pwm->clk = devm_clk_get(&pdev->dev, NULL); +	if (IS_ERR(atmel_pwm->clk)) +		return PTR_ERR(atmel_pwm->clk); + +	ret = clk_prepare(atmel_pwm->clk); +	if (ret) { +		dev_err(&pdev->dev, "failed to prepare PWM clock\n"); +		return ret; +	} + +	atmel_pwm->chip.dev = &pdev->dev; +	atmel_pwm->chip.ops = &atmel_pwm_ops; + +	if (pdev->dev.of_node) { +		atmel_pwm->chip.of_xlate = of_pwm_xlate_with_flags; +		atmel_pwm->chip.of_pwm_n_cells = 3; +	} + +	atmel_pwm->chip.base = -1; +	atmel_pwm->chip.npwm = 4; +	atmel_pwm->chip.can_sleep = true; +	atmel_pwm->config = data->config; + +	ret = pwmchip_add(&atmel_pwm->chip); +	if (ret < 0) { +		dev_err(&pdev->dev, "failed to add PWM chip %d\n", ret); +		goto unprepare_clk; +	} + +	platform_set_drvdata(pdev, atmel_pwm); + +	return ret; + +unprepare_clk: +	clk_unprepare(atmel_pwm->clk); +	return ret; +} + +static int atmel_pwm_remove(struct platform_device *pdev) +{ +	struct atmel_pwm_chip *atmel_pwm = platform_get_drvdata(pdev); + +	clk_unprepare(atmel_pwm->clk); + +	return pwmchip_remove(&atmel_pwm->chip); +} + +static struct platform_driver atmel_pwm_driver = { +	.driver = { +		.name = "atmel-pwm", +		.of_match_table = of_match_ptr(atmel_pwm_dt_ids), +	}, +	.id_table = atmel_pwm_devtypes, +	.probe = atmel_pwm_probe, +	.remove = atmel_pwm_remove, +}; +module_platform_driver(atmel_pwm_driver); + +MODULE_ALIAS("platform:atmel-pwm"); +MODULE_AUTHOR("Bo Shen <voice.shen@atmel.com>"); +MODULE_DESCRIPTION("Atmel PWM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-bcm-kona.c b/drivers/pwm/pwm-bcm-kona.c new file mode 100644 index 00000000000..02bc048892a --- /dev/null +++ b/drivers/pwm/pwm-bcm-kona.c @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2014 Broadcom Corporation + * + * 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. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> +#include <linux/types.h> + +/* + * The Kona PWM has some unusual characteristics.  Here are the main points. + * + * 1) There is no disable bit and the hardware docs advise programming a zero + *    duty to achieve output equivalent to that of a normal disable operation. + * + * 2) Changes to prescale, duty, period, and polarity do not take effect until + *    a subsequent rising edge of the trigger bit. + * + * 3) If the smooth bit and trigger bit are both low, the output is a constant + *    high signal.  Otherwise, the earlier waveform continues to be output. + * + * 4) If the smooth bit is set on the rising edge of the trigger bit, output + *    will transition to the new settings on a period boundary (which could be + *    seconds away).  If the smooth bit is clear, new settings will be applied + *    as soon as possible (the hardware always has a 400ns delay). + * + * 5) When the external clock that feeds the PWM is disabled, output is pegged + *    high or low depending on its state at that exact instant. + */ + +#define PWM_CONTROL_OFFSET			(0x00000000) +#define PWM_CONTROL_SMOOTH_SHIFT(chan)		(24 + (chan)) +#define PWM_CONTROL_TYPE_SHIFT(chan)		(16 + (chan)) +#define PWM_CONTROL_POLARITY_SHIFT(chan)	(8 + (chan)) +#define PWM_CONTROL_TRIGGER_SHIFT(chan)		(chan) + +#define PRESCALE_OFFSET				(0x00000004) +#define PRESCALE_SHIFT(chan)			((chan) << 2) +#define PRESCALE_MASK(chan)			(0x7 << PRESCALE_SHIFT(chan)) +#define PRESCALE_MIN				(0x00000000) +#define PRESCALE_MAX				(0x00000007) + +#define PERIOD_COUNT_OFFSET(chan)		(0x00000008 + ((chan) << 3)) +#define PERIOD_COUNT_MIN			(0x00000002) +#define PERIOD_COUNT_MAX			(0x00ffffff) + +#define DUTY_CYCLE_HIGH_OFFSET(chan)		(0x0000000c + ((chan) << 3)) +#define DUTY_CYCLE_HIGH_MIN			(0x00000000) +#define DUTY_CYCLE_HIGH_MAX			(0x00ffffff) + +struct kona_pwmc { +	struct pwm_chip chip; +	void __iomem *base; +	struct clk *clk; +}; + +static inline struct kona_pwmc *to_kona_pwmc(struct pwm_chip *_chip) +{ +	return container_of(_chip, struct kona_pwmc, chip); +} + +static void kona_pwmc_apply_settings(struct kona_pwmc *kp, unsigned int chan) +{ +	unsigned int value = readl(kp->base + PWM_CONTROL_OFFSET); + +	/* Clear trigger bit but set smooth bit to maintain old output */ +	value |= 1 << PWM_CONTROL_SMOOTH_SHIFT(chan); +	value &= ~(1 << PWM_CONTROL_TRIGGER_SHIFT(chan)); +	writel(value, kp->base + PWM_CONTROL_OFFSET); + +	/* Set trigger bit and clear smooth bit to apply new settings */ +	value &= ~(1 << PWM_CONTROL_SMOOTH_SHIFT(chan)); +	value |= 1 << PWM_CONTROL_TRIGGER_SHIFT(chan); +	writel(value, kp->base + PWM_CONTROL_OFFSET); +} + +static int kona_pwmc_config(struct pwm_chip *chip, struct pwm_device *pwm, +			    int duty_ns, int period_ns) +{ +	struct kona_pwmc *kp = to_kona_pwmc(chip); +	u64 val, div, rate; +	unsigned long prescale = PRESCALE_MIN, pc, dc; +	unsigned int value, chan = pwm->hwpwm; + +	/* +	 * Find period count, duty count and prescale to suit duty_ns and +	 * period_ns. This is done according to formulas described below: +	 * +	 * period_ns = 10^9 * (PRESCALE + 1) * PC / PWM_CLK_RATE +	 * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE +	 * +	 * PC = (PWM_CLK_RATE * period_ns) / (10^9 * (PRESCALE + 1)) +	 * DC = (PWM_CLK_RATE * duty_ns) / (10^9 * (PRESCALE + 1)) +	 */ + +	rate = clk_get_rate(kp->clk); + +	while (1) { +		div = 1000000000; +		div *= 1 + prescale; +		val = rate * period_ns; +		pc = div64_u64(val, div); +		val = rate * duty_ns; +		dc = div64_u64(val, div); + +		/* If duty_ns or period_ns are not achievable then return */ +		if (pc < PERIOD_COUNT_MIN || dc < DUTY_CYCLE_HIGH_MIN) +			return -EINVAL; + +		/* If pc and dc are in bounds, the calculation is done */ +		if (pc <= PERIOD_COUNT_MAX && dc <= DUTY_CYCLE_HIGH_MAX) +			break; + +		/* Otherwise, increase prescale and recalculate pc and dc */ +		if (++prescale > PRESCALE_MAX) +			return -EINVAL; +	} + +	/* If the PWM channel is enabled, write the settings to the HW */ +	if (test_bit(PWMF_ENABLED, &pwm->flags)) { +		value = readl(kp->base + PRESCALE_OFFSET); +		value &= ~PRESCALE_MASK(chan); +		value |= prescale << PRESCALE_SHIFT(chan); +		writel(value, kp->base + PRESCALE_OFFSET); + +		writel(pc, kp->base + PERIOD_COUNT_OFFSET(chan)); + +		writel(dc, kp->base + DUTY_CYCLE_HIGH_OFFSET(chan)); + +		kona_pwmc_apply_settings(kp, chan); +	} + +	return 0; +} + +static int kona_pwmc_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm, +				  enum pwm_polarity polarity) +{ +	struct kona_pwmc *kp = to_kona_pwmc(chip); +	unsigned int chan = pwm->hwpwm; +	unsigned int value; +	int ret; + +	ret = clk_prepare_enable(kp->clk); +	if (ret < 0) { +		dev_err(chip->dev, "failed to enable clock: %d\n", ret); +		return ret; +	} + +	value = readl(kp->base + PWM_CONTROL_OFFSET); + +	if (polarity == PWM_POLARITY_NORMAL) +		value |= 1 << PWM_CONTROL_POLARITY_SHIFT(chan); +	else +		value &= ~(1 << PWM_CONTROL_POLARITY_SHIFT(chan)); + +	writel(value, kp->base + PWM_CONTROL_OFFSET); + +	kona_pwmc_apply_settings(kp, chan); + +	/* Wait for waveform to settle before gating off the clock */ +	ndelay(400); + +	clk_disable_unprepare(kp->clk); + +	return 0; +} + +static int kona_pwmc_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct kona_pwmc *kp = to_kona_pwmc(chip); +	int ret; + +	ret = clk_prepare_enable(kp->clk); +	if (ret < 0) { +		dev_err(chip->dev, "failed to enable clock: %d\n", ret); +		return ret; +	} + +	ret = kona_pwmc_config(chip, pwm, pwm->duty_cycle, pwm->period); +	if (ret < 0) { +		clk_disable_unprepare(kp->clk); +		return ret; +	} + +	return 0; +} + +static void kona_pwmc_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct kona_pwmc *kp = to_kona_pwmc(chip); +	unsigned int chan = pwm->hwpwm; + +	/* Simulate a disable by configuring for zero duty */ +	writel(0, kp->base + DUTY_CYCLE_HIGH_OFFSET(chan)); +	kona_pwmc_apply_settings(kp, chan); + +	/* Wait for waveform to settle before gating off the clock */ +	ndelay(400); + +	clk_disable_unprepare(kp->clk); +} + +static const struct pwm_ops kona_pwm_ops = { +	.config = kona_pwmc_config, +	.set_polarity = kona_pwmc_set_polarity, +	.enable = kona_pwmc_enable, +	.disable = kona_pwmc_disable, +	.owner = THIS_MODULE, +}; + +static int kona_pwmc_probe(struct platform_device *pdev) +{ +	struct kona_pwmc *kp; +	struct resource *res; +	unsigned int chan; +	unsigned int value = 0; +	int ret = 0; + +	kp = devm_kzalloc(&pdev->dev, sizeof(*kp), GFP_KERNEL); +	if (kp == NULL) +		return -ENOMEM; + +	platform_set_drvdata(pdev, kp); + +	kp->chip.dev = &pdev->dev; +	kp->chip.ops = &kona_pwm_ops; +	kp->chip.base = -1; +	kp->chip.npwm = 6; +	kp->chip.of_xlate = of_pwm_xlate_with_flags; +	kp->chip.of_pwm_n_cells = 3; +	kp->chip.can_sleep = true; + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	kp->base = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(kp->base)) +		return PTR_ERR(kp->base); + +	kp->clk = devm_clk_get(&pdev->dev, NULL); +	if (IS_ERR(kp->clk)) { +		dev_err(&pdev->dev, "failed to get clock: %ld\n", +			PTR_ERR(kp->clk)); +		return PTR_ERR(kp->clk); +	} + +	ret = clk_prepare_enable(kp->clk); +	if (ret < 0) { +		dev_err(&pdev->dev, "failed to enable clock: %d\n", ret); +		return ret; +	} + +	/* Set smooth mode, push/pull, and normal polarity for all channels */ +	for (chan = 0; chan < kp->chip.npwm; chan++) { +		value |= (1 << PWM_CONTROL_SMOOTH_SHIFT(chan)); +		value |= (1 << PWM_CONTROL_TYPE_SHIFT(chan)); +		value |= (1 << PWM_CONTROL_POLARITY_SHIFT(chan)); +	} + +	writel(value, kp->base + PWM_CONTROL_OFFSET); + +	clk_disable_unprepare(kp->clk); + +	ret = pwmchip_add(&kp->chip); +	if (ret < 0) +		dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret); + +	return ret; +} + +static int kona_pwmc_remove(struct platform_device *pdev) +{ +	struct kona_pwmc *kp = platform_get_drvdata(pdev); +	unsigned int chan; + +	for (chan = 0; chan < kp->chip.npwm; chan++) +		if (test_bit(PWMF_ENABLED, &kp->chip.pwms[chan].flags)) +			clk_disable_unprepare(kp->clk); + +	return pwmchip_remove(&kp->chip); +} + +static const struct of_device_id bcm_kona_pwmc_dt[] = { +	{ .compatible = "brcm,kona-pwm" }, +	{ }, +}; +MODULE_DEVICE_TABLE(of, bcm_kona_pwmc_dt); + +static struct platform_driver kona_pwmc_driver = { +	.driver = { +		.name = "bcm-kona-pwm", +		.of_match_table = bcm_kona_pwmc_dt, +	}, +	.probe = kona_pwmc_probe, +	.remove = kona_pwmc_remove, +}; +module_platform_driver(kona_pwmc_driver); + +MODULE_AUTHOR("Broadcom Corporation <bcm-kernel-feedback-list@broadcom.com>"); +MODULE_AUTHOR("Tim Kryger <tkryger@broadcom.com>"); +MODULE_DESCRIPTION("Broadcom Kona PWM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-bfin.c b/drivers/pwm/pwm-bfin.c new file mode 100644 index 00000000000..9985d830e55 --- /dev/null +++ b/drivers/pwm/pwm-bfin.c @@ -0,0 +1,160 @@ +/* + * Blackfin Pulse Width Modulation (PWM) core + * + * Copyright (c) 2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> + +#include <asm/gptimers.h> +#include <asm/portmux.h> + +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; + +	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 bfin_pwm_remove(struct platform_device *pdev) +{ +	struct bfin_pwm_chip *pwm = platform_get_drvdata(pdev); + +	return pwmchip_remove(&pwm->chip); +} + +static struct platform_driver bfin_pwm_driver = { +	.driver = { +		.name = "bfin-pwm", +		.owner = THIS_MODULE, +	}, +	.probe = bfin_pwm_probe, +	.remove = bfin_pwm_remove, +}; + +module_platform_driver(bfin_pwm_driver); + +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-clps711x.c b/drivers/pwm/pwm-clps711x.c new file mode 100644 index 00000000000..fafb6a0111b --- /dev/null +++ b/drivers/pwm/pwm-clps711x.c @@ -0,0 +1,176 @@ +/* + * Cirrus Logic CLPS711X PWM driver + * + * Copyright (C) 2014 Alexander Shiyan <shc_work@mail.ru> + * + * 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. + */ + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> + +struct clps711x_chip { +	struct pwm_chip chip; +	void __iomem *pmpcon; +	struct clk *clk; +	spinlock_t lock; +}; + +static inline struct clps711x_chip *to_clps711x_chip(struct pwm_chip *chip) +{ +	return container_of(chip, struct clps711x_chip, chip); +} + +static void clps711x_pwm_update_val(struct clps711x_chip *priv, u32 n, u32 v) +{ +	/* PWM0 - bits 4..7, PWM1 - bits 8..11 */ +	u32 shift = (n + 1) * 4; +	unsigned long flags; +	u32 tmp; + +	spin_lock_irqsave(&priv->lock, flags); + +	tmp = readl(priv->pmpcon); +	tmp &= ~(0xf << shift); +	tmp |= v << shift; +	writel(tmp, priv->pmpcon); + +	spin_unlock_irqrestore(&priv->lock, flags); +} + +static unsigned int clps711x_get_duty(struct pwm_device *pwm, unsigned int v) +{ +	/* Duty cycle 0..15 max */ +	return DIV_ROUND_CLOSEST(v * 0xf, pwm_get_period(pwm)); +} + +static int clps711x_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct clps711x_chip *priv = to_clps711x_chip(chip); +	unsigned int freq = clk_get_rate(priv->clk); + +	if (!freq) +		return -EINVAL; + +	/* Store constant period value */ +	pwm_set_period(pwm, DIV_ROUND_CLOSEST(NSEC_PER_SEC, freq)); + +	return 0; +} + +static int clps711x_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, +			       int duty_ns, int period_ns) +{ +	struct clps711x_chip *priv = to_clps711x_chip(chip); +	unsigned int duty; + +	if (period_ns != pwm_get_period(pwm)) +		return -EINVAL; + +	duty = clps711x_get_duty(pwm, duty_ns); +	clps711x_pwm_update_val(priv, pwm->hwpwm, duty); + +	return 0; +} + +static int clps711x_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct clps711x_chip *priv = to_clps711x_chip(chip); +	unsigned int duty; + +	duty = clps711x_get_duty(pwm, pwm_get_duty_cycle(pwm)); +	clps711x_pwm_update_val(priv, pwm->hwpwm, duty); + +	return 0; +} + +static void clps711x_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct clps711x_chip *priv = to_clps711x_chip(chip); + +	clps711x_pwm_update_val(priv, pwm->hwpwm, 0); +} + +static const struct pwm_ops clps711x_pwm_ops = { +	.request = clps711x_pwm_request, +	.config = clps711x_pwm_config, +	.enable = clps711x_pwm_enable, +	.disable = clps711x_pwm_disable, +	.owner = THIS_MODULE, +}; + +static struct pwm_device *clps711x_pwm_xlate(struct pwm_chip *chip, +					     const struct of_phandle_args *args) +{ +	if (args->args[0] >= chip->npwm) +		return ERR_PTR(-EINVAL); + +	return pwm_request_from_chip(chip, args->args[0], NULL); +} + +static int clps711x_pwm_probe(struct platform_device *pdev) +{ +	struct clps711x_chip *priv; +	struct resource *res; + +	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); +	if (!priv) +		return -ENOMEM; + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	priv->pmpcon = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(priv->pmpcon)) +		return PTR_ERR(priv->pmpcon); + +	priv->clk = devm_clk_get(&pdev->dev, NULL); +	if (IS_ERR(priv->clk)) +		return PTR_ERR(priv->clk); + +	priv->chip.ops = &clps711x_pwm_ops; +	priv->chip.dev = &pdev->dev; +	priv->chip.base = -1; +	priv->chip.npwm = 2; +	priv->chip.of_xlate = clps711x_pwm_xlate; +	priv->chip.of_pwm_n_cells = 1; + +	spin_lock_init(&priv->lock); + +	platform_set_drvdata(pdev, priv); + +	return pwmchip_add(&priv->chip); +} + +static int clps711x_pwm_remove(struct platform_device *pdev) +{ +	struct clps711x_chip *priv = platform_get_drvdata(pdev); + +	return pwmchip_remove(&priv->chip); +} + +static const struct of_device_id __maybe_unused clps711x_pwm_dt_ids[] = { +	{ .compatible = "cirrus,clps711x-pwm", }, +	{ } +}; +MODULE_DEVICE_TABLE(of, clps711x_pwm_dt_ids); + +static struct platform_driver clps711x_pwm_driver = { +	.driver = { +		.name = "clps711x-pwm", +		.owner = THIS_MODULE, +		.of_match_table = of_match_ptr(clps711x_pwm_dt_ids), +	}, +	.probe = clps711x_pwm_probe, +	.remove = clps711x_pwm_remove, +}; +module_platform_driver(clps711x_pwm_driver); + +MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>"); +MODULE_DESCRIPTION("Cirrus Logic CLPS711X PWM driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-ep93xx.c b/drivers/pwm/pwm-ep93xx.c new file mode 100644 index 00000000000..e593e9c45c5 --- /dev/null +++ b/drivers/pwm/pwm-ep93xx.c @@ -0,0 +1,230 @@ +/* + * PWM framework driver for Cirrus Logic EP93xx + * + * Copyright (c) 2009        Matthieu Crapet <mcrapet@gmail.com> + * Copyright (c) 2009, 2013  H Hartley Sweeten <hsweeten@visionengravers.com> + * + * EP9301/02 have only one channel: + *   platform device ep93xx-pwm.1 - PWMOUT1 (EGPIO14) + * + * EP9307 has only one channel: + *   platform device ep93xx-pwm.0 - PWMOUT + * + * EP9312/15 have two channels: + *   platform device ep93xx-pwm.0 - PWMOUT + *   platform device ep93xx-pwm.1 - PWMOUT1 (EGPIO14) + * + * 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. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/pwm.h> + +#include <asm/div64.h> + +#include <mach/platform.h>	/* for ep93xx_pwm_{acquire,release}_gpio() */ + +#define EP93XX_PWMx_TERM_COUNT	0x00 +#define EP93XX_PWMx_DUTY_CYCLE	0x04 +#define EP93XX_PWMx_ENABLE	0x08 +#define EP93XX_PWMx_INVERT	0x0c + +struct ep93xx_pwm { +	void __iomem *base; +	struct clk *clk; +	struct pwm_chip chip; +}; + +static inline struct ep93xx_pwm *to_ep93xx_pwm(struct pwm_chip *chip) +{ +	return container_of(chip, struct ep93xx_pwm, chip); +} + +static int ep93xx_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct platform_device *pdev = to_platform_device(chip->dev); + +	return ep93xx_pwm_acquire_gpio(pdev); +} + +static void ep93xx_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct platform_device *pdev = to_platform_device(chip->dev); + +	ep93xx_pwm_release_gpio(pdev); +} + +static int ep93xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, +			     int duty_ns, int period_ns) +{ +	struct ep93xx_pwm *ep93xx_pwm = to_ep93xx_pwm(chip); +	void __iomem *base = ep93xx_pwm->base; +	unsigned long long c; +	unsigned long period_cycles; +	unsigned long duty_cycles; +	unsigned long term; +	int ret = 0; + +	/* +	 * The clock needs to be enabled to access the PWM registers. +	 * Configuration can be changed at any time. +	 */ +	if (!test_bit(PWMF_ENABLED, &pwm->flags)) { +		ret = clk_enable(ep93xx_pwm->clk); +		if (ret) +			return ret; +	} + +	c = clk_get_rate(ep93xx_pwm->clk); +	c *= period_ns; +	do_div(c, 1000000000); +	period_cycles = c; + +	c = period_cycles; +	c *= duty_ns; +	do_div(c, period_ns); +	duty_cycles = c; + +	if (period_cycles < 0x10000 && duty_cycles < 0x10000) { +		term = readw(base + EP93XX_PWMx_TERM_COUNT); + +		/* Order is important if PWM is running */ +		if (period_cycles > term) { +			writew(period_cycles, base + EP93XX_PWMx_TERM_COUNT); +			writew(duty_cycles, base + EP93XX_PWMx_DUTY_CYCLE); +		} else { +			writew(duty_cycles, base + EP93XX_PWMx_DUTY_CYCLE); +			writew(period_cycles, base + EP93XX_PWMx_TERM_COUNT); +		} +	} else { +		ret = -EINVAL; +	} + +	if (!test_bit(PWMF_ENABLED, &pwm->flags)) +		clk_disable(ep93xx_pwm->clk); + +	return ret; +} + +static int ep93xx_pwm_polarity(struct pwm_chip *chip, struct pwm_device *pwm, +			       enum pwm_polarity polarity) +{ +	struct ep93xx_pwm *ep93xx_pwm = to_ep93xx_pwm(chip); +	int ret; + +	/* +	 * The clock needs to be enabled to access the PWM registers. +	 * Polarity can only be changed when the PWM is disabled. +	 */ +	ret = clk_enable(ep93xx_pwm->clk); +	if (ret) +		return ret; + +	if (polarity == PWM_POLARITY_INVERSED) +		writew(0x1, ep93xx_pwm->base + EP93XX_PWMx_INVERT); +	else +		writew(0x0, ep93xx_pwm->base + EP93XX_PWMx_INVERT); + +	clk_disable(ep93xx_pwm->clk); + +	return 0; +} + +static int ep93xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct ep93xx_pwm *ep93xx_pwm = to_ep93xx_pwm(chip); +	int ret; + +	ret = clk_enable(ep93xx_pwm->clk); +	if (ret) +		return ret; + +	writew(0x1, ep93xx_pwm->base + EP93XX_PWMx_ENABLE); + +	return 0; +} + +static void ep93xx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct ep93xx_pwm *ep93xx_pwm = to_ep93xx_pwm(chip); + +	writew(0x0, ep93xx_pwm->base + EP93XX_PWMx_ENABLE); +	clk_disable(ep93xx_pwm->clk); +} + +static const struct pwm_ops ep93xx_pwm_ops = { +	.request = ep93xx_pwm_request, +	.free = ep93xx_pwm_free, +	.config = ep93xx_pwm_config, +	.set_polarity = ep93xx_pwm_polarity, +	.enable = ep93xx_pwm_enable, +	.disable = ep93xx_pwm_disable, +	.owner = THIS_MODULE, +}; + +static int ep93xx_pwm_probe(struct platform_device *pdev) +{ +	struct ep93xx_pwm *ep93xx_pwm; +	struct resource *res; +	int ret; + +	ep93xx_pwm = devm_kzalloc(&pdev->dev, sizeof(*ep93xx_pwm), GFP_KERNEL); +	if (!ep93xx_pwm) +		return -ENOMEM; + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	ep93xx_pwm->base = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(ep93xx_pwm->base)) +		return PTR_ERR(ep93xx_pwm->base); + +	ep93xx_pwm->clk = devm_clk_get(&pdev->dev, "pwm_clk"); +	if (IS_ERR(ep93xx_pwm->clk)) +		return PTR_ERR(ep93xx_pwm->clk); + +	ep93xx_pwm->chip.dev = &pdev->dev; +	ep93xx_pwm->chip.ops = &ep93xx_pwm_ops; +	ep93xx_pwm->chip.base = -1; +	ep93xx_pwm->chip.npwm = 1; + +	ret = pwmchip_add(&ep93xx_pwm->chip); +	if (ret < 0) +		return ret; + +	platform_set_drvdata(pdev, ep93xx_pwm); +	return 0; +} + +static int ep93xx_pwm_remove(struct platform_device *pdev) +{ +	struct ep93xx_pwm *ep93xx_pwm = platform_get_drvdata(pdev); + +	return pwmchip_remove(&ep93xx_pwm->chip); +} + +static struct platform_driver ep93xx_pwm_driver = { +	.driver = { +		.name = "ep93xx-pwm", +	}, +	.probe = ep93xx_pwm_probe, +	.remove = ep93xx_pwm_remove, +}; +module_platform_driver(ep93xx_pwm_driver); + +MODULE_DESCRIPTION("Cirrus Logic EP93xx PWM driver"); +MODULE_AUTHOR("Matthieu Crapet <mcrapet@gmail.com>"); +MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>"); +MODULE_ALIAS("platform:ep93xx-pwm"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-fsl-ftm.c b/drivers/pwm/pwm-fsl-ftm.c new file mode 100644 index 00000000000..a18bc8fea38 --- /dev/null +++ b/drivers/pwm/pwm-fsl-ftm.c @@ -0,0 +1,496 @@ +/* + *  Freescale FlexTimer Module (FTM) PWM Driver + * + *  Copyright 2012-2013 Freescale Semiconductor, Inc. + * + * 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. + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> + +#define FTM_SC		0x00 +#define FTM_SC_CLK_MASK	0x3 +#define FTM_SC_CLK_SHIFT	3 +#define FTM_SC_CLK(c)	(((c) + 1) << FTM_SC_CLK_SHIFT) +#define FTM_SC_PS_MASK	0x7 +#define FTM_SC_PS_SHIFT	0 + +#define FTM_CNT		0x04 +#define FTM_MOD		0x08 + +#define FTM_CSC_BASE	0x0C +#define FTM_CSC_MSB	BIT(5) +#define FTM_CSC_MSA	BIT(4) +#define FTM_CSC_ELSB	BIT(3) +#define FTM_CSC_ELSA	BIT(2) +#define FTM_CSC(_channel)	(FTM_CSC_BASE + ((_channel) * 8)) + +#define FTM_CV_BASE	0x10 +#define FTM_CV(_channel)	(FTM_CV_BASE + ((_channel) * 8)) + +#define FTM_CNTIN	0x4C +#define FTM_STATUS	0x50 + +#define FTM_MODE	0x54 +#define FTM_MODE_FTMEN	BIT(0) +#define FTM_MODE_INIT	BIT(2) +#define FTM_MODE_PWMSYNC	BIT(3) + +#define FTM_SYNC	0x58 +#define FTM_OUTINIT	0x5C +#define FTM_OUTMASK	0x60 +#define FTM_COMBINE	0x64 +#define FTM_DEADTIME	0x68 +#define FTM_EXTTRIG	0x6C +#define FTM_POL		0x70 +#define FTM_FMS		0x74 +#define FTM_FILTER	0x78 +#define FTM_FLTCTRL	0x7C +#define FTM_QDCTRL	0x80 +#define FTM_CONF	0x84 +#define FTM_FLTPOL	0x88 +#define FTM_SYNCONF	0x8C +#define FTM_INVCTRL	0x90 +#define FTM_SWOCTRL	0x94 +#define FTM_PWMLOAD	0x98 + +enum fsl_pwm_clk { +	FSL_PWM_CLK_SYS, +	FSL_PWM_CLK_FIX, +	FSL_PWM_CLK_EXT, +	FSL_PWM_CLK_CNTEN, +	FSL_PWM_CLK_MAX +}; + +struct fsl_pwm_chip { +	struct pwm_chip chip; + +	struct mutex lock; + +	unsigned int use_count; +	unsigned int cnt_select; +	unsigned int clk_ps; + +	void __iomem *base; + +	int period_ns; + +	struct clk *clk[FSL_PWM_CLK_MAX]; +}; + +static inline struct fsl_pwm_chip *to_fsl_chip(struct pwm_chip *chip) +{ +	return container_of(chip, struct fsl_pwm_chip, chip); +} + +static int fsl_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct fsl_pwm_chip *fpc = to_fsl_chip(chip); + +	return clk_prepare_enable(fpc->clk[FSL_PWM_CLK_SYS]); +} + +static void fsl_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct fsl_pwm_chip *fpc = to_fsl_chip(chip); + +	clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_SYS]); +} + +static int fsl_pwm_calculate_default_ps(struct fsl_pwm_chip *fpc, +					enum fsl_pwm_clk index) +{ +	unsigned long sys_rate, cnt_rate; +	unsigned long long ratio; + +	sys_rate = clk_get_rate(fpc->clk[FSL_PWM_CLK_SYS]); +	if (!sys_rate) +		return -EINVAL; + +	cnt_rate = clk_get_rate(fpc->clk[fpc->cnt_select]); +	if (!cnt_rate) +		return -EINVAL; + +	switch (index) { +	case FSL_PWM_CLK_SYS: +		fpc->clk_ps = 1; +		break; +	case FSL_PWM_CLK_FIX: +		ratio = 2 * cnt_rate - 1; +		do_div(ratio, sys_rate); +		fpc->clk_ps = ratio; +		break; +	case FSL_PWM_CLK_EXT: +		ratio = 4 * cnt_rate - 1; +		do_div(ratio, sys_rate); +		fpc->clk_ps = ratio; +		break; +	default: +		return -EINVAL; +	} + +	return 0; +} + +static unsigned long fsl_pwm_calculate_cycles(struct fsl_pwm_chip *fpc, +					      unsigned long period_ns) +{ +	unsigned long long c, c0; + +	c = clk_get_rate(fpc->clk[fpc->cnt_select]); +	c = c * period_ns; +	do_div(c, 1000000000UL); + +	do { +		c0 = c; +		do_div(c0, (1 << fpc->clk_ps)); +		if (c0 <= 0xFFFF) +			return (unsigned long)c0; +	} while (++fpc->clk_ps < 8); + +	return 0; +} + +static unsigned long fsl_pwm_calculate_period_cycles(struct fsl_pwm_chip *fpc, +						     unsigned long period_ns, +						     enum fsl_pwm_clk index) +{ +	int ret; + +	ret = fsl_pwm_calculate_default_ps(fpc, index); +	if (ret) { +		dev_err(fpc->chip.dev, +			"failed to calculate default prescaler: %d\n", +			ret); +		return 0; +	} + +	return fsl_pwm_calculate_cycles(fpc, period_ns); +} + +static unsigned long fsl_pwm_calculate_period(struct fsl_pwm_chip *fpc, +					      unsigned long period_ns) +{ +	enum fsl_pwm_clk m0, m1; +	unsigned long fix_rate, ext_rate, cycles; + +	cycles = fsl_pwm_calculate_period_cycles(fpc, period_ns, +			FSL_PWM_CLK_SYS); +	if (cycles) { +		fpc->cnt_select = FSL_PWM_CLK_SYS; +		return cycles; +	} + +	fix_rate = clk_get_rate(fpc->clk[FSL_PWM_CLK_FIX]); +	ext_rate = clk_get_rate(fpc->clk[FSL_PWM_CLK_EXT]); + +	if (fix_rate > ext_rate) { +		m0 = FSL_PWM_CLK_FIX; +		m1 = FSL_PWM_CLK_EXT; +	} else { +		m0 = FSL_PWM_CLK_EXT; +		m1 = FSL_PWM_CLK_FIX; +	} + +	cycles = fsl_pwm_calculate_period_cycles(fpc, period_ns, m0); +	if (cycles) { +		fpc->cnt_select = m0; +		return cycles; +	} + +	fpc->cnt_select = m1; + +	return fsl_pwm_calculate_period_cycles(fpc, period_ns, m1); +} + +static unsigned long fsl_pwm_calculate_duty(struct fsl_pwm_chip *fpc, +					    unsigned long period_ns, +					    unsigned long duty_ns) +{ +	unsigned long long val, duty; + +	val = readl(fpc->base + FTM_MOD); +	duty = duty_ns * (val + 1); +	do_div(duty, period_ns); + +	return (unsigned long)duty; +} + +static int fsl_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, +			  int duty_ns, int period_ns) +{ +	struct fsl_pwm_chip *fpc = to_fsl_chip(chip); +	u32 val, period, duty; + +	mutex_lock(&fpc->lock); + +	/* +	 * The Freescale FTM controller supports only a single period for +	 * all PWM channels, therefore incompatible changes need to be +	 * refused. +	 */ +	if (fpc->period_ns && fpc->period_ns != period_ns) { +		dev_err(fpc->chip.dev, +			"conflicting period requested for PWM %u\n", +			pwm->hwpwm); +		mutex_unlock(&fpc->lock); +		return -EBUSY; +	} + +	if (!fpc->period_ns && duty_ns) { +		period = fsl_pwm_calculate_period(fpc, period_ns); +		if (!period) { +			dev_err(fpc->chip.dev, "failed to calculate period\n"); +			mutex_unlock(&fpc->lock); +			return -EINVAL; +		} + +		val = readl(fpc->base + FTM_SC); +		val &= ~(FTM_SC_PS_MASK << FTM_SC_PS_SHIFT); +		val |= fpc->clk_ps; +		writel(val, fpc->base + FTM_SC); +		writel(period - 1, fpc->base + FTM_MOD); + +		fpc->period_ns = period_ns; +	} + +	mutex_unlock(&fpc->lock); + +	duty = fsl_pwm_calculate_duty(fpc, period_ns, duty_ns); + +	writel(FTM_CSC_MSB | FTM_CSC_ELSB, fpc->base + FTM_CSC(pwm->hwpwm)); +	writel(duty, fpc->base + FTM_CV(pwm->hwpwm)); + +	return 0; +} + +static int fsl_pwm_set_polarity(struct pwm_chip *chip, +				struct pwm_device *pwm, +				enum pwm_polarity polarity) +{ +	struct fsl_pwm_chip *fpc = to_fsl_chip(chip); +	u32 val; + +	val = readl(fpc->base + FTM_POL); + +	if (polarity == PWM_POLARITY_INVERSED) +		val |= BIT(pwm->hwpwm); +	else +		val &= ~BIT(pwm->hwpwm); + +	writel(val, fpc->base + FTM_POL); + +	return 0; +} + +static int fsl_counter_clock_enable(struct fsl_pwm_chip *fpc) +{ +	u32 val; +	int ret; + +	if (fpc->use_count != 0) +		return 0; + +	/* select counter clock source */ +	val = readl(fpc->base + FTM_SC); +	val &= ~(FTM_SC_CLK_MASK << FTM_SC_CLK_SHIFT); +	val |= FTM_SC_CLK(fpc->cnt_select); +	writel(val, fpc->base + FTM_SC); + +	ret = clk_prepare_enable(fpc->clk[fpc->cnt_select]); +	if (ret) +		return ret; + +	ret = clk_prepare_enable(fpc->clk[FSL_PWM_CLK_CNTEN]); +	if (ret) { +		clk_disable_unprepare(fpc->clk[fpc->cnt_select]); +		return ret; +	} + +	fpc->use_count++; + +	return 0; +} + +static int fsl_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct fsl_pwm_chip *fpc = to_fsl_chip(chip); +	u32 val; +	int ret; + +	mutex_lock(&fpc->lock); +	val = readl(fpc->base + FTM_OUTMASK); +	val &= ~BIT(pwm->hwpwm); +	writel(val, fpc->base + FTM_OUTMASK); + +	ret = fsl_counter_clock_enable(fpc); +	mutex_unlock(&fpc->lock); + +	return ret; +} + +static void fsl_counter_clock_disable(struct fsl_pwm_chip *fpc) +{ +	u32 val; + +	/* +	 * already disabled, do nothing +	 */ +	if (fpc->use_count == 0) +		return; + +	/* there are still users, so can't disable yet */ +	if (--fpc->use_count > 0) +		return; + +	/* no users left, disable PWM counter clock */ +	val = readl(fpc->base + FTM_SC); +	val &= ~(FTM_SC_CLK_MASK << FTM_SC_CLK_SHIFT); +	writel(val, fpc->base + FTM_SC); + +	clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_CNTEN]); +	clk_disable_unprepare(fpc->clk[fpc->cnt_select]); +} + +static void fsl_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct fsl_pwm_chip *fpc = to_fsl_chip(chip); +	u32 val; + +	mutex_lock(&fpc->lock); +	val = readl(fpc->base + FTM_OUTMASK); +	val |= BIT(pwm->hwpwm); +	writel(val, fpc->base + FTM_OUTMASK); + +	fsl_counter_clock_disable(fpc); + +	val = readl(fpc->base + FTM_OUTMASK); + +	if ((val & 0xFF) == 0xFF) +		fpc->period_ns = 0; + +	mutex_unlock(&fpc->lock); +} + +static const struct pwm_ops fsl_pwm_ops = { +	.request = fsl_pwm_request, +	.free = fsl_pwm_free, +	.config = fsl_pwm_config, +	.set_polarity = fsl_pwm_set_polarity, +	.enable = fsl_pwm_enable, +	.disable = fsl_pwm_disable, +	.owner = THIS_MODULE, +}; + +static int fsl_pwm_init(struct fsl_pwm_chip *fpc) +{ +	int ret; + +	ret = clk_prepare_enable(fpc->clk[FSL_PWM_CLK_SYS]); +	if (ret) +		return ret; + +	writel(0x00, fpc->base + FTM_CNTIN); +	writel(0x00, fpc->base + FTM_OUTINIT); +	writel(0xFF, fpc->base + FTM_OUTMASK); + +	clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_SYS]); + +	return 0; +} + +static int fsl_pwm_probe(struct platform_device *pdev) +{ +	struct fsl_pwm_chip *fpc; +	struct resource *res; +	int ret; + +	fpc = devm_kzalloc(&pdev->dev, sizeof(*fpc), GFP_KERNEL); +	if (!fpc) +		return -ENOMEM; + +	mutex_init(&fpc->lock); + +	fpc->chip.dev = &pdev->dev; + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	fpc->base = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(fpc->base)) +		return PTR_ERR(fpc->base); + +	fpc->clk[FSL_PWM_CLK_SYS] = devm_clk_get(&pdev->dev, "ftm_sys"); +	if (IS_ERR(fpc->clk[FSL_PWM_CLK_SYS])) { +		dev_err(&pdev->dev, "failed to get \"ftm_sys\" clock\n"); +		return PTR_ERR(fpc->clk[FSL_PWM_CLK_SYS]); +	} + +	fpc->clk[FSL_PWM_CLK_FIX] = devm_clk_get(fpc->chip.dev, "ftm_fix"); +	if (IS_ERR(fpc->clk[FSL_PWM_CLK_FIX])) +		return PTR_ERR(fpc->clk[FSL_PWM_CLK_FIX]); + +	fpc->clk[FSL_PWM_CLK_EXT] = devm_clk_get(fpc->chip.dev, "ftm_ext"); +	if (IS_ERR(fpc->clk[FSL_PWM_CLK_EXT])) +		return PTR_ERR(fpc->clk[FSL_PWM_CLK_EXT]); + +	fpc->clk[FSL_PWM_CLK_CNTEN] = +				devm_clk_get(fpc->chip.dev, "ftm_cnt_clk_en"); +	if (IS_ERR(fpc->clk[FSL_PWM_CLK_CNTEN])) +		return PTR_ERR(fpc->clk[FSL_PWM_CLK_CNTEN]); + +	fpc->chip.ops = &fsl_pwm_ops; +	fpc->chip.of_xlate = of_pwm_xlate_with_flags; +	fpc->chip.of_pwm_n_cells = 3; +	fpc->chip.base = -1; +	fpc->chip.npwm = 8; +	fpc->chip.can_sleep = true; + +	ret = pwmchip_add(&fpc->chip); +	if (ret < 0) { +		dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret); +		return ret; +	} + +	platform_set_drvdata(pdev, fpc); + +	return fsl_pwm_init(fpc); +} + +static int fsl_pwm_remove(struct platform_device *pdev) +{ +	struct fsl_pwm_chip *fpc = platform_get_drvdata(pdev); + +	return pwmchip_remove(&fpc->chip); +} + +static const struct of_device_id fsl_pwm_dt_ids[] = { +	{ .compatible = "fsl,vf610-ftm-pwm", }, +	{ /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, fsl_pwm_dt_ids); + +static struct platform_driver fsl_pwm_driver = { +	.driver = { +		.name = "fsl-ftm-pwm", +		.of_match_table = fsl_pwm_dt_ids, +	}, +	.probe = fsl_pwm_probe, +	.remove = fsl_pwm_remove, +}; +module_platform_driver(fsl_pwm_driver); + +MODULE_DESCRIPTION("Freescale FlexTimer Module PWM Driver"); +MODULE_AUTHOR("Xiubo Li <Li.Xiubo@freescale.com>"); +MODULE_ALIAS("platform:fsl-ftm-pwm"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-imx.c b/drivers/pwm/pwm-imx.c new file mode 100644 index 00000000000..d797c7b84c3 --- /dev/null +++ b/drivers/pwm/pwm-imx.c @@ -0,0 +1,307 @@ +/* + * 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 <eric.miao@marvell.com> + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/pwm.h> +#include <linux/of.h> +#include <linux/of_device.h> + +/* 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 */ + +#define MX1_PWMC_EN		(1 << 4) + +/* 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_per; +	struct clk	*clk_ipg; + +	void __iomem	*mmio_base; + +	struct pwm_chip	chip; + +	int (*config)(struct pwm_chip *chip, +		struct pwm_device *pwm, int duty_ns, int period_ns); +	void (*set_enable)(struct pwm_chip *chip, bool enable); +}; + +#define to_imx_chip(chip)	container_of(chip, struct imx_chip, chip) + +static int imx_pwm_config_v1(struct pwm_chip *chip, +		struct pwm_device *pwm, int duty_ns, int period_ns) +{ +	struct imx_chip *imx = to_imx_chip(chip); + +	/* +	 * 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); + +	return 0; +} + +static void imx_pwm_set_enable_v1(struct pwm_chip *chip, bool enable) +{ +	struct imx_chip *imx = to_imx_chip(chip); +	u32 val; + +	val = readl(imx->mmio_base + MX1_PWMC); + +	if (enable) +		val |= MX1_PWMC_EN; +	else +		val &= ~MX1_PWMC_EN; + +	writel(val, imx->mmio_base + MX1_PWMC); +} + +static int imx_pwm_config_v2(struct pwm_chip *chip, +		struct pwm_device *pwm, int duty_ns, int period_ns) +{ +	struct imx_chip *imx = to_imx_chip(chip); +	unsigned long long c; +	unsigned long period_cycles, duty_cycles, prescale; +	u32 cr; + +	c = clk_get_rate(imx->clk_per); +	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_CLKSRC_IPG_HIGH; + +	if (test_bit(PWMF_ENABLED, &pwm->flags)) +		cr |= MX3_PWMCR_EN; + +	writel(cr, imx->mmio_base + MX3_PWMCR); + +	return 0; +} + +static void imx_pwm_set_enable_v2(struct pwm_chip *chip, bool enable) +{ +	struct imx_chip *imx = to_imx_chip(chip); +	u32 val; + +	val = readl(imx->mmio_base + MX3_PWMCR); + +	if (enable) +		val |= MX3_PWMCR_EN; +	else +		val &= ~MX3_PWMCR_EN; + +	writel(val, imx->mmio_base + MX3_PWMCR); +} + +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); +	int ret; + +	ret = clk_prepare_enable(imx->clk_ipg); +	if (ret) +		return ret; + +	ret = imx->config(chip, pwm, duty_ns, period_ns); + +	clk_disable_unprepare(imx->clk_ipg); + +	return ret; +} + +static int imx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct imx_chip *imx = to_imx_chip(chip); +	int ret; + +	ret = clk_prepare_enable(imx->clk_per); +	if (ret) +		return ret; + +	imx->set_enable(chip, true); + +	return 0; +} + +static void imx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct imx_chip *imx = to_imx_chip(chip); + +	imx->set_enable(chip, false); + +	clk_disable_unprepare(imx->clk_per); +} + +static struct pwm_ops imx_pwm_ops = { +	.enable = imx_pwm_enable, +	.disable = imx_pwm_disable, +	.config = imx_pwm_config, +	.owner = THIS_MODULE, +}; + +struct imx_pwm_data { +	int (*config)(struct pwm_chip *chip, +		struct pwm_device *pwm, int duty_ns, int period_ns); +	void (*set_enable)(struct pwm_chip *chip, bool enable); +}; + +static struct imx_pwm_data imx_pwm_data_v1 = { +	.config = imx_pwm_config_v1, +	.set_enable = imx_pwm_set_enable_v1, +}; + +static struct imx_pwm_data imx_pwm_data_v2 = { +	.config = imx_pwm_config_v2, +	.set_enable = imx_pwm_set_enable_v2, +}; + +static const struct of_device_id imx_pwm_dt_ids[] = { +	{ .compatible = "fsl,imx1-pwm", .data = &imx_pwm_data_v1, }, +	{ .compatible = "fsl,imx27-pwm", .data = &imx_pwm_data_v2, }, +	{ /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_pwm_dt_ids); + +static int imx_pwm_probe(struct platform_device *pdev) +{ +	const struct of_device_id *of_id = +			of_match_device(imx_pwm_dt_ids, &pdev->dev); +	const struct imx_pwm_data *data; +	struct imx_chip *imx; +	struct resource *r; +	int ret = 0; + +	if (!of_id) +		return -ENODEV; + +	imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL); +	if (imx == NULL) +		return -ENOMEM; + +	imx->clk_per = devm_clk_get(&pdev->dev, "per"); +	if (IS_ERR(imx->clk_per)) { +		dev_err(&pdev->dev, "getting per clock failed with %ld\n", +				PTR_ERR(imx->clk_per)); +		return PTR_ERR(imx->clk_per); +	} + +	imx->clk_ipg = devm_clk_get(&pdev->dev, "ipg"); +	if (IS_ERR(imx->clk_ipg)) { +		dev_err(&pdev->dev, "getting ipg clock failed with %ld\n", +				PTR_ERR(imx->clk_ipg)); +		return PTR_ERR(imx->clk_ipg); +	} + +	imx->chip.ops = &imx_pwm_ops; +	imx->chip.dev = &pdev->dev; +	imx->chip.base = -1; +	imx->chip.npwm = 1; + +	r = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	imx->mmio_base = devm_ioremap_resource(&pdev->dev, r); +	if (IS_ERR(imx->mmio_base)) +		return PTR_ERR(imx->mmio_base); + +	data = of_id->data; +	imx->config = data->config; +	imx->set_enable = data->set_enable; + +	ret = pwmchip_add(&imx->chip); +	if (ret < 0) +		return ret; + +	platform_set_drvdata(pdev, imx); +	return 0; +} + +static int imx_pwm_remove(struct platform_device *pdev) +{ +	struct imx_chip *imx; + +	imx = platform_get_drvdata(pdev); +	if (imx == NULL) +		return -ENODEV; + +	return pwmchip_remove(&imx->chip); +} + +static struct platform_driver imx_pwm_driver = { +	.driver		= { +		.name	= "imx-pwm", +		.owner = THIS_MODULE, +		.of_match_table = imx_pwm_dt_ids, +	}, +	.probe		= imx_pwm_probe, +	.remove		= imx_pwm_remove, +}; + +module_platform_driver(imx_pwm_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c new file mode 100644 index 00000000000..9c46209e1d0 --- /dev/null +++ b/drivers/pwm/pwm-jz4740.c @@ -0,0 +1,207 @@ +/* + *  Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de> + *  JZ4740 platform PWM support + * + *  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. + * + *  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 <linux/clk.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> + +#include <asm/mach-jz4740/gpio.h> +#include <asm/mach-jz4740/timer.h> + +#define NUM_PWM 8 + +static const unsigned int jz4740_pwm_gpio_list[NUM_PWM] = { +	JZ_GPIO_PWM0, +	JZ_GPIO_PWM1, +	JZ_GPIO_PWM2, +	JZ_GPIO_PWM3, +	JZ_GPIO_PWM4, +	JZ_GPIO_PWM5, +	JZ_GPIO_PWM6, +	JZ_GPIO_PWM7, +}; + +struct jz4740_pwm_chip { +	struct pwm_chip chip; +	struct clk *clk; +}; + +static inline struct jz4740_pwm_chip *to_jz4740(struct pwm_chip *chip) +{ +	return container_of(chip, struct jz4740_pwm_chip, chip); +} + +static int jz4740_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	unsigned int gpio = jz4740_pwm_gpio_list[pwm->hwpwm]; +	int ret; + +	/* +	 * Timers 0 and 1 are used for system tasks, so they are unavailable +	 * for use as PWMs. +	 */ +	if (pwm->hwpwm < 2) +		return -EBUSY; + +	ret = gpio_request(gpio, pwm->label); +	if (ret) { +		dev_err(chip->dev, "Failed to request GPIO#%u for PWM: %d\n", +			gpio, ret); +		return ret; +	} + +	jz_gpio_set_function(gpio, JZ_GPIO_FUNC_PWM); + +	jz4740_timer_start(pwm->hwpwm); + +	return 0; +} + +static void jz4740_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	unsigned int gpio = jz4740_pwm_gpio_list[pwm->hwpwm]; + +	jz4740_timer_set_ctrl(pwm->hwpwm, 0); + +	jz_gpio_set_function(gpio, JZ_GPIO_FUNC_NONE); +	gpio_free(gpio); + +	jz4740_timer_stop(pwm->hwpwm); +} + +static int jz4740_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	uint32_t ctrl = jz4740_timer_get_ctrl(pwm->pwm); + +	ctrl |= JZ_TIMER_CTRL_PWM_ENABLE; +	jz4740_timer_set_ctrl(pwm->hwpwm, ctrl); +	jz4740_timer_enable(pwm->hwpwm); + +	return 0; +} + +static void jz4740_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	uint32_t ctrl = jz4740_timer_get_ctrl(pwm->hwpwm); + +	ctrl &= ~JZ_TIMER_CTRL_PWM_ENABLE; +	jz4740_timer_disable(pwm->hwpwm); +	jz4740_timer_set_ctrl(pwm->hwpwm, ctrl); +} + +static int jz4740_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, +			     int duty_ns, int period_ns) +{ +	struct jz4740_pwm_chip *jz4740 = to_jz4740(pwm->chip); +	unsigned long long tmp; +	unsigned long period, duty; +	unsigned int prescaler = 0; +	uint16_t ctrl; +	bool is_enabled; + +	tmp = (unsigned long long)clk_get_rate(jz4740->clk) * period_ns; +	do_div(tmp, 1000000000); +	period = tmp; + +	while (period > 0xffff && prescaler < 6) { +		period >>= 2; +		++prescaler; +	} + +	if (prescaler == 6) +		return -EINVAL; + +	tmp = (unsigned long long)period * duty_ns; +	do_div(tmp, period_ns); +	duty = period - tmp; + +	if (duty >= period) +		duty = period - 1; + +	is_enabled = jz4740_timer_is_enabled(pwm->hwpwm); +	if (is_enabled) +		jz4740_pwm_disable(chip, pwm); + +	jz4740_timer_set_count(pwm->hwpwm, 0); +	jz4740_timer_set_duty(pwm->hwpwm, duty); +	jz4740_timer_set_period(pwm->hwpwm, period); + +	ctrl = JZ_TIMER_CTRL_PRESCALER(prescaler) | JZ_TIMER_CTRL_SRC_EXT | +		JZ_TIMER_CTRL_PWM_ABBRUPT_SHUTDOWN; + +	jz4740_timer_set_ctrl(pwm->hwpwm, ctrl); + +	if (is_enabled) +		jz4740_pwm_enable(chip, pwm); + +	return 0; +} + +static const struct pwm_ops jz4740_pwm_ops = { +	.request = jz4740_pwm_request, +	.free = jz4740_pwm_free, +	.config = jz4740_pwm_config, +	.enable = jz4740_pwm_enable, +	.disable = jz4740_pwm_disable, +	.owner = THIS_MODULE, +}; + +static int jz4740_pwm_probe(struct platform_device *pdev) +{ +	struct jz4740_pwm_chip *jz4740; + +	jz4740 = devm_kzalloc(&pdev->dev, sizeof(*jz4740), GFP_KERNEL); +	if (!jz4740) +		return -ENOMEM; + +	jz4740->clk = devm_clk_get(&pdev->dev, "ext"); +	if (IS_ERR(jz4740->clk)) +		return PTR_ERR(jz4740->clk); + +	jz4740->chip.dev = &pdev->dev; +	jz4740->chip.ops = &jz4740_pwm_ops; +	jz4740->chip.npwm = NUM_PWM; +	jz4740->chip.base = -1; + +	platform_set_drvdata(pdev, jz4740); + +	return pwmchip_add(&jz4740->chip); +} + +static int jz4740_pwm_remove(struct platform_device *pdev) +{ +	struct jz4740_pwm_chip *jz4740 = platform_get_drvdata(pdev); + +	return pwmchip_remove(&jz4740->chip); +} + +static struct platform_driver jz4740_pwm_driver = { +	.driver = { +		.name = "jz4740-pwm", +		.owner = THIS_MODULE, +	}, +	.probe = jz4740_pwm_probe, +	.remove = jz4740_pwm_remove, +}; +module_platform_driver(jz4740_pwm_driver); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("Ingenic JZ4740 PWM driver"); +MODULE_ALIAS("platform:jz4740-pwm"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-lp3943.c b/drivers/pwm/pwm-lp3943.c new file mode 100644 index 00000000000..2c39b0e50fa --- /dev/null +++ b/drivers/pwm/pwm-lp3943.c @@ -0,0 +1,317 @@ +/* + * TI/National Semiconductor LP3943 PWM driver + * + * Copyright 2013 Texas Instruments + * + * Author: Milo Kim <milo.kim@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; version 2. + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/mfd/lp3943.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> + +#define LP3943_MAX_DUTY			255 +#define LP3943_MIN_PERIOD		6250 +#define LP3943_MAX_PERIOD		1600000 + +struct lp3943_pwm { +	struct pwm_chip chip; +	struct lp3943 *lp3943; +	struct lp3943_platform_data *pdata; +}; + +static inline struct lp3943_pwm *to_lp3943_pwm(struct pwm_chip *_chip) +{ +	return container_of(_chip, struct lp3943_pwm, chip); +} + +static struct lp3943_pwm_map * +lp3943_pwm_request_map(struct lp3943_pwm *lp3943_pwm, int hwpwm) +{ +	struct lp3943_platform_data *pdata = lp3943_pwm->pdata; +	struct lp3943 *lp3943 = lp3943_pwm->lp3943; +	struct lp3943_pwm_map *pwm_map; +	int i, offset; + +	pwm_map = kzalloc(sizeof(*pwm_map), GFP_KERNEL); +	if (!pwm_map) +		return ERR_PTR(-ENOMEM); + +	pwm_map->output = pdata->pwms[hwpwm]->output; +	pwm_map->num_outputs = pdata->pwms[hwpwm]->num_outputs; + +	for (i = 0; i < pwm_map->num_outputs; i++) { +		offset = pwm_map->output[i]; + +		/* Return an error if the pin is already assigned */ +		if (test_and_set_bit(offset, &lp3943->pin_used)) { +			kfree(pwm_map); +			return ERR_PTR(-EBUSY); +		} +	} + +	return pwm_map; +} + +static int lp3943_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct lp3943_pwm *lp3943_pwm = to_lp3943_pwm(chip); +	struct lp3943_pwm_map *pwm_map; + +	pwm_map = lp3943_pwm_request_map(lp3943_pwm, pwm->hwpwm); +	if (IS_ERR(pwm_map)) +		return PTR_ERR(pwm_map); + +	return pwm_set_chip_data(pwm, pwm_map); +} + +static void lp3943_pwm_free_map(struct lp3943_pwm *lp3943_pwm, +				struct lp3943_pwm_map *pwm_map) +{ +	struct lp3943 *lp3943 = lp3943_pwm->lp3943; +	int i, offset; + +	for (i = 0; i < pwm_map->num_outputs; i++) { +		offset = pwm_map->output[i]; +		clear_bit(offset, &lp3943->pin_used); +	} + +	kfree(pwm_map); +} + +static void lp3943_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct lp3943_pwm *lp3943_pwm = to_lp3943_pwm(chip); +	struct lp3943_pwm_map *pwm_map = pwm_get_chip_data(pwm); + +	lp3943_pwm_free_map(lp3943_pwm, pwm_map); +} + +static int lp3943_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, +			     int duty_ns, int period_ns) +{ +	struct lp3943_pwm *lp3943_pwm = to_lp3943_pwm(chip); +	struct lp3943 *lp3943 = lp3943_pwm->lp3943; +	u8 val, reg_duty, reg_prescale; +	int err; + +	/* +	 * How to configure the LP3943 PWMs +	 * +	 * 1) Period = 6250 ~ 1600000 +	 * 2) Prescale = period / 6250 -1 +	 * 3) Duty = input duty +	 * +	 * Prescale and duty are register values +	 */ + +	if (pwm->hwpwm == 0) { +		reg_prescale = LP3943_REG_PRESCALE0; +		reg_duty     = LP3943_REG_PWM0; +	} else { +		reg_prescale = LP3943_REG_PRESCALE1; +		reg_duty     = LP3943_REG_PWM1; +	} + +	period_ns = clamp(period_ns, LP3943_MIN_PERIOD, LP3943_MAX_PERIOD); +	val       = (u8)(period_ns / LP3943_MIN_PERIOD - 1); + +	err = lp3943_write_byte(lp3943, reg_prescale, val); +	if (err) +		return err; + +	val = (u8)(duty_ns * LP3943_MAX_DUTY / period_ns); + +	return lp3943_write_byte(lp3943, reg_duty, val); +} + +static int lp3943_pwm_set_mode(struct lp3943_pwm *lp3943_pwm, +			       struct lp3943_pwm_map *pwm_map, +			       u8 val) +{ +	struct lp3943 *lp3943 = lp3943_pwm->lp3943; +	const struct lp3943_reg_cfg *mux = lp3943->mux_cfg; +	int i, index, err; + +	for (i = 0; i < pwm_map->num_outputs; i++) { +		index = pwm_map->output[i]; +		err = lp3943_update_bits(lp3943, mux[index].reg, +					 mux[index].mask, +					 val << mux[index].shift); +		if (err) +			return err; +	} + +	return 0; +} + +static int lp3943_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct lp3943_pwm *lp3943_pwm = to_lp3943_pwm(chip); +	struct lp3943_pwm_map *pwm_map = pwm_get_chip_data(pwm); +	u8 val; + +	if (pwm->hwpwm == 0) +		val = LP3943_DIM_PWM0; +	else +		val = LP3943_DIM_PWM1; + +	/* +	 * Each PWM generator is set to control any of outputs of LP3943. +	 * To enable/disable the PWM, these output pins should be configured. +	 */ + +	return lp3943_pwm_set_mode(lp3943_pwm, pwm_map, val); +} + +static void lp3943_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct lp3943_pwm *lp3943_pwm = to_lp3943_pwm(chip); +	struct lp3943_pwm_map *pwm_map = pwm_get_chip_data(pwm); + +	/* +	 * LP3943 outputs are open-drain, so the pin should be configured +	 * when the PWM is disabled. +	 */ + +	lp3943_pwm_set_mode(lp3943_pwm, pwm_map, LP3943_GPIO_OUT_HIGH); +} + +static const struct pwm_ops lp3943_pwm_ops = { +	.request	= lp3943_pwm_request, +	.free		= lp3943_pwm_free, +	.config		= lp3943_pwm_config, +	.enable		= lp3943_pwm_enable, +	.disable	= lp3943_pwm_disable, +	.owner		= THIS_MODULE, +}; + +static int lp3943_pwm_parse_dt(struct device *dev, +			       struct lp3943_pwm *lp3943_pwm) +{ +	static const char * const name[] = { "ti,pwm0", "ti,pwm1", }; +	struct device_node *node = dev->of_node; +	struct lp3943_platform_data *pdata; +	struct lp3943_pwm_map *pwm_map; +	enum lp3943_pwm_output *output; +	int i, err, proplen, count = 0; +	u32 num_outputs; + +	if (!node) +		return -EINVAL; + +	pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); +	if (!pdata) +		return -ENOMEM; + +	/* +	 * Read the output map configuration from the device tree. +	 * Each of the two PWM generators can drive zero or more outputs. +	 */ + +	for (i = 0; i < LP3943_NUM_PWMS; i++) { +		if (!of_get_property(node, name[i], &proplen)) +			continue; + +		num_outputs = proplen / sizeof(u32); +		if (num_outputs == 0) +			continue; + +		output = devm_kzalloc(dev, sizeof(*output) * num_outputs, +				      GFP_KERNEL); +		if (!output) +			return -ENOMEM; + +		err = of_property_read_u32_array(node, name[i], output, +						 num_outputs); +		if (err) +			return err; + +		pwm_map = devm_kzalloc(dev, sizeof(*pwm_map), GFP_KERNEL); +		if (!pwm_map) +			return -ENOMEM; + +		pwm_map->output = output; +		pwm_map->num_outputs = num_outputs; +		pdata->pwms[i] = pwm_map; + +		count++; +	} + +	if (count == 0) +		return -ENODATA; + +	lp3943_pwm->pdata = pdata; +	return 0; +} + +static int lp3943_pwm_probe(struct platform_device *pdev) +{ +	struct lp3943 *lp3943 = dev_get_drvdata(pdev->dev.parent); +	struct lp3943_pwm *lp3943_pwm; +	int ret; + +	lp3943_pwm = devm_kzalloc(&pdev->dev, sizeof(*lp3943_pwm), GFP_KERNEL); +	if (!lp3943_pwm) +		return -ENOMEM; + +	lp3943_pwm->pdata = lp3943->pdata; +	if (!lp3943_pwm->pdata) { +		if (IS_ENABLED(CONFIG_OF)) +			ret = lp3943_pwm_parse_dt(&pdev->dev, lp3943_pwm); +		else +			ret = -ENODEV; + +		if (ret) +			return ret; +	} + +	lp3943_pwm->lp3943 = lp3943; +	lp3943_pwm->chip.dev = &pdev->dev; +	lp3943_pwm->chip.ops = &lp3943_pwm_ops; +	lp3943_pwm->chip.npwm = LP3943_NUM_PWMS; +	lp3943_pwm->chip.can_sleep = true; + +	platform_set_drvdata(pdev, lp3943_pwm); + +	return pwmchip_add(&lp3943_pwm->chip); +} + +static int lp3943_pwm_remove(struct platform_device *pdev) +{ +	struct lp3943_pwm *lp3943_pwm = platform_get_drvdata(pdev); + +	return pwmchip_remove(&lp3943_pwm->chip); +} + +#ifdef CONFIG_OF +static const struct of_device_id lp3943_pwm_of_match[] = { +	{ .compatible = "ti,lp3943-pwm", }, +	{ } +}; +MODULE_DEVICE_TABLE(of, lp3943_pwm_of_match); +#endif + +static struct platform_driver lp3943_pwm_driver = { +	.probe = lp3943_pwm_probe, +	.remove = lp3943_pwm_remove, +	.driver = { +		.name = "lp3943-pwm", +		.owner = THIS_MODULE, +		.of_match_table = of_match_ptr(lp3943_pwm_of_match), +	}, +}; +module_platform_driver(lp3943_pwm_driver); + +MODULE_DESCRIPTION("LP3943 PWM driver"); +MODULE_ALIAS("platform:lp3943-pwm"); +MODULE_AUTHOR("Milo Kim"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-lpc32xx.c b/drivers/pwm/pwm-lpc32xx.c new file mode 100644 index 00000000000..9dc0f9d42bf --- /dev/null +++ b/drivers/pwm/pwm-lpc32xx.c @@ -0,0 +1,182 @@ +/* + * Copyright 2012 Alexandre Pereira da Silva <aletes.xgr@gmail.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; version 2. + * + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> + +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; +	u32 val; + +	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; + +	/* The duty-cycle value is as follows: +	 * +	 *  DUTY-CYCLE     HIGH LEVEL +	 *      1            99.9% +	 *      25           90.0% +	 *      128          50.0% +	 *      220          10.0% +	 *      255           0.1% +	 *      0             0.0% +	 * +	 * In other words, the register value is duty-cycle % 256 with +	 * duty-cycle in the range 1-256. +	 */ +	c = 256 * duty_ns; +	do_div(c, period_ns); +	if (c > 255) +		c = 255; +	duty_cycles = 256 - c; + +	val = readl(lpc32xx->base + (pwm->hwpwm << 2)); +	val &= ~0xFFFF; +	val |= PWM_RELOADV(period_cycles) | PWM_DUTY(duty_cycles); +	writel(val, 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); +	u32 val; +	int ret; + +	ret = clk_enable(lpc32xx->clk); +	if (ret) +		return ret; + +	val = readl(lpc32xx->base + (pwm->hwpwm << 2)); +	val |= PWM_ENABLE; +	writel(val, lpc32xx->base + (pwm->hwpwm << 2)); + +	return 0; +} + +static void lpc32xx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct lpc32xx_pwm_chip *lpc32xx = to_lpc32xx_pwm_chip(chip); +	u32 val; + +	val = readl(lpc32xx->base + (pwm->hwpwm << 2)); +	val &= ~PWM_ENABLE; +	writel(val, 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); +	lpc32xx->base = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(lpc32xx->base)) +		return PTR_ERR(lpc32xx->base); + +	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; +	lpc32xx->chip.base = -1; + +	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 lpc32xx_pwm_remove(struct platform_device *pdev) +{ +	struct lpc32xx_pwm_chip *lpc32xx = platform_get_drvdata(pdev); +	unsigned int i; + +	for (i = 0; i < lpc32xx->chip.npwm; i++) +		pwm_disable(&lpc32xx->chip.pwms[i]); + +	return pwmchip_remove(&lpc32xx->chip); +} + +static const 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", +		.owner = THIS_MODULE, +		.of_match_table = lpc32xx_pwm_dt_ids, +	}, +	.probe = lpc32xx_pwm_probe, +	.remove = lpc32xx_pwm_remove, +}; +module_platform_driver(lpc32xx_pwm_driver); + +MODULE_ALIAS("platform:lpc32xx-pwm"); +MODULE_AUTHOR("Alexandre Pereira da Silva <aletes.xgr@gmail.com>"); +MODULE_DESCRIPTION("LPC32XX PWM Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-lpss.c b/drivers/pwm/pwm-lpss.c new file mode 100644 index 00000000000..44ce6c6103a --- /dev/null +++ b/drivers/pwm/pwm-lpss.c @@ -0,0 +1,282 @@ +/* + * Intel Low Power Subsystem PWM controller driver + * + * Copyright (C) 2014, Intel Corporation + * Author: Mika Westerberg <mika.westerberg@linux.intel.com> + * Author: Chew Kean Ho <kean.ho.chew@intel.com> + * Author: Chang Rebecca Swee Fun <rebecca.swee.fun.chang@intel.com> + * Author: Chew Chiau Ee <chiau.ee.chew@intel.com> + * Author: Alan Cox <alan@linux.intel.com> + * + * 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. + */ + +#include <linux/acpi.h> +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pwm.h> +#include <linux/platform_device.h> +#include <linux/pci.h> + +static int pci_drv, plat_drv;	/* So we know which drivers registered */ + +#define PWM				0x00000000 +#define PWM_ENABLE			BIT(31) +#define PWM_SW_UPDATE			BIT(30) +#define PWM_BASE_UNIT_SHIFT		8 +#define PWM_BASE_UNIT_MASK		0x00ffff00 +#define PWM_ON_TIME_DIV_MASK		0x000000ff +#define PWM_DIVISION_CORRECTION		0x2 +#define PWM_LIMIT			(0x8000 + PWM_DIVISION_CORRECTION) +#define NSECS_PER_SEC			1000000000UL + +struct pwm_lpss_chip { +	struct pwm_chip chip; +	void __iomem *regs; +	struct clk *clk; +	unsigned long clk_rate; +}; + +struct pwm_lpss_boardinfo { +	unsigned long clk_rate; +}; + +/* BayTrail */ +static const struct pwm_lpss_boardinfo byt_info = { +	25000000 +}; + +static inline struct pwm_lpss_chip *to_lpwm(struct pwm_chip *chip) +{ +	return container_of(chip, struct pwm_lpss_chip, chip); +} + +static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm, +			   int duty_ns, int period_ns) +{ +	struct pwm_lpss_chip *lpwm = to_lpwm(chip); +	u8 on_time_div; +	unsigned long c; +	unsigned long long base_unit, freq = NSECS_PER_SEC; +	u32 ctrl; + +	do_div(freq, period_ns); + +	/* The equation is: base_unit = ((freq / c) * 65536) + correction */ +	base_unit = freq * 65536; + +	c = lpwm->clk_rate; +	if (!c) +		return -EINVAL; + +	do_div(base_unit, c); +	base_unit += PWM_DIVISION_CORRECTION; +	if (base_unit > PWM_LIMIT) +		return -EINVAL; + +	if (duty_ns <= 0) +		duty_ns = 1; +	on_time_div = 255 - (255 * duty_ns / period_ns); + +	ctrl = readl(lpwm->regs + PWM); +	ctrl &= ~(PWM_BASE_UNIT_MASK | PWM_ON_TIME_DIV_MASK); +	ctrl |= (u16) base_unit << PWM_BASE_UNIT_SHIFT; +	ctrl |= on_time_div; +	/* request PWM to update on next cycle */ +	ctrl |= PWM_SW_UPDATE; +	writel(ctrl, lpwm->regs + PWM); + +	return 0; +} + +static int pwm_lpss_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct pwm_lpss_chip *lpwm = to_lpwm(chip); +	u32 ctrl; +	int ret; + +	ret = clk_prepare_enable(lpwm->clk); +	if (ret) +		return ret; + +	ctrl = readl(lpwm->regs + PWM); +	writel(ctrl | PWM_ENABLE, lpwm->regs + PWM); + +	return 0; +} + +static void pwm_lpss_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct pwm_lpss_chip *lpwm = to_lpwm(chip); +	u32 ctrl; + +	ctrl = readl(lpwm->regs + PWM); +	writel(ctrl & ~PWM_ENABLE, lpwm->regs + PWM); + +	clk_disable_unprepare(lpwm->clk); +} + +static const struct pwm_ops pwm_lpss_ops = { +	.config = pwm_lpss_config, +	.enable = pwm_lpss_enable, +	.disable = pwm_lpss_disable, +	.owner = THIS_MODULE, +}; + +static struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, +					    struct resource *r, +					    const struct pwm_lpss_boardinfo *info) +{ +	struct pwm_lpss_chip *lpwm; +	int ret; + +	lpwm = devm_kzalloc(dev, sizeof(*lpwm), GFP_KERNEL); +	if (!lpwm) +		return ERR_PTR(-ENOMEM); + +	lpwm->regs = devm_ioremap_resource(dev, r); +	if (IS_ERR(lpwm->regs)) +		return ERR_CAST(lpwm->regs); + +	if (info) { +		lpwm->clk_rate = info->clk_rate; +	} else { +		lpwm->clk = devm_clk_get(dev, NULL); +		if (IS_ERR(lpwm->clk)) { +			dev_err(dev, "failed to get PWM clock\n"); +			return ERR_CAST(lpwm->clk); +		} +		lpwm->clk_rate = clk_get_rate(lpwm->clk); +	} + +	lpwm->chip.dev = dev; +	lpwm->chip.ops = &pwm_lpss_ops; +	lpwm->chip.base = -1; +	lpwm->chip.npwm = 1; + +	ret = pwmchip_add(&lpwm->chip); +	if (ret) { +		dev_err(dev, "failed to add PWM chip: %d\n", ret); +		return ERR_PTR(ret); +	} + +	return lpwm; +} + +static int pwm_lpss_remove(struct pwm_lpss_chip *lpwm) +{ +	u32 ctrl; + +	ctrl = readl(lpwm->regs + PWM); +	writel(ctrl & ~PWM_ENABLE, lpwm->regs + PWM); + +	return pwmchip_remove(&lpwm->chip); +} + +static int pwm_lpss_probe_pci(struct pci_dev *pdev, +			      const struct pci_device_id *id) +{ +	const struct pwm_lpss_boardinfo *info; +	struct pwm_lpss_chip *lpwm; +	int err; + +	err = pci_enable_device(pdev); +	if (err < 0) +		return err; + +	info = (struct pwm_lpss_boardinfo *)id->driver_data; +	lpwm = pwm_lpss_probe(&pdev->dev, &pdev->resource[0], info); +	if (IS_ERR(lpwm)) +		return PTR_ERR(lpwm); + +	pci_set_drvdata(pdev, lpwm); +	return 0; +} + +static void pwm_lpss_remove_pci(struct pci_dev *pdev) +{ +	struct pwm_lpss_chip *lpwm = pci_get_drvdata(pdev); + +	pwm_lpss_remove(lpwm); +	pci_disable_device(pdev); +} + +static struct pci_device_id pwm_lpss_pci_ids[] = { +	{ PCI_VDEVICE(INTEL, 0x0f08), (unsigned long)&byt_info}, +	{ PCI_VDEVICE(INTEL, 0x0f09), (unsigned long)&byt_info}, +	{ }, +}; +MODULE_DEVICE_TABLE(pci, pwm_lpss_pci_ids); + +static struct pci_driver pwm_lpss_driver_pci = { +	.name = "pwm-lpss", +	.id_table = pwm_lpss_pci_ids, +	.probe = pwm_lpss_probe_pci, +	.remove = pwm_lpss_remove_pci, +}; + +static int pwm_lpss_probe_platform(struct platform_device *pdev) +{ +	struct pwm_lpss_chip *lpwm; +	struct resource *r; + +	r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + +	lpwm = pwm_lpss_probe(&pdev->dev, r, NULL); +	if (IS_ERR(lpwm)) +		return PTR_ERR(lpwm); + +	platform_set_drvdata(pdev, lpwm); +	return 0; +} + +static int pwm_lpss_remove_platform(struct platform_device *pdev) +{ +	struct pwm_lpss_chip *lpwm = platform_get_drvdata(pdev); + +	return pwm_lpss_remove(lpwm); +} + +static const struct acpi_device_id pwm_lpss_acpi_match[] = { +	{ "80860F09", 0 }, +	{ }, +}; +MODULE_DEVICE_TABLE(acpi, pwm_lpss_acpi_match); + +static struct platform_driver pwm_lpss_driver_platform = { +	.driver = { +		.name = "pwm-lpss", +		.acpi_match_table = pwm_lpss_acpi_match, +	}, +	.probe = pwm_lpss_probe_platform, +	.remove = pwm_lpss_remove_platform, +}; + +static int __init pwm_init(void) +{ +	pci_drv = pci_register_driver(&pwm_lpss_driver_pci); +	plat_drv = platform_driver_register(&pwm_lpss_driver_platform); +	if (pci_drv && plat_drv) +		return pci_drv; + +	return 0; +} +module_init(pwm_init); + +static void __exit pwm_exit(void) +{ +	if (!pci_drv) +		pci_unregister_driver(&pwm_lpss_driver_pci); +	if (!plat_drv) +		platform_driver_unregister(&pwm_lpss_driver_platform); +} +module_exit(pwm_exit); + +MODULE_DESCRIPTION("PWM driver for Intel LPSS"); +MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:pwm-lpss"); diff --git a/drivers/pwm/pwm-mxs.c b/drivers/pwm/pwm-mxs.c new file mode 100644 index 00000000000..4f1bb4e0a42 --- /dev/null +++ b/drivers/pwm/pwm-mxs.c @@ -0,0 +1,203 @@ +/* + * 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 <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> +#include <linux/stmp_device.h> + +#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 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; +	struct resource *res; +	int ret; + +	mxs = devm_kzalloc(&pdev->dev, sizeof(*mxs), GFP_KERNEL); +	if (!mxs) +		return -ENOMEM; + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	mxs->base = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(mxs->base)) +		return PTR_ERR(mxs->base); + +	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; +	mxs->chip.base = -1; +	mxs->chip.can_sleep = true; +	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); +		return ret; +	} + +	ret = pwmchip_add(&mxs->chip); +	if (ret < 0) { +		dev_err(&pdev->dev, "failed to add pwm chip %d\n", ret); +		return ret; +	} + +	platform_set_drvdata(pdev, mxs); + +	ret = stmp_reset_block(mxs->base); +	if (ret) +		goto pwm_remove; + +	return 0; + +pwm_remove: +	pwmchip_remove(&mxs->chip); +	return ret; +} + +static int mxs_pwm_remove(struct platform_device *pdev) +{ +	struct mxs_pwm_chip *mxs = platform_get_drvdata(pdev); + +	return pwmchip_remove(&mxs->chip); +} + +static const struct of_device_id mxs_pwm_dt_ids[] = { +	{ .compatible = "fsl,imx23-pwm", }, +	{ /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mxs_pwm_dt_ids); + +static struct platform_driver mxs_pwm_driver = { +	.driver = { +		.name = "mxs-pwm", +		.owner = THIS_MODULE, +		.of_match_table = mxs_pwm_dt_ids, +	}, +	.probe = mxs_pwm_probe, +	.remove = mxs_pwm_remove, +}; +module_platform_driver(mxs_pwm_driver); + +MODULE_ALIAS("platform:mxs-pwm"); +MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>"); +MODULE_DESCRIPTION("Freescale MXS PWM Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c new file mode 100644 index 00000000000..3fb775ded0d --- /dev/null +++ b/drivers/pwm/pwm-pca9685.c @@ -0,0 +1,300 @@ +/* + * Driver for PCA9685 16-channel 12-bit PWM LED controller + * + * Copyright (C) 2013 Steffen Trumtrar <s.trumtrar@pengutronix.de> + * + * based on the pwm-twl-led.c driver + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#define PCA9685_MODE1		0x00 +#define PCA9685_MODE2		0x01 +#define PCA9685_SUBADDR1	0x02 +#define PCA9685_SUBADDR2	0x03 +#define PCA9685_SUBADDR3	0x04 +#define PCA9685_ALLCALLADDR	0x05 +#define PCA9685_LEDX_ON_L	0x06 +#define PCA9685_LEDX_ON_H	0x07 +#define PCA9685_LEDX_OFF_L	0x08 +#define PCA9685_LEDX_OFF_H	0x09 + +#define PCA9685_ALL_LED_ON_L	0xFA +#define PCA9685_ALL_LED_ON_H	0xFB +#define PCA9685_ALL_LED_OFF_L	0xFC +#define PCA9685_ALL_LED_OFF_H	0xFD +#define PCA9685_PRESCALE	0xFE + +#define PCA9685_NUMREGS		0xFF +#define PCA9685_MAXCHAN		0x10 + +#define LED_FULL		(1 << 4) +#define MODE1_SLEEP		(1 << 4) +#define MODE2_INVRT		(1 << 4) +#define MODE2_OUTDRV		(1 << 2) + +#define LED_N_ON_H(N)	(PCA9685_LEDX_ON_H + (4 * (N))) +#define LED_N_ON_L(N)	(PCA9685_LEDX_ON_L + (4 * (N))) +#define LED_N_OFF_H(N)	(PCA9685_LEDX_OFF_H + (4 * (N))) +#define LED_N_OFF_L(N)	(PCA9685_LEDX_OFF_L + (4 * (N))) + +struct pca9685 { +	struct pwm_chip chip; +	struct regmap *regmap; +	int active_cnt; +}; + +static inline struct pca9685 *to_pca(struct pwm_chip *chip) +{ +	return container_of(chip, struct pca9685, chip); +} + +static int pca9685_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, +			      int duty_ns, int period_ns) +{ +	struct pca9685 *pca = to_pca(chip); +	unsigned long long duty; +	unsigned int reg; + +	if (duty_ns < 1) { +		if (pwm->hwpwm >= PCA9685_MAXCHAN) +			reg = PCA9685_ALL_LED_OFF_H; +		else +			reg = LED_N_OFF_H(pwm->hwpwm); + +		regmap_write(pca->regmap, reg, LED_FULL); + +		return 0; +	} + +	if (duty_ns == period_ns) { +		if (pwm->hwpwm >= PCA9685_MAXCHAN) +			reg = PCA9685_ALL_LED_ON_H; +		else +			reg = LED_N_ON_H(pwm->hwpwm); + +		regmap_write(pca->regmap, reg, LED_FULL); + +		return 0; +	} + +	duty = 4096 * (unsigned long long)duty_ns; +	duty = DIV_ROUND_UP_ULL(duty, period_ns); + +	if (pwm->hwpwm >= PCA9685_MAXCHAN) +		reg = PCA9685_ALL_LED_OFF_L; +	else +		reg = LED_N_OFF_L(pwm->hwpwm); + +	regmap_write(pca->regmap, reg, (int)duty & 0xff); + +	if (pwm->hwpwm >= PCA9685_MAXCHAN) +		reg = PCA9685_ALL_LED_OFF_H; +	else +		reg = LED_N_OFF_H(pwm->hwpwm); + +	regmap_write(pca->regmap, reg, ((int)duty >> 8) & 0xf); + +	return 0; +} + +static int pca9685_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct pca9685 *pca = to_pca(chip); +	unsigned int reg; + +	/* +	 * The PWM subsystem does not support a pre-delay. +	 * So, set the ON-timeout to 0 +	 */ +	if (pwm->hwpwm >= PCA9685_MAXCHAN) +		reg = PCA9685_ALL_LED_ON_L; +	else +		reg = LED_N_ON_L(pwm->hwpwm); + +	regmap_write(pca->regmap, reg, 0); + +	if (pwm->hwpwm >= PCA9685_MAXCHAN) +		reg = PCA9685_ALL_LED_ON_H; +	else +		reg = LED_N_ON_H(pwm->hwpwm); + +	regmap_write(pca->regmap, reg, 0); + +	/* +	 * Clear the full-off bit. +	 * It has precedence over the others and must be off. +	 */ +	if (pwm->hwpwm >= PCA9685_MAXCHAN) +		reg = PCA9685_ALL_LED_OFF_H; +	else +		reg = LED_N_OFF_H(pwm->hwpwm); + +	regmap_update_bits(pca->regmap, reg, LED_FULL, 0x0); + +	return 0; +} + +static void pca9685_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct pca9685 *pca = to_pca(chip); +	unsigned int reg; + +	if (pwm->hwpwm >= PCA9685_MAXCHAN) +		reg = PCA9685_ALL_LED_OFF_H; +	else +		reg = LED_N_OFF_H(pwm->hwpwm); + +	regmap_write(pca->regmap, reg, LED_FULL); + +	/* Clear the LED_OFF counter. */ +	if (pwm->hwpwm >= PCA9685_MAXCHAN) +		reg = PCA9685_ALL_LED_OFF_L; +	else +		reg = LED_N_OFF_L(pwm->hwpwm); + +	regmap_write(pca->regmap, reg, 0x0); +} + +static int pca9685_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct pca9685 *pca = to_pca(chip); + +	if (pca->active_cnt++ == 0) +		return regmap_update_bits(pca->regmap, PCA9685_MODE1, +					  MODE1_SLEEP, 0x0); + +	return 0; +} + +static void pca9685_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct pca9685 *pca = to_pca(chip); + +	if (--pca->active_cnt == 0) +		regmap_update_bits(pca->regmap, PCA9685_MODE1, MODE1_SLEEP, +				   MODE1_SLEEP); +} + +static const struct pwm_ops pca9685_pwm_ops = { +	.enable = pca9685_pwm_enable, +	.disable = pca9685_pwm_disable, +	.config = pca9685_pwm_config, +	.request = pca9685_pwm_request, +	.free = pca9685_pwm_free, +	.owner = THIS_MODULE, +}; + +static struct regmap_config pca9685_regmap_i2c_config = { +	.reg_bits = 8, +	.val_bits = 8, +	.max_register = PCA9685_NUMREGS, +	.cache_type = REGCACHE_NONE, +}; + +static int pca9685_pwm_probe(struct i2c_client *client, +				const struct i2c_device_id *id) +{ +	struct device_node *np = client->dev.of_node; +	struct pca9685 *pca; +	int ret; +	int mode2; + +	pca = devm_kzalloc(&client->dev, sizeof(*pca), GFP_KERNEL); +	if (!pca) +		return -ENOMEM; + +	pca->regmap = devm_regmap_init_i2c(client, &pca9685_regmap_i2c_config); +	if (IS_ERR(pca->regmap)) { +		ret = PTR_ERR(pca->regmap); +		dev_err(&client->dev, "Failed to initialize register map: %d\n", +			ret); +		return ret; +	} + +	i2c_set_clientdata(client, pca); + +	regmap_read(pca->regmap, PCA9685_MODE2, &mode2); + +	if (of_property_read_bool(np, "invert")) +		mode2 |= MODE2_INVRT; +	else +		mode2 &= ~MODE2_INVRT; + +	if (of_property_read_bool(np, "open-drain")) +		mode2 &= ~MODE2_OUTDRV; +	else +		mode2 |= MODE2_OUTDRV; + +	regmap_write(pca->regmap, PCA9685_MODE2, mode2); + +	/* clear all "full off" bits */ +	regmap_write(pca->regmap, PCA9685_ALL_LED_OFF_L, 0); +	regmap_write(pca->regmap, PCA9685_ALL_LED_OFF_H, 0); + +	pca->chip.ops = &pca9685_pwm_ops; +	/* add an extra channel for ALL_LED */ +	pca->chip.npwm = PCA9685_MAXCHAN + 1; + +	pca->chip.dev = &client->dev; +	pca->chip.base = -1; +	pca->chip.can_sleep = true; + +	return pwmchip_add(&pca->chip); +} + +static int pca9685_pwm_remove(struct i2c_client *client) +{ +	struct pca9685 *pca = i2c_get_clientdata(client); + +	regmap_update_bits(pca->regmap, PCA9685_MODE1, MODE1_SLEEP, +			   MODE1_SLEEP); + +	return pwmchip_remove(&pca->chip); +} + +static const struct i2c_device_id pca9685_id[] = { +	{ "pca9685", 0 }, +	{ /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(i2c, pca9685_id); + +static const struct of_device_id pca9685_dt_ids[] = { +	{ .compatible = "nxp,pca9685-pwm", }, +	{ /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, pca9685_dt_ids); + +static struct i2c_driver pca9685_i2c_driver = { +	.driver = { +		.name = "pca9685-pwm", +		.owner = THIS_MODULE, +		.of_match_table = pca9685_dt_ids, +	}, +	.probe = pca9685_pwm_probe, +	.remove = pca9685_pwm_remove, +	.id_table = pca9685_id, +}; + +module_i2c_driver(pca9685_i2c_driver); + +MODULE_AUTHOR("Steffen Trumtrar <s.trumtrar@pengutronix.de>"); +MODULE_DESCRIPTION("PWM driver for PCA9685"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-puv3.c b/drivers/pwm/pwm-puv3.c new file mode 100644 index 00000000000..a9a28083f24 --- /dev/null +++ b/drivers/pwm/pwm-puv3.c @@ -0,0 +1,156 @@ +/* + * linux/arch/unicore32/kernel/pwm.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + *	Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn> + *	Copyright (C) 2001-2010 Guan Xuetao + * + * 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. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/pwm.h> + +#include <asm/div64.h> +#include <mach/hardware.h> + +struct puv3_pwm_chip { +	struct pwm_chip chip; +	void __iomem *base; +	struct clk *clk; +}; + +static inline struct puv3_pwm_chip *to_puv3(struct pwm_chip *chip) +{ +	return container_of(chip, struct puv3_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 puv3_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, +			   int duty_ns, int period_ns) +{ +	unsigned long period_cycles, prescale, pv, dc; +	struct puv3_pwm_chip *puv3 = to_puv3(chip); +	unsigned long long c; + +	c = clk_get_rate(puv3->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 = OST_PWMDCCR_FDCYCLE; +	else +		dc = (pv + 1) * duty_ns / period_ns; + +	/* +	 * NOTE: the clock to PWM has to be enabled first +	 * before writing to the registers +	 */ +	clk_prepare_enable(puv3->clk); + +	writel(prescale, puv3->base + OST_PWM_PWCR); +	writel(pv - dc, puv3->base + OST_PWM_DCCR); +	writel(pv, puv3->base + OST_PWM_PCR); + +	clk_disable_unprepare(puv3->clk); + +	return 0; +} + +static int puv3_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct puv3_pwm_chip *puv3 = to_puv3(chip); + +	return clk_prepare_enable(puv3->clk); +} + +static void puv3_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct puv3_pwm_chip *puv3 = to_puv3(chip); + +	clk_disable_unprepare(puv3->clk); +} + +static const struct pwm_ops puv3_pwm_ops = { +	.config = puv3_pwm_config, +	.enable = puv3_pwm_enable, +	.disable = puv3_pwm_disable, +	.owner = THIS_MODULE, +}; + +static int pwm_probe(struct platform_device *pdev) +{ +	struct puv3_pwm_chip *puv3; +	struct resource *r; +	int ret; + +	puv3 = devm_kzalloc(&pdev->dev, sizeof(*puv3), GFP_KERNEL); +	if (puv3 == NULL) { +		dev_err(&pdev->dev, "failed to allocate memory\n"); +		return -ENOMEM; +	} + +	puv3->clk = devm_clk_get(&pdev->dev, "OST_CLK"); +	if (IS_ERR(puv3->clk)) +		return PTR_ERR(puv3->clk); + +	r = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	puv3->base = devm_ioremap_resource(&pdev->dev, r); +	if (IS_ERR(puv3->base)) +		return PTR_ERR(puv3->base); + +	puv3->chip.dev = &pdev->dev; +	puv3->chip.ops = &puv3_pwm_ops; +	puv3->chip.base = -1; +	puv3->chip.npwm = 1; + +	ret = pwmchip_add(&puv3->chip); +	if (ret < 0) { +		dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); +		return ret; +	} + +	platform_set_drvdata(pdev, puv3); +	return 0; +} + +static int pwm_remove(struct platform_device *pdev) +{ +	struct puv3_pwm_chip *puv3 = platform_get_drvdata(pdev); + +	return pwmchip_remove(&puv3->chip); +} + +static struct platform_driver puv3_pwm_driver = { +	.driver = { +		.name = "PKUnity-v3-PWM", +		.owner = THIS_MODULE, +	}, +	.probe = pwm_probe, +	.remove = pwm_remove, +}; +module_platform_driver(puv3_pwm_driver); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-pxa.c b/drivers/pwm/pwm-pxa.c new file mode 100644 index 00000000000..0b312ec420b --- /dev/null +++ b/drivers/pwm/pwm-pxa.c @@ -0,0 +1,238 @@ +/* + * drivers/pwm/pwm-pxa.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 <eric.miao@marvell.com> + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/pwm.h> +#include <linux/of_device.h> + +#include <asm/div64.h> + +#define HAS_SECONDARY_PWM	0x10 + +static const struct platform_device_id pwm_id_table[] = { +	/*   PWM    has_secondary_pwm? */ +	{ "pxa25x-pwm", 0 }, +	{ "pxa27x-pwm", HAS_SECONDARY_PWM }, +	{ "pxa168-pwm", 0 }, +	{ "pxa910-pwm", 0 }, +	{ }, +}; +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; +	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; + +	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); + +	return clk_prepare_enable(pc->clk); +} + +static void pxa_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct pxa_pwm_chip *pc = to_pxa_pwm_chip(chip); + +	clk_disable_unprepare(pc->clk); +} + +static struct pwm_ops pxa_pwm_ops = { +	.config = pxa_pwm_config, +	.enable = pxa_pwm_enable, +	.disable = pxa_pwm_disable, +	.owner = THIS_MODULE, +}; + +#ifdef CONFIG_OF +/* + * Device tree users must create one device instance for each PWM channel. + * Hence we dispense with the HAS_SECONDARY_PWM and "tell" the original driver + * code that this is a single channel pxa25x-pwm.  Currently all devices are + * supported identically. + */ +static const struct of_device_id pwm_of_match[] = { +	{ .compatible = "marvell,pxa250-pwm", .data = &pwm_id_table[0]}, +	{ .compatible = "marvell,pxa270-pwm", .data = &pwm_id_table[0]}, +	{ .compatible = "marvell,pxa168-pwm", .data = &pwm_id_table[0]}, +	{ .compatible = "marvell,pxa910-pwm", .data = &pwm_id_table[0]}, +	{ } +}; +MODULE_DEVICE_TABLE(of, pwm_of_match); +#else +#define pwm_of_match NULL +#endif + +static const struct platform_device_id *pxa_pwm_get_id_dt(struct device *dev) +{ +	const struct of_device_id *id = of_match_device(pwm_of_match, dev); + +	return id ? id->data : NULL; +} + +static struct pwm_device * +pxa_pwm_of_xlate(struct pwm_chip *pc, const struct of_phandle_args *args) +{ +	struct pwm_device *pwm; + +	pwm = pwm_request_from_chip(pc, 0, NULL); +	if (IS_ERR(pwm)) +		return pwm; + +	pwm_set_period(pwm, args->args[0]); + +	return pwm; +} + +static int 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; + +	if (IS_ENABLED(CONFIG_OF) && id == NULL) +		id = pxa_pwm_get_id_dt(&pdev->dev); + +	if (id == NULL) +		return -EINVAL; + +	pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); +	if (pwm == NULL) +		return -ENOMEM; + +	pwm->clk = devm_clk_get(&pdev->dev, NULL); +	if (IS_ERR(pwm->clk)) +		return PTR_ERR(pwm->clk); + +	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; + +	if (IS_ENABLED(CONFIG_OF)) { +		pwm->chip.of_xlate = pxa_pwm_of_xlate; +		pwm->chip.of_pwm_n_cells = 1; +	} + +	r = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	pwm->mmio_base = devm_ioremap_resource(&pdev->dev, r); +	if (IS_ERR(pwm->mmio_base)) +		return PTR_ERR(pwm->mmio_base); + +	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; +} + +static int pwm_remove(struct platform_device *pdev) +{ +	struct pxa_pwm_chip *chip; + +	chip = platform_get_drvdata(pdev); +	if (chip == NULL) +		return -ENODEV; + +	return pwmchip_remove(&chip->chip); +} + +static struct platform_driver pwm_driver = { +	.driver		= { +		.name	= "pxa25x-pwm", +		.owner	= THIS_MODULE, +		.of_match_table = pwm_of_match, +	}, +	.probe		= pwm_probe, +	.remove		= pwm_remove, +	.id_table	= pwm_id_table, +}; + +module_platform_driver(pwm_driver); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-renesas-tpu.c b/drivers/pwm/pwm-renesas-tpu.c new file mode 100644 index 00000000000..3b71b42e89d --- /dev/null +++ b/drivers/pwm/pwm-renesas-tpu.c @@ -0,0 +1,481 @@ +/* + * R-Mobile TPU PWM driver + * + * Copyright (C) 2012 Renesas Solutions Corp. + * + * 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 + * + * 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 <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/pwm.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +#define TPU_CHANNEL_MAX		4 + +#define TPU_TSTR		0x00	/* Timer start register (shared) */ + +#define TPU_TCRn		0x00	/* Timer control register */ +#define TPU_TCR_CCLR_NONE	(0 << 5) +#define TPU_TCR_CCLR_TGRA	(1 << 5) +#define TPU_TCR_CCLR_TGRB	(2 << 5) +#define TPU_TCR_CCLR_TGRC	(5 << 5) +#define TPU_TCR_CCLR_TGRD	(6 << 5) +#define TPU_TCR_CKEG_RISING	(0 << 3) +#define TPU_TCR_CKEG_FALLING	(1 << 3) +#define TPU_TCR_CKEG_BOTH	(2 << 3) +#define TPU_TMDRn		0x04	/* Timer mode register */ +#define TPU_TMDR_BFWT		(1 << 6) +#define TPU_TMDR_BFB		(1 << 5) +#define TPU_TMDR_BFA		(1 << 4) +#define TPU_TMDR_MD_NORMAL	(0 << 0) +#define TPU_TMDR_MD_PWM		(2 << 0) +#define TPU_TIORn		0x08	/* Timer I/O control register */ +#define TPU_TIOR_IOA_0		(0 << 0) +#define TPU_TIOR_IOA_0_CLR	(1 << 0) +#define TPU_TIOR_IOA_0_SET	(2 << 0) +#define TPU_TIOR_IOA_0_TOGGLE	(3 << 0) +#define TPU_TIOR_IOA_1		(4 << 0) +#define TPU_TIOR_IOA_1_CLR	(5 << 0) +#define TPU_TIOR_IOA_1_SET	(6 << 0) +#define TPU_TIOR_IOA_1_TOGGLE	(7 << 0) +#define TPU_TIERn		0x0c	/* Timer interrupt enable register */ +#define TPU_TSRn		0x10	/* Timer status register */ +#define TPU_TCNTn		0x14	/* Timer counter */ +#define TPU_TGRAn		0x18	/* Timer general register A */ +#define TPU_TGRBn		0x1c	/* Timer general register B */ +#define TPU_TGRCn		0x20	/* Timer general register C */ +#define TPU_TGRDn		0x24	/* Timer general register D */ + +#define TPU_CHANNEL_OFFSET	0x10 +#define TPU_CHANNEL_SIZE	0x40 + +enum tpu_pin_state { +	TPU_PIN_INACTIVE,		/* Pin is driven inactive */ +	TPU_PIN_PWM,			/* Pin is driven by PWM */ +	TPU_PIN_ACTIVE,			/* Pin is driven active */ +}; + +struct tpu_device; + +struct tpu_pwm_device { +	bool timer_on;			/* Whether the timer is running */ + +	struct tpu_device *tpu; +	unsigned int channel;		/* Channel number in the TPU */ + +	enum pwm_polarity polarity; +	unsigned int prescaler; +	u16 period; +	u16 duty; +}; + +struct tpu_device { +	struct platform_device *pdev; +	struct pwm_chip chip; +	spinlock_t lock; + +	void __iomem *base; +	struct clk *clk; +}; + +#define to_tpu_device(c)	container_of(c, struct tpu_device, chip) + +static void tpu_pwm_write(struct tpu_pwm_device *pwm, int reg_nr, u16 value) +{ +	void __iomem *base = pwm->tpu->base + TPU_CHANNEL_OFFSET +			   + pwm->channel * TPU_CHANNEL_SIZE; + +	iowrite16(value, base + reg_nr); +} + +static void tpu_pwm_set_pin(struct tpu_pwm_device *pwm, +			    enum tpu_pin_state state) +{ +	static const char * const states[] = { "inactive", "PWM", "active" }; + +	dev_dbg(&pwm->tpu->pdev->dev, "%u: configuring pin as %s\n", +		pwm->channel, states[state]); + +	switch (state) { +	case TPU_PIN_INACTIVE: +		tpu_pwm_write(pwm, TPU_TIORn, +			      pwm->polarity == PWM_POLARITY_INVERSED ? +			      TPU_TIOR_IOA_1 : TPU_TIOR_IOA_0); +		break; +	case TPU_PIN_PWM: +		tpu_pwm_write(pwm, TPU_TIORn, +			      pwm->polarity == PWM_POLARITY_INVERSED ? +			      TPU_TIOR_IOA_0_SET : TPU_TIOR_IOA_1_CLR); +		break; +	case TPU_PIN_ACTIVE: +		tpu_pwm_write(pwm, TPU_TIORn, +			      pwm->polarity == PWM_POLARITY_INVERSED ? +			      TPU_TIOR_IOA_0 : TPU_TIOR_IOA_1); +		break; +	} +} + +static void tpu_pwm_start_stop(struct tpu_pwm_device *pwm, int start) +{ +	unsigned long flags; +	u16 value; + +	spin_lock_irqsave(&pwm->tpu->lock, flags); +	value = ioread16(pwm->tpu->base + TPU_TSTR); + +	if (start) +		value |= 1 << pwm->channel; +	else +		value &= ~(1 << pwm->channel); + +	iowrite16(value, pwm->tpu->base + TPU_TSTR); +	spin_unlock_irqrestore(&pwm->tpu->lock, flags); +} + +static int tpu_pwm_timer_start(struct tpu_pwm_device *pwm) +{ +	int ret; + +	if (!pwm->timer_on) { +		/* Wake up device and enable clock. */ +		pm_runtime_get_sync(&pwm->tpu->pdev->dev); +		ret = clk_prepare_enable(pwm->tpu->clk); +		if (ret) { +			dev_err(&pwm->tpu->pdev->dev, "cannot enable clock\n"); +			return ret; +		} +		pwm->timer_on = true; +	} + +	/* +	 * Make sure the channel is stopped, as we need to reconfigure it +	 * completely. First drive the pin to the inactive state to avoid +	 * glitches. +	 */ +	tpu_pwm_set_pin(pwm, TPU_PIN_INACTIVE); +	tpu_pwm_start_stop(pwm, false); + +	/* +	 * - Clear TCNT on TGRB match +	 * - Count on rising edge +	 * - Set prescaler +	 * - Output 0 until TGRA, output 1 until TGRB (active low polarity) +	 * - Output 1 until TGRA, output 0 until TGRB (active high polarity +	 * - PWM mode +	 */ +	tpu_pwm_write(pwm, TPU_TCRn, TPU_TCR_CCLR_TGRB | TPU_TCR_CKEG_RISING | +		      pwm->prescaler); +	tpu_pwm_write(pwm, TPU_TMDRn, TPU_TMDR_MD_PWM); +	tpu_pwm_set_pin(pwm, TPU_PIN_PWM); +	tpu_pwm_write(pwm, TPU_TGRAn, pwm->duty); +	tpu_pwm_write(pwm, TPU_TGRBn, pwm->period); + +	dev_dbg(&pwm->tpu->pdev->dev, "%u: TGRA 0x%04x TGRB 0x%04x\n", +		pwm->channel, pwm->duty, pwm->period); + +	/* Start the channel. */ +	tpu_pwm_start_stop(pwm, true); + +	return 0; +} + +static void tpu_pwm_timer_stop(struct tpu_pwm_device *pwm) +{ +	if (!pwm->timer_on) +		return; + +	/* Disable channel. */ +	tpu_pwm_start_stop(pwm, false); + +	/* Stop clock and mark device as idle. */ +	clk_disable_unprepare(pwm->tpu->clk); +	pm_runtime_put(&pwm->tpu->pdev->dev); + +	pwm->timer_on = false; +} + +/* ----------------------------------------------------------------------------- + * PWM API + */ + +static int tpu_pwm_request(struct pwm_chip *chip, struct pwm_device *_pwm) +{ +	struct tpu_device *tpu = to_tpu_device(chip); +	struct tpu_pwm_device *pwm; + +	if (_pwm->hwpwm >= TPU_CHANNEL_MAX) +		return -EINVAL; + +	pwm = kzalloc(sizeof(*pwm), GFP_KERNEL); +	if (pwm == NULL) +		return -ENOMEM; + +	pwm->tpu = tpu; +	pwm->channel = _pwm->hwpwm; +	pwm->polarity = PWM_POLARITY_NORMAL; +	pwm->prescaler = 0; +	pwm->period = 0; +	pwm->duty = 0; + +	pwm->timer_on = false; + +	pwm_set_chip_data(_pwm, pwm); + +	return 0; +} + +static void tpu_pwm_free(struct pwm_chip *chip, struct pwm_device *_pwm) +{ +	struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm); + +	tpu_pwm_timer_stop(pwm); +	kfree(pwm); +} + +static int tpu_pwm_config(struct pwm_chip *chip, struct pwm_device *_pwm, +			  int duty_ns, int period_ns) +{ +	static const unsigned int prescalers[] = { 1, 4, 16, 64 }; +	struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm); +	struct tpu_device *tpu = to_tpu_device(chip); +	unsigned int prescaler; +	bool duty_only = false; +	u32 clk_rate; +	u32 period; +	u32 duty; +	int ret; + +	/* +	 * Pick a prescaler to avoid overflowing the counter. +	 * TODO: Pick the highest acceptable prescaler. +	 */ +	clk_rate = clk_get_rate(tpu->clk); + +	for (prescaler = 0; prescaler < ARRAY_SIZE(prescalers); ++prescaler) { +		period = clk_rate / prescalers[prescaler] +		       / (NSEC_PER_SEC / period_ns); +		if (period <= 0xffff) +			break; +	} + +	if (prescaler == ARRAY_SIZE(prescalers) || period == 0) { +		dev_err(&tpu->pdev->dev, "clock rate mismatch\n"); +		return -ENOTSUPP; +	} + +	if (duty_ns) { +		duty = clk_rate / prescalers[prescaler] +		     / (NSEC_PER_SEC / duty_ns); +		if (duty > period) +			return -EINVAL; +	} else { +		duty = 0; +	} + +	dev_dbg(&tpu->pdev->dev, +		"rate %u, prescaler %u, period %u, duty %u\n", +		clk_rate, prescalers[prescaler], period, duty); + +	if (pwm->prescaler == prescaler && pwm->period == period) +		duty_only = true; + +	pwm->prescaler = prescaler; +	pwm->period = period; +	pwm->duty = duty; + +	/* If the channel is disabled we're done. */ +	if (!test_bit(PWMF_ENABLED, &_pwm->flags)) +		return 0; + +	if (duty_only && pwm->timer_on) { +		/* +		 * If only the duty cycle changed and the timer is already +		 * running, there's no need to reconfigure it completely, Just +		 * modify the duty cycle. +		 */ +		tpu_pwm_write(pwm, TPU_TGRAn, pwm->duty); +		dev_dbg(&tpu->pdev->dev, "%u: TGRA 0x%04x\n", pwm->channel, +			pwm->duty); +	} else { +		/* Otherwise perform a full reconfiguration. */ +		ret = tpu_pwm_timer_start(pwm); +		if (ret < 0) +			return ret; +	} + +	if (duty == 0 || duty == period) { +		/* +		 * To avoid running the timer when not strictly required, handle +		 * 0% and 100% duty cycles as fixed levels and stop the timer. +		 */ +		tpu_pwm_set_pin(pwm, duty ? TPU_PIN_ACTIVE : TPU_PIN_INACTIVE); +		tpu_pwm_timer_stop(pwm); +	} + +	return 0; +} + +static int tpu_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *_pwm, +				enum pwm_polarity polarity) +{ +	struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm); + +	pwm->polarity = polarity; + +	return 0; +} + +static int tpu_pwm_enable(struct pwm_chip *chip, struct pwm_device *_pwm) +{ +	struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm); +	int ret; + +	ret = tpu_pwm_timer_start(pwm); +	if (ret < 0) +		return ret; + +	/* +	 * To avoid running the timer when not strictly required, handle 0% and +	 * 100% duty cycles as fixed levels and stop the timer. +	 */ +	if (pwm->duty == 0 || pwm->duty == pwm->period) { +		tpu_pwm_set_pin(pwm, pwm->duty ? +				TPU_PIN_ACTIVE : TPU_PIN_INACTIVE); +		tpu_pwm_timer_stop(pwm); +	} + +	return 0; +} + +static void tpu_pwm_disable(struct pwm_chip *chip, struct pwm_device *_pwm) +{ +	struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm); + +	/* The timer must be running to modify the pin output configuration. */ +	tpu_pwm_timer_start(pwm); +	tpu_pwm_set_pin(pwm, TPU_PIN_INACTIVE); +	tpu_pwm_timer_stop(pwm); +} + +static const struct pwm_ops tpu_pwm_ops = { +	.request = tpu_pwm_request, +	.free = tpu_pwm_free, +	.config = tpu_pwm_config, +	.set_polarity = tpu_pwm_set_polarity, +	.enable = tpu_pwm_enable, +	.disable = tpu_pwm_disable, +	.owner = THIS_MODULE, +}; + +/* ----------------------------------------------------------------------------- + * Probe and remove + */ + +static int tpu_probe(struct platform_device *pdev) +{ +	struct tpu_device *tpu; +	struct resource *res; +	int ret; + +	tpu = devm_kzalloc(&pdev->dev, sizeof(*tpu), GFP_KERNEL); +	if (tpu == NULL) +		return -ENOMEM; + +	spin_lock_init(&tpu->lock); +	tpu->pdev = pdev; + +	/* Map memory, get clock and pin control. */ +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	tpu->base = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(tpu->base)) +		return PTR_ERR(tpu->base); + +	tpu->clk = devm_clk_get(&pdev->dev, NULL); +	if (IS_ERR(tpu->clk)) { +		dev_err(&pdev->dev, "cannot get clock\n"); +		return PTR_ERR(tpu->clk); +	} + +	/* Initialize and register the device. */ +	platform_set_drvdata(pdev, tpu); + +	tpu->chip.dev = &pdev->dev; +	tpu->chip.ops = &tpu_pwm_ops; +	tpu->chip.of_xlate = of_pwm_xlate_with_flags; +	tpu->chip.of_pwm_n_cells = 3; +	tpu->chip.base = -1; +	tpu->chip.npwm = TPU_CHANNEL_MAX; + +	ret = pwmchip_add(&tpu->chip); +	if (ret < 0) { +		dev_err(&pdev->dev, "failed to register PWM chip\n"); +		return ret; +	} + +	dev_info(&pdev->dev, "TPU PWM %d registered\n", tpu->pdev->id); + +	pm_runtime_enable(&pdev->dev); + +	return 0; +} + +static int tpu_remove(struct platform_device *pdev) +{ +	struct tpu_device *tpu = platform_get_drvdata(pdev); +	int ret; + +	ret = pwmchip_remove(&tpu->chip); +	if (ret) +		return ret; + +	pm_runtime_disable(&pdev->dev); + +	return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id tpu_of_table[] = { +	{ .compatible = "renesas,tpu-r8a73a4", }, +	{ .compatible = "renesas,tpu-r8a7740", }, +	{ .compatible = "renesas,tpu-r8a7790", }, +	{ .compatible = "renesas,tpu-sh7372", }, +	{ .compatible = "renesas,tpu", }, +	{ }, +}; + +MODULE_DEVICE_TABLE(of, tpu_of_table); +#endif + +static struct platform_driver tpu_driver = { +	.probe		= tpu_probe, +	.remove		= tpu_remove, +	.driver		= { +		.name	= "renesas-tpu-pwm", +		.owner	= THIS_MODULE, +		.of_match_table = of_match_ptr(tpu_of_table), +	} +}; + +module_platform_driver(tpu_driver); + +MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); +MODULE_DESCRIPTION("Renesas TPU PWM Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:renesas-tpu-pwm"); diff --git a/drivers/pwm/pwm-samsung.c b/drivers/pwm/pwm-samsung.c new file mode 100644 index 00000000000..ba6b650cf8d --- /dev/null +++ b/drivers/pwm/pwm-samsung.c @@ -0,0 +1,615 @@ +/* + * Copyright (c) 2007 Ben Dooks + * Copyright (c) 2008 Simtec Electronics + *     Ben Dooks <ben@simtec.co.uk>, <ben-linux@fluff.org> + * Copyright (c) 2013 Tomasz Figa <tomasz.figa@gmail.com> + * + * PWM driver for Samsung SoCs + * + * 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 <linux/bitops.h> +#include <linux/clk.h> +#include <linux/export.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/time.h> + +/* For struct samsung_timer_variant and samsung_pwm_lock. */ +#include <clocksource/samsung_pwm.h> + +#define REG_TCFG0			0x00 +#define REG_TCFG1			0x04 +#define REG_TCON			0x08 + +#define REG_TCNTB(chan)			(0x0c + ((chan) * 0xc)) +#define REG_TCMPB(chan)			(0x10 + ((chan) * 0xc)) + +#define TCFG0_PRESCALER_MASK		0xff +#define TCFG0_PRESCALER1_SHIFT		8 + +#define TCFG1_MUX_MASK			0xf +#define TCFG1_SHIFT(chan)		(4 * (chan)) + +/* + * Each channel occupies 4 bits in TCON register, but there is a gap of 4 + * bits (one channel) after channel 0, so channels have different numbering + * when accessing TCON register. See to_tcon_channel() function. + * + * In addition, the location of autoreload bit for channel 4 (TCON channel 5) + * in its set of bits is 2 as opposed to 3 for other channels. + */ +#define TCON_START(chan)		BIT(4 * (chan) + 0) +#define TCON_MANUALUPDATE(chan)		BIT(4 * (chan) + 1) +#define TCON_INVERT(chan)		BIT(4 * (chan) + 2) +#define _TCON_AUTORELOAD(chan)		BIT(4 * (chan) + 3) +#define _TCON_AUTORELOAD4(chan)		BIT(4 * (chan) + 2) +#define TCON_AUTORELOAD(chan)		\ +	((chan < 5) ? _TCON_AUTORELOAD(chan) : _TCON_AUTORELOAD4(chan)) + +/** + * struct samsung_pwm_channel - private data of PWM channel + * @period_ns:	current period in nanoseconds programmed to the hardware + * @duty_ns:	current duty time in nanoseconds programmed to the hardware + * @tin_ns:	time of one timer tick in nanoseconds with current timer rate + */ +struct samsung_pwm_channel { +	u32 period_ns; +	u32 duty_ns; +	u32 tin_ns; +}; + +/** + * struct samsung_pwm_chip - private data of PWM chip + * @chip:		generic PWM chip + * @variant:		local copy of hardware variant data + * @inverter_mask:	inverter status for all channels - one bit per channel + * @base:		base address of mapped PWM registers + * @base_clk:		base clock used to drive the timers + * @tclk0:		external clock 0 (can be ERR_PTR if not present) + * @tclk1:		external clock 1 (can be ERR_PTR if not present) + */ +struct samsung_pwm_chip { +	struct pwm_chip chip; +	struct samsung_pwm_variant variant; +	u8 inverter_mask; + +	void __iomem *base; +	struct clk *base_clk; +	struct clk *tclk0; +	struct clk *tclk1; +}; + +#ifndef CONFIG_CLKSRC_SAMSUNG_PWM +/* + * PWM block is shared between pwm-samsung and samsung_pwm_timer drivers + * and some registers need access synchronization. If both drivers are + * compiled in, the spinlock is defined in the clocksource driver, + * otherwise following definition is used. + * + * Currently we do not need any more complex synchronization method + * because all the supported SoCs contain only one instance of the PWM + * IP. Should this change, both drivers will need to be modified to + * properly synchronize accesses to particular instances. + */ +static DEFINE_SPINLOCK(samsung_pwm_lock); +#endif + +static inline +struct samsung_pwm_chip *to_samsung_pwm_chip(struct pwm_chip *chip) +{ +	return container_of(chip, struct samsung_pwm_chip, chip); +} + +static inline unsigned int to_tcon_channel(unsigned int channel) +{ +	/* TCON register has a gap of 4 bits (1 channel) after channel 0 */ +	return (channel == 0) ? 0 : (channel + 1); +} + +static void pwm_samsung_set_divisor(struct samsung_pwm_chip *pwm, +				    unsigned int channel, u8 divisor) +{ +	u8 shift = TCFG1_SHIFT(channel); +	unsigned long flags; +	u32 reg; +	u8 bits; + +	bits = (fls(divisor) - 1) - pwm->variant.div_base; + +	spin_lock_irqsave(&samsung_pwm_lock, flags); + +	reg = readl(pwm->base + REG_TCFG1); +	reg &= ~(TCFG1_MUX_MASK << shift); +	reg |= bits << shift; +	writel(reg, pwm->base + REG_TCFG1); + +	spin_unlock_irqrestore(&samsung_pwm_lock, flags); +} + +static int pwm_samsung_is_tdiv(struct samsung_pwm_chip *chip, unsigned int chan) +{ +	struct samsung_pwm_variant *variant = &chip->variant; +	u32 reg; + +	reg = readl(chip->base + REG_TCFG1); +	reg >>= TCFG1_SHIFT(chan); +	reg &= TCFG1_MUX_MASK; + +	return (BIT(reg) & variant->tclk_mask) == 0; +} + +static unsigned long pwm_samsung_get_tin_rate(struct samsung_pwm_chip *chip, +					      unsigned int chan) +{ +	unsigned long rate; +	u32 reg; + +	rate = clk_get_rate(chip->base_clk); + +	reg = readl(chip->base + REG_TCFG0); +	if (chan >= 2) +		reg >>= TCFG0_PRESCALER1_SHIFT; +	reg &= TCFG0_PRESCALER_MASK; + +	return rate / (reg + 1); +} + +static unsigned long pwm_samsung_calc_tin(struct samsung_pwm_chip *chip, +					  unsigned int chan, unsigned long freq) +{ +	struct samsung_pwm_variant *variant = &chip->variant; +	unsigned long rate; +	struct clk *clk; +	u8 div; + +	if (!pwm_samsung_is_tdiv(chip, chan)) { +		clk = (chan < 2) ? chip->tclk0 : chip->tclk1; +		if (!IS_ERR(clk)) { +			rate = clk_get_rate(clk); +			if (rate) +				return rate; +		} + +		dev_warn(chip->chip.dev, +			"tclk of PWM %d is inoperational, using tdiv\n", chan); +	} + +	rate = pwm_samsung_get_tin_rate(chip, chan); +	dev_dbg(chip->chip.dev, "tin parent at %lu\n", rate); + +	/* +	 * Compare minimum PWM frequency that can be achieved with possible +	 * divider settings and choose the lowest divisor that can generate +	 * frequencies lower than requested. +	 */ +	for (div = variant->div_base; div < 4; ++div) +		if ((rate >> (variant->bits + div)) < freq) +			break; + +	pwm_samsung_set_divisor(chip, chan, BIT(div)); + +	return rate >> div; +} + +static int pwm_samsung_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip); +	struct samsung_pwm_channel *our_chan; + +	if (!(our_chip->variant.output_mask & BIT(pwm->hwpwm))) { +		dev_warn(chip->dev, +			"tried to request PWM channel %d without output\n", +			pwm->hwpwm); +		return -EINVAL; +	} + +	our_chan = devm_kzalloc(chip->dev, sizeof(*our_chan), GFP_KERNEL); +	if (!our_chan) +		return -ENOMEM; + +	pwm_set_chip_data(pwm, our_chan); + +	return 0; +} + +static void pwm_samsung_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	devm_kfree(chip->dev, pwm_get_chip_data(pwm)); +	pwm_set_chip_data(pwm, NULL); +} + +static int pwm_samsung_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip); +	unsigned int tcon_chan = to_tcon_channel(pwm->hwpwm); +	unsigned long flags; +	u32 tcon; + +	spin_lock_irqsave(&samsung_pwm_lock, flags); + +	tcon = readl(our_chip->base + REG_TCON); + +	tcon &= ~TCON_START(tcon_chan); +	tcon |= TCON_MANUALUPDATE(tcon_chan); +	writel(tcon, our_chip->base + REG_TCON); + +	tcon &= ~TCON_MANUALUPDATE(tcon_chan); +	tcon |= TCON_START(tcon_chan) | TCON_AUTORELOAD(tcon_chan); +	writel(tcon, our_chip->base + REG_TCON); + +	spin_unlock_irqrestore(&samsung_pwm_lock, flags); + +	return 0; +} + +static void pwm_samsung_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip); +	unsigned int tcon_chan = to_tcon_channel(pwm->hwpwm); +	unsigned long flags; +	u32 tcon; + +	spin_lock_irqsave(&samsung_pwm_lock, flags); + +	tcon = readl(our_chip->base + REG_TCON); +	tcon &= ~TCON_AUTORELOAD(tcon_chan); +	writel(tcon, our_chip->base + REG_TCON); + +	spin_unlock_irqrestore(&samsung_pwm_lock, flags); +} + +static int pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm, +			      int duty_ns, int period_ns) +{ +	struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip); +	struct samsung_pwm_channel *chan = pwm_get_chip_data(pwm); +	u32 tin_ns = chan->tin_ns, tcnt, tcmp; + +	/* +	 * We currently avoid using 64bit arithmetic by using the +	 * fact that anything faster than 1Hz is easily representable +	 * by 32bits. +	 */ +	if (period_ns > NSEC_PER_SEC) +		return -ERANGE; + +	if (period_ns == chan->period_ns && duty_ns == chan->duty_ns) +		return 0; + +	tcnt = readl(our_chip->base + REG_TCNTB(pwm->hwpwm)); + +	/* We need tick count for calculation, not last tick. */ +	++tcnt; + +	/* Check to see if we are changing the clock rate of the PWM. */ +	if (chan->period_ns != period_ns) { +		unsigned long tin_rate; +		u32 period; + +		period = NSEC_PER_SEC / period_ns; + +		dev_dbg(our_chip->chip.dev, "duty_ns=%d, period_ns=%d (%u)\n", +						duty_ns, period_ns, period); + +		tin_rate = pwm_samsung_calc_tin(our_chip, pwm->hwpwm, period); + +		dev_dbg(our_chip->chip.dev, "tin_rate=%lu\n", tin_rate); + +		tin_ns = NSEC_PER_SEC / tin_rate; +		tcnt = period_ns / tin_ns; +	} + +	/* Period is too short. */ +	if (tcnt <= 1) +		return -ERANGE; + +	/* Note that counters count down. */ +	tcmp = duty_ns / tin_ns; + +	/* 0% duty is not available */ +	if (!tcmp) +		++tcmp; + +	tcmp = tcnt - tcmp; + +	/* Decrement to get tick numbers, instead of tick counts. */ +	--tcnt; +	/* -1UL will give 100% duty. */ +	--tcmp; + +	dev_dbg(our_chip->chip.dev, +				"tin_ns=%u, tcmp=%u/%u\n", tin_ns, tcmp, tcnt); + +	/* Update PWM registers. */ +	writel(tcnt, our_chip->base + REG_TCNTB(pwm->hwpwm)); +	writel(tcmp, our_chip->base + REG_TCMPB(pwm->hwpwm)); + +	chan->period_ns = period_ns; +	chan->tin_ns = tin_ns; +	chan->duty_ns = duty_ns; + +	return 0; +} + +static void pwm_samsung_set_invert(struct samsung_pwm_chip *chip, +				   unsigned int channel, bool invert) +{ +	unsigned int tcon_chan = to_tcon_channel(channel); +	unsigned long flags; +	u32 tcon; + +	spin_lock_irqsave(&samsung_pwm_lock, flags); + +	tcon = readl(chip->base + REG_TCON); + +	if (invert) { +		chip->inverter_mask |= BIT(channel); +		tcon |= TCON_INVERT(tcon_chan); +	} else { +		chip->inverter_mask &= ~BIT(channel); +		tcon &= ~TCON_INVERT(tcon_chan); +	} + +	writel(tcon, chip->base + REG_TCON); + +	spin_unlock_irqrestore(&samsung_pwm_lock, flags); +} + +static int pwm_samsung_set_polarity(struct pwm_chip *chip, +				    struct pwm_device *pwm, +				    enum pwm_polarity polarity) +{ +	struct samsung_pwm_chip *our_chip = to_samsung_pwm_chip(chip); +	bool invert = (polarity == PWM_POLARITY_NORMAL); + +	/* Inverted means normal in the hardware. */ +	pwm_samsung_set_invert(our_chip, pwm->hwpwm, invert); + +	return 0; +} + +static const struct pwm_ops pwm_samsung_ops = { +	.request	= pwm_samsung_request, +	.free		= pwm_samsung_free, +	.enable		= pwm_samsung_enable, +	.disable	= pwm_samsung_disable, +	.config		= pwm_samsung_config, +	.set_polarity	= pwm_samsung_set_polarity, +	.owner		= THIS_MODULE, +}; + +#ifdef CONFIG_OF +static const struct samsung_pwm_variant s3c24xx_variant = { +	.bits		= 16, +	.div_base	= 1, +	.has_tint_cstat	= false, +	.tclk_mask	= BIT(4), +}; + +static const struct samsung_pwm_variant s3c64xx_variant = { +	.bits		= 32, +	.div_base	= 0, +	.has_tint_cstat	= true, +	.tclk_mask	= BIT(7) | BIT(6) | BIT(5), +}; + +static const struct samsung_pwm_variant s5p64x0_variant = { +	.bits		= 32, +	.div_base	= 0, +	.has_tint_cstat	= true, +	.tclk_mask	= 0, +}; + +static const struct samsung_pwm_variant s5pc100_variant = { +	.bits		= 32, +	.div_base	= 0, +	.has_tint_cstat	= true, +	.tclk_mask	= BIT(5), +}; + +static const struct of_device_id samsung_pwm_matches[] = { +	{ .compatible = "samsung,s3c2410-pwm", .data = &s3c24xx_variant }, +	{ .compatible = "samsung,s3c6400-pwm", .data = &s3c64xx_variant }, +	{ .compatible = "samsung,s5p6440-pwm", .data = &s5p64x0_variant }, +	{ .compatible = "samsung,s5pc100-pwm", .data = &s5pc100_variant }, +	{ .compatible = "samsung,exynos4210-pwm", .data = &s5p64x0_variant }, +	{}, +}; + +static int pwm_samsung_parse_dt(struct samsung_pwm_chip *chip) +{ +	struct device_node *np = chip->chip.dev->of_node; +	const struct of_device_id *match; +	struct property *prop; +	const __be32 *cur; +	u32 val; + +	match = of_match_node(samsung_pwm_matches, np); +	if (!match) +		return -ENODEV; + +	memcpy(&chip->variant, match->data, sizeof(chip->variant)); + +	of_property_for_each_u32(np, "samsung,pwm-outputs", prop, cur, val) { +		if (val >= SAMSUNG_PWM_NUM) { +			dev_err(chip->chip.dev, +				"%s: invalid channel index in samsung,pwm-outputs property\n", +								__func__); +			continue; +		} +		chip->variant.output_mask |= BIT(val); +	} + +	return 0; +} +#else +static int pwm_samsung_parse_dt(struct samsung_pwm_chip *chip) +{ +	return -ENODEV; +} +#endif + +static int pwm_samsung_probe(struct platform_device *pdev) +{ +	struct device *dev = &pdev->dev; +	struct samsung_pwm_chip *chip; +	struct resource *res; +	unsigned int chan; +	int ret; + +	chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); +	if (chip == NULL) +		return -ENOMEM; + +	chip->chip.dev = &pdev->dev; +	chip->chip.ops = &pwm_samsung_ops; +	chip->chip.base = -1; +	chip->chip.npwm = SAMSUNG_PWM_NUM; +	chip->inverter_mask = BIT(SAMSUNG_PWM_NUM) - 1; + +	if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) { +		ret = pwm_samsung_parse_dt(chip); +		if (ret) +			return ret; + +		chip->chip.of_xlate = of_pwm_xlate_with_flags; +		chip->chip.of_pwm_n_cells = 3; +	} else { +		if (!pdev->dev.platform_data) { +			dev_err(&pdev->dev, "no platform data specified\n"); +			return -EINVAL; +		} + +		memcpy(&chip->variant, pdev->dev.platform_data, +							sizeof(chip->variant)); +	} + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	chip->base = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(chip->base)) +		return PTR_ERR(chip->base); + +	chip->base_clk = devm_clk_get(&pdev->dev, "timers"); +	if (IS_ERR(chip->base_clk)) { +		dev_err(dev, "failed to get timer base clk\n"); +		return PTR_ERR(chip->base_clk); +	} + +	ret = clk_prepare_enable(chip->base_clk); +	if (ret < 0) { +		dev_err(dev, "failed to enable base clock\n"); +		return ret; +	} + +	for (chan = 0; chan < SAMSUNG_PWM_NUM; ++chan) +		if (chip->variant.output_mask & BIT(chan)) +			pwm_samsung_set_invert(chip, chan, true); + +	/* Following clocks are optional. */ +	chip->tclk0 = devm_clk_get(&pdev->dev, "pwm-tclk0"); +	chip->tclk1 = devm_clk_get(&pdev->dev, "pwm-tclk1"); + +	platform_set_drvdata(pdev, chip); + +	ret = pwmchip_add(&chip->chip); +	if (ret < 0) { +		dev_err(dev, "failed to register PWM chip\n"); +		clk_disable_unprepare(chip->base_clk); +		return ret; +	} + +	dev_dbg(dev, "base_clk at %lu, tclk0 at %lu, tclk1 at %lu\n", +		clk_get_rate(chip->base_clk), +		!IS_ERR(chip->tclk0) ? clk_get_rate(chip->tclk0) : 0, +		!IS_ERR(chip->tclk1) ? clk_get_rate(chip->tclk1) : 0); + +	return 0; +} + +static int pwm_samsung_remove(struct platform_device *pdev) +{ +	struct samsung_pwm_chip *chip = platform_get_drvdata(pdev); +	int ret; + +	ret = pwmchip_remove(&chip->chip); +	if (ret < 0) +		return ret; + +	clk_disable_unprepare(chip->base_clk); + +	return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int pwm_samsung_suspend(struct device *dev) +{ +	struct samsung_pwm_chip *chip = dev_get_drvdata(dev); +	unsigned int i; + +	/* +	 * No one preserves these values during suspend so reset them. +	 * Otherwise driver leaves PWM unconfigured if same values are +	 * passed to pwm_config() next time. +	 */ +	for (i = 0; i < SAMSUNG_PWM_NUM; ++i) { +		struct pwm_device *pwm = &chip->chip.pwms[i]; +		struct samsung_pwm_channel *chan = pwm_get_chip_data(pwm); + +		if (!chan) +			continue; + +		chan->period_ns = 0; +		chan->duty_ns = 0; +	} + +	return 0; +} + +static int pwm_samsung_resume(struct device *dev) +{ +	struct samsung_pwm_chip *chip = dev_get_drvdata(dev); +	unsigned int chan; + +	/* +	 * Inverter setting must be preserved across suspend/resume +	 * as nobody really seems to configure it more than once. +	 */ +	for (chan = 0; chan < SAMSUNG_PWM_NUM; ++chan) { +		if (chip->variant.output_mask & BIT(chan)) +			pwm_samsung_set_invert(chip, chan, +					chip->inverter_mask & BIT(chan)); +	} + +	return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(pwm_samsung_pm_ops, pwm_samsung_suspend, +			 pwm_samsung_resume); + +static struct platform_driver pwm_samsung_driver = { +	.driver		= { +		.name	= "samsung-pwm", +		.owner	= THIS_MODULE, +		.pm	= &pwm_samsung_pm_ops, +		.of_match_table = of_match_ptr(samsung_pwm_matches), +	}, +	.probe		= pwm_samsung_probe, +	.remove		= pwm_samsung_remove, +}; +module_platform_driver(pwm_samsung_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Tomasz Figa <tomasz.figa@gmail.com>"); +MODULE_ALIAS("platform:samsung-pwm"); diff --git a/drivers/pwm/pwm-spear.c b/drivers/pwm/pwm-spear.c new file mode 100644 index 00000000000..6fd93e6a412 --- /dev/null +++ b/drivers/pwm/pwm-spear.c @@ -0,0 +1,267 @@ +/* + * ST Microelectronics SPEAr Pulse Width Modulator driver + * + * Copyright (C) 2012 ST Microelectronics + * Shiraz Hashim <shiraz.linux.kernel@gmail.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> +#include <linux/types.h> + +#define NUM_PWM		4 + +/* PWM registers and bits definitions */ +#define PWMCR			0x00	/* Control Register */ +#define PWMCR_PWM_ENABLE	0x1 +#define PWMCR_PRESCALE_SHIFT	2 +#define PWMCR_MIN_PRESCALE	0x00 +#define PWMCR_MAX_PRESCALE	0x3FFF + +#define PWMDCR			0x04	/* Duty Cycle Register */ +#define PWMDCR_MIN_DUTY		0x0001 +#define PWMDCR_MAX_DUTY		0xFFFF + +#define PWMPCR			0x08	/* Period Register */ +#define PWMPCR_MIN_PERIOD	0x0001 +#define PWMPCR_MAX_PERIOD	0xFFFF + +/* Following only available on 13xx SoCs */ +#define PWMMCR			0x3C	/* Master Control Register */ +#define PWMMCR_PWM_ENABLE	0x1 + +/** + * struct spear_pwm_chip - struct representing pwm chip + * + * @mmio_base: base address of pwm chip + * @clk: pointer to clk structure of pwm chip + * @chip: linux pwm chip representation + */ +struct spear_pwm_chip { +	void __iomem *mmio_base; +	struct clk *clk; +	struct pwm_chip chip; +}; + +static inline struct spear_pwm_chip *to_spear_pwm_chip(struct pwm_chip *chip) +{ +	return container_of(chip, struct spear_pwm_chip, chip); +} + +static inline u32 spear_pwm_readl(struct spear_pwm_chip *chip, unsigned int num, +				  unsigned long offset) +{ +	return readl_relaxed(chip->mmio_base + (num << 4) + offset); +} + +static inline void spear_pwm_writel(struct spear_pwm_chip *chip, +				    unsigned int num, unsigned long offset, +				    unsigned long val) +{ +	writel_relaxed(val, chip->mmio_base + (num << 4) + offset); +} + +static int spear_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, +			    int duty_ns, int period_ns) +{ +	struct spear_pwm_chip *pc = to_spear_pwm_chip(chip); +	u64 val, div, clk_rate; +	unsigned long prescale = PWMCR_MIN_PRESCALE, pv, dc; +	int ret; + +	/* +	 * Find pv, dc and prescale to suit duty_ns and period_ns. This is done +	 * according to formulas described below: +	 * +	 * period_ns = 10^9 * (PRESCALE + 1) * PV / PWM_CLK_RATE +	 * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE +	 * +	 * PV = (PWM_CLK_RATE * period_ns) / (10^9 * (PRESCALE + 1)) +	 * DC = (PWM_CLK_RATE * duty_ns) / (10^9 * (PRESCALE + 1)) +	 */ +	clk_rate = clk_get_rate(pc->clk); +	while (1) { +		div = 1000000000; +		div *= 1 + prescale; +		val = clk_rate * period_ns; +		pv = div64_u64(val, div); +		val = clk_rate * duty_ns; +		dc = div64_u64(val, div); + +		/* if duty_ns and period_ns are not achievable then return */ +		if (pv < PWMPCR_MIN_PERIOD || dc < PWMDCR_MIN_DUTY) +			return -EINVAL; + +		/* +		 * if pv and dc have crossed their upper limit, then increase +		 * prescale and recalculate pv and dc. +		 */ +		if (pv > PWMPCR_MAX_PERIOD || dc > PWMDCR_MAX_DUTY) { +			if (++prescale > PWMCR_MAX_PRESCALE) +				return -EINVAL; +			continue; +		} +		break; +	} + +	/* +	 * NOTE: the clock to PWM has to be enabled first before writing to the +	 * registers. +	 */ +	ret = clk_enable(pc->clk); +	if (ret) +		return ret; + +	spear_pwm_writel(pc, pwm->hwpwm, PWMCR, +			prescale << PWMCR_PRESCALE_SHIFT); +	spear_pwm_writel(pc, pwm->hwpwm, PWMDCR, dc); +	spear_pwm_writel(pc, pwm->hwpwm, PWMPCR, pv); +	clk_disable(pc->clk); + +	return 0; +} + +static int spear_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct spear_pwm_chip *pc = to_spear_pwm_chip(chip); +	int rc = 0; +	u32 val; + +	rc = clk_enable(pc->clk); +	if (rc) +		return rc; + +	val = spear_pwm_readl(pc, pwm->hwpwm, PWMCR); +	val |= PWMCR_PWM_ENABLE; +	spear_pwm_writel(pc, pwm->hwpwm, PWMCR, val); + +	return 0; +} + +static void spear_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct spear_pwm_chip *pc = to_spear_pwm_chip(chip); +	u32 val; + +	val = spear_pwm_readl(pc, pwm->hwpwm, PWMCR); +	val &= ~PWMCR_PWM_ENABLE; +	spear_pwm_writel(pc, pwm->hwpwm, PWMCR, val); + +	clk_disable(pc->clk); +} + +static const struct pwm_ops spear_pwm_ops = { +	.config = spear_pwm_config, +	.enable = spear_pwm_enable, +	.disable = spear_pwm_disable, +	.owner = THIS_MODULE, +}; + +static int spear_pwm_probe(struct platform_device *pdev) +{ +	struct device_node *np = pdev->dev.of_node; +	struct spear_pwm_chip *pc; +	struct resource *r; +	int ret; +	u32 val; + +	pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); +	if (!pc) +		return -ENOMEM; + +	r = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	pc->mmio_base = devm_ioremap_resource(&pdev->dev, r); +	if (IS_ERR(pc->mmio_base)) +		return PTR_ERR(pc->mmio_base); + +	pc->clk = devm_clk_get(&pdev->dev, NULL); +	if (IS_ERR(pc->clk)) +		return PTR_ERR(pc->clk); + +	platform_set_drvdata(pdev, pc); + +	pc->chip.dev = &pdev->dev; +	pc->chip.ops = &spear_pwm_ops; +	pc->chip.base = -1; +	pc->chip.npwm = NUM_PWM; + +	ret = clk_prepare(pc->clk); +	if (ret) +		return ret; + +	if (of_device_is_compatible(np, "st,spear1340-pwm")) { +		ret = clk_enable(pc->clk); +		if (ret) { +			clk_unprepare(pc->clk); +			return ret; +		} +		/* +		 * Following enables PWM chip, channels would still be +		 * enabled individually through their control register +		 */ +		val = readl_relaxed(pc->mmio_base + PWMMCR); +		val |= PWMMCR_PWM_ENABLE; +		writel_relaxed(val, pc->mmio_base + PWMMCR); + +		clk_disable(pc->clk); +	} + +	ret = pwmchip_add(&pc->chip); +	if (ret < 0) { +		clk_unprepare(pc->clk); +		dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); +	} + +	return ret; +} + +static int spear_pwm_remove(struct platform_device *pdev) +{ +	struct spear_pwm_chip *pc = platform_get_drvdata(pdev); +	int i; + +	for (i = 0; i < NUM_PWM; i++) +		pwm_disable(&pc->chip.pwms[i]); + +	/* clk was prepared in probe, hence unprepare it here */ +	clk_unprepare(pc->clk); +	return pwmchip_remove(&pc->chip); +} + +static const struct of_device_id spear_pwm_of_match[] = { +	{ .compatible = "st,spear320-pwm" }, +	{ .compatible = "st,spear1340-pwm" }, +	{ } +}; + +MODULE_DEVICE_TABLE(of, spear_pwm_of_match); + +static struct platform_driver spear_pwm_driver = { +	.driver = { +		.name = "spear-pwm", +		.owner = THIS_MODULE, +		.of_match_table = spear_pwm_of_match, +	}, +	.probe = spear_pwm_probe, +	.remove = spear_pwm_remove, +}; + +module_platform_driver(spear_pwm_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Shiraz Hashim <shiraz.linux.kernel@gmail.com>"); +MODULE_AUTHOR("Viresh Kumar <viresh.kumar@linaro.com>"); +MODULE_ALIAS("platform:spear-pwm"); diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c new file mode 100644 index 00000000000..61d86b9498c --- /dev/null +++ b/drivers/pwm/pwm-tegra.c @@ -0,0 +1,251 @@ +/* + * 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 <s.hauer@pengutronix.de> + * + * 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 <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/pwm.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#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) +		return -ENOMEM; + +	pwm->dev = &pdev->dev; + +	r = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	pwm->mmio_base = devm_ioremap_resource(&pdev->dev, r); +	if (IS_ERR(pwm->mmio_base)) +		return PTR_ERR(pwm->mmio_base); + +	platform_set_drvdata(pdev, pwm); + +	pwm->clk = devm_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); +		return ret; +	} + +	return 0; +} + +static int 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); +	} + +	return pwmchip_remove(&pc->chip); +} + +static const struct of_device_id tegra_pwm_of_match[] = { +	{ .compatible = "nvidia,tegra20-pwm" }, +	{ .compatible = "nvidia,tegra30-pwm" }, +	{ } +}; + +MODULE_DEVICE_TABLE(of, tegra_pwm_of_match); + +static struct platform_driver tegra_pwm_driver = { +	.driver = { +		.name = "tegra-pwm", +		.owner = THIS_MODULE, +		.of_match_table = tegra_pwm_of_match, +	}, +	.probe = tegra_pwm_probe, +	.remove = tegra_pwm_remove, +}; + +module_platform_driver(tegra_pwm_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("NVIDIA Corporation"); +MODULE_ALIAS("platform:tegra-pwm"); diff --git a/drivers/pwm/pwm-tiecap.c b/drivers/pwm/pwm-tiecap.c new file mode 100644 index 00000000000..74efbe7f20c --- /dev/null +++ b/drivers/pwm/pwm-tiecap.c @@ -0,0 +1,346 @@ +/* + * 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 <linux/module.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/pm_runtime.h> +#include <linux/pwm.h> +#include <linux/of_device.h> + +#include "pwm-tipwmss.h" + +/* ECAP registers and bits definitions */ +#define CAP1			0x08 +#define CAP2			0x0C +#define CAP3			0x10 +#define CAP4			0x14 +#define ECCTL2			0x2A +#define ECCTL2_APWM_POL_LOW	BIT(10) +#define ECCTL2_APWM_MODE	BIT(9) +#define ECCTL2_SYNC_SEL_DISA	(BIT(7) | BIT(6)) +#define ECCTL2_TSCTR_FREERUN	BIT(4) + +struct ecap_context { +	u32	cap3; +	u32	cap4; +	u16	ecctl2; +}; + +struct ecap_pwm_chip { +	struct pwm_chip	chip; +	unsigned int	clk_rate; +	void __iomem	*mmio_base; +	struct ecap_context ctx; +}; + +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 > 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); +	} + +	if (!test_bit(PWMF_ENABLED, &pwm->flags)) { +		reg_val = readw(pc->mmio_base + ECCTL2); +		/* Disable APWM mode to put APWM output Low */ +		reg_val &= ~ECCTL2_APWM_MODE; +		writew(reg_val, pc->mmio_base + ECCTL2); +	} + +	pm_runtime_put_sync(pc->chip.dev); +	return 0; +} + +static int ecap_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm, +		enum pwm_polarity polarity) +{ +	struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip); +	unsigned short reg_val; + +	pm_runtime_get_sync(pc->chip.dev); +	reg_val = readw(pc->mmio_base + ECCTL2); +	if (polarity == PWM_POLARITY_INVERSED) +		/* Duty cycle defines LOW period of PWM */ +		reg_val |= ECCTL2_APWM_POL_LOW; +	else +		/* Duty cycle defines HIGH period of PWM */ +		reg_val &= ~ECCTL2_APWM_POL_LOW; + +	writew(reg_val, pc->mmio_base + ECCTL2); +	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, +	.set_polarity	= ecap_pwm_set_polarity, +	.enable		= ecap_pwm_enable, +	.disable	= ecap_pwm_disable, +	.owner		= THIS_MODULE, +}; + +static const struct of_device_id ecap_of_match[] = { +	{ .compatible	= "ti,am33xx-ecap" }, +	{}, +}; +MODULE_DEVICE_TABLE(of, ecap_of_match); + +static int ecap_pwm_probe(struct platform_device *pdev) +{ +	int ret; +	struct resource *r; +	struct clk *clk; +	struct ecap_pwm_chip *pc; +	u16 status; + +	pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); +	if (!pc) +		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.of_xlate = of_pwm_xlate_with_flags; +	pc->chip.of_pwm_n_cells = 3; +	pc->chip.base = -1; +	pc->chip.npwm = 1; + +	r = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	pc->mmio_base = devm_ioremap_resource(&pdev->dev, r); +	if (IS_ERR(pc->mmio_base)) +		return PTR_ERR(pc->mmio_base); + +	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); +	pm_runtime_get_sync(&pdev->dev); + +	status = pwmss_submodule_state_change(pdev->dev.parent, +			PWMSS_ECAPCLK_EN); +	if (!(status & PWMSS_ECAPCLK_EN_ACK)) { +		dev_err(&pdev->dev, "PWMSS config space clock enable failed\n"); +		ret = -EINVAL; +		goto pwmss_clk_failure; +	} + +	pm_runtime_put_sync(&pdev->dev); + +	platform_set_drvdata(pdev, pc); +	return 0; + +pwmss_clk_failure: +	pm_runtime_put_sync(&pdev->dev); +	pm_runtime_disable(&pdev->dev); +	pwmchip_remove(&pc->chip); +	return ret; +} + +static int ecap_pwm_remove(struct platform_device *pdev) +{ +	struct ecap_pwm_chip *pc = platform_get_drvdata(pdev); + +	pm_runtime_get_sync(&pdev->dev); +	/* +	 * Due to hardware misbehaviour, acknowledge of the stop_req +	 * is missing. Hence checking of the status bit skipped. +	 */ +	pwmss_submodule_state_change(pdev->dev.parent, PWMSS_ECAPCLK_STOP_REQ); +	pm_runtime_put_sync(&pdev->dev); + +	pm_runtime_disable(&pdev->dev); +	return pwmchip_remove(&pc->chip); +} + +#ifdef CONFIG_PM_SLEEP +static void ecap_pwm_save_context(struct ecap_pwm_chip *pc) +{ +	pm_runtime_get_sync(pc->chip.dev); +	pc->ctx.ecctl2 = readw(pc->mmio_base + ECCTL2); +	pc->ctx.cap4 = readl(pc->mmio_base + CAP4); +	pc->ctx.cap3 = readl(pc->mmio_base + CAP3); +	pm_runtime_put_sync(pc->chip.dev); +} + +static void ecap_pwm_restore_context(struct ecap_pwm_chip *pc) +{ +	writel(pc->ctx.cap3, pc->mmio_base + CAP3); +	writel(pc->ctx.cap4, pc->mmio_base + CAP4); +	writew(pc->ctx.ecctl2, pc->mmio_base + ECCTL2); +} + +static int ecap_pwm_suspend(struct device *dev) +{ +	struct ecap_pwm_chip *pc = dev_get_drvdata(dev); +	struct pwm_device *pwm = pc->chip.pwms; + +	ecap_pwm_save_context(pc); + +	/* Disable explicitly if PWM is running */ +	if (test_bit(PWMF_ENABLED, &pwm->flags)) +		pm_runtime_put_sync(dev); + +	return 0; +} + +static int ecap_pwm_resume(struct device *dev) +{ +	struct ecap_pwm_chip *pc = dev_get_drvdata(dev); +	struct pwm_device *pwm = pc->chip.pwms; + +	/* Enable explicitly if PWM was running */ +	if (test_bit(PWMF_ENABLED, &pwm->flags)) +		pm_runtime_get_sync(dev); + +	ecap_pwm_restore_context(pc); +	return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(ecap_pwm_pm_ops, ecap_pwm_suspend, ecap_pwm_resume); + +static struct platform_driver ecap_pwm_driver = { +	.driver = { +		.name	= "ecap", +		.owner	= THIS_MODULE, +		.of_match_table = ecap_of_match, +		.pm	= &ecap_pwm_pm_ops, +	}, +	.probe = ecap_pwm_probe, +	.remove = ecap_pwm_remove, +}; + +module_platform_driver(ecap_pwm_driver); + +MODULE_DESCRIPTION("ECAP PWM driver"); +MODULE_AUTHOR("Texas Instruments"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c new file mode 100644 index 00000000000..cb75133085a --- /dev/null +++ b/drivers/pwm/pwm-tiehrpwm.c @@ -0,0 +1,614 @@ +/* + * 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 <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/pm_runtime.h> +#include <linux/of_device.h> + +#include "pwm-tipwmss.h" + +/* 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 AQCTL_CHANA_POLNORMAL	(AQCTL_CAU_FRCLOW | AQCTL_PRD_FRCHIGH | \ +				AQCTL_ZRO_FRCHIGH) +#define AQCTL_CHANA_POLINVERSED	(AQCTL_CAU_FRCHIGH | AQCTL_PRD_FRCLOW | \ +				AQCTL_ZRO_FRCLOW) +#define AQCTL_CHANB_POLNORMAL	(AQCTL_CBU_FRCLOW | AQCTL_PRD_FRCHIGH | \ +				AQCTL_ZRO_FRCHIGH) +#define AQCTL_CHANB_POLINVERSED	(AQCTL_CBU_FRCHIGH | AQCTL_PRD_FRCLOW | \ +				AQCTL_ZRO_FRCLOW) + +#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_context { +	u16 tbctl; +	u16 tbprd; +	u16 cmpa; +	u16 cmpb; +	u16 aqctla; +	u16 aqctlb; +	u16 aqsfrc; +	u16 aqcsfrc; +}; + +struct ehrpwm_pwm_chip { +	struct pwm_chip	chip; +	unsigned int	clk_rate; +	void __iomem	*mmio_base; +	unsigned long period_cycles[NUM_PWM_CHANNEL]; +	enum pwm_polarity polarity[NUM_PWM_CHANNEL]; +	struct	clk	*tbclk; +	struct ehrpwm_context ctx; +}; + +static inline struct ehrpwm_pwm_chip *to_ehrpwm_pwm_chip(struct pwm_chip *chip) +{ +	return container_of(chip, struct ehrpwm_pwm_chip, chip); +} + +static inline u16 ehrpwm_read(void __iomem *base, int offset) +{ +	return readw(base + offset); +} + +static inline void ehrpwm_write(void __iomem *base, int offset, unsigned int val) +{ +	writew(val & 0xFFFF, base + offset); +} + +static void ehrpwm_modify(void __iomem *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_polarity(struct ehrpwm_pwm_chip *pc, int chan) +{ +	int aqctl_reg; +	unsigned short aqctl_val, aqctl_mask; + +	/* +	 * Configure PWM output to HIGH/LOW level on counter +	 * reaches compare register value and LOW/HIGH level +	 * on counter value reaches period register value and +	 * zero value on counter +	 */ +	if (chan == 1) { +		aqctl_reg = AQCTLB; +		aqctl_mask = AQCTL_CBU_MASK; + +		if (pc->polarity[chan] == PWM_POLARITY_INVERSED) +			aqctl_val = AQCTL_CHANB_POLINVERSED; +		else +			aqctl_val = AQCTL_CHANB_POLNORMAL; +	} else { +		aqctl_reg = AQCTLA; +		aqctl_mask = AQCTL_CAU_MASK; + +		if (pc->polarity[chan] == PWM_POLARITY_INVERSED) +			aqctl_val = AQCTL_CHANA_POLINVERSED; +		else +			aqctl_val = AQCTL_CHANA_POLNORMAL; +	} + +	aqctl_mask |= AQCTL_PRD_MASK | AQCTL_ZRO_MASK; +	ehrpwm_modify(pc->mmio_base, aqctl_reg, aqctl_mask, aqctl_val); +} + +/* + * 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; +	int i, cmp_reg; + +	if (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; +	} + +	/* +	 * Period values should be same for multiple PWM channels as IP uses +	 * same period register for multiple channels. +	 */ +	for (i = 0; i < NUM_PWM_CHANNEL; i++) { +		if (pc->period_cycles[i] && +				(pc->period_cycles[i] != period_cycles)) { +			/* +			 * Allow channel to reconfigure period if no other +			 * channels being configured. +			 */ +			if (i == pwm->hwpwm) +				continue; + +			dev_err(chip->dev, "Period value conflicts with channel %d\n", +					i); +			return -EINVAL; +		} +	} + +	pc->period_cycles[pwm->hwpwm] = period_cycles; + +	/* 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); + +	if (pwm->hwpwm == 1) +		/* Channel 1 configured with compare B register */ +		cmp_reg = CMPB; +	else +		/* Channel 0 configured with compare A register */ +		cmp_reg = CMPA; + +	ehrpwm_write(pc->mmio_base, cmp_reg, duty_cycles); + +	pm_runtime_put_sync(chip->dev); +	return 0; +} + +static int ehrpwm_pwm_set_polarity(struct pwm_chip *chip, +		struct pwm_device *pwm,	enum pwm_polarity polarity) +{ +	struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip); + +	/* Configuration of polarity in hardware delayed, do at enable */ +	pc->polarity[pwm->hwpwm] = polarity; +	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; +	int ret; + +	/* 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); + +	/* Channels polarity can be configured from action qualifier module */ +	configure_polarity(pc, pwm->hwpwm); + +	/* Enable TBCLK before enabling PWM device */ +	ret = clk_enable(pc->tbclk); +	if (ret) { +		dev_err(chip->dev, "Failed to enable TBCLK for %s\n", +			dev_name(pc->chip.dev)); +		return ret; +	} + +	/* 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); + +	/* Disabling TBCLK on PWM disable */ +	clk_disable(pc->tbclk); + +	/* 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) +{ +	struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip); + +	if (test_bit(PWMF_ENABLED, &pwm->flags)) { +		dev_warn(chip->dev, "Removing PWM device without disabling\n"); +		pm_runtime_put_sync(chip->dev); +	} + +	/* set period value to zero on free */ +	pc->period_cycles[pwm->hwpwm] = 0; +} + +static const struct pwm_ops ehrpwm_pwm_ops = { +	.free		= ehrpwm_pwm_free, +	.config		= ehrpwm_pwm_config, +	.set_polarity	= ehrpwm_pwm_set_polarity, +	.enable		= ehrpwm_pwm_enable, +	.disable	= ehrpwm_pwm_disable, +	.owner		= THIS_MODULE, +}; + +static const struct of_device_id ehrpwm_of_match[] = { +	{ .compatible	= "ti,am33xx-ehrpwm" }, +	{}, +}; +MODULE_DEVICE_TABLE(of, ehrpwm_of_match); + +static int ehrpwm_pwm_probe(struct platform_device *pdev) +{ +	int ret; +	struct resource *r; +	struct clk *clk; +	struct ehrpwm_pwm_chip *pc; +	u16 status; + +	pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); +	if (!pc) +		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.of_xlate = of_pwm_xlate_with_flags; +	pc->chip.of_pwm_n_cells = 3; +	pc->chip.base = -1; +	pc->chip.npwm = NUM_PWM_CHANNEL; + +	r = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	pc->mmio_base = devm_ioremap_resource(&pdev->dev, r); +	if (IS_ERR(pc->mmio_base)) +		return PTR_ERR(pc->mmio_base); + +	/* Acquire tbclk for Time Base EHRPWM submodule */ +	pc->tbclk = devm_clk_get(&pdev->dev, "tbclk"); +	if (IS_ERR(pc->tbclk)) { +		dev_err(&pdev->dev, "Failed to get tbclk\n"); +		return PTR_ERR(pc->tbclk); +	} + +	ret = clk_prepare(pc->tbclk); +	if (ret < 0) { +		dev_err(&pdev->dev, "clk_prepare() failed: %d\n", ret); +		return ret; +	} + +	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); +	pm_runtime_get_sync(&pdev->dev); + +	status = pwmss_submodule_state_change(pdev->dev.parent, +			PWMSS_EPWMCLK_EN); +	if (!(status & PWMSS_EPWMCLK_EN_ACK)) { +		dev_err(&pdev->dev, "PWMSS config space clock enable failed\n"); +		ret = -EINVAL; +		goto pwmss_clk_failure; +	} + +	pm_runtime_put_sync(&pdev->dev); + +	platform_set_drvdata(pdev, pc); +	return 0; + +pwmss_clk_failure: +	pm_runtime_put_sync(&pdev->dev); +	pm_runtime_disable(&pdev->dev); +	pwmchip_remove(&pc->chip); +	clk_unprepare(pc->tbclk); +	return ret; +} + +static int ehrpwm_pwm_remove(struct platform_device *pdev) +{ +	struct ehrpwm_pwm_chip *pc = platform_get_drvdata(pdev); + +	clk_unprepare(pc->tbclk); + +	pm_runtime_get_sync(&pdev->dev); +	/* +	 * Due to hardware misbehaviour, acknowledge of the stop_req +	 * is missing. Hence checking of the status bit skipped. +	 */ +	pwmss_submodule_state_change(pdev->dev.parent, PWMSS_EPWMCLK_STOP_REQ); +	pm_runtime_put_sync(&pdev->dev); + +	pm_runtime_put_sync(&pdev->dev); +	pm_runtime_disable(&pdev->dev); +	return pwmchip_remove(&pc->chip); +} + +#ifdef CONFIG_PM_SLEEP +static void ehrpwm_pwm_save_context(struct ehrpwm_pwm_chip *pc) +{ +	pm_runtime_get_sync(pc->chip.dev); +	pc->ctx.tbctl = ehrpwm_read(pc->mmio_base, TBCTL); +	pc->ctx.tbprd = ehrpwm_read(pc->mmio_base, TBPRD); +	pc->ctx.cmpa = ehrpwm_read(pc->mmio_base, CMPA); +	pc->ctx.cmpb = ehrpwm_read(pc->mmio_base, CMPB); +	pc->ctx.aqctla = ehrpwm_read(pc->mmio_base, AQCTLA); +	pc->ctx.aqctlb = ehrpwm_read(pc->mmio_base, AQCTLB); +	pc->ctx.aqsfrc = ehrpwm_read(pc->mmio_base, AQSFRC); +	pc->ctx.aqcsfrc = ehrpwm_read(pc->mmio_base, AQCSFRC); +	pm_runtime_put_sync(pc->chip.dev); +} + +static void ehrpwm_pwm_restore_context(struct ehrpwm_pwm_chip *pc) +{ +	ehrpwm_write(pc->mmio_base, TBPRD, pc->ctx.tbprd); +	ehrpwm_write(pc->mmio_base, CMPA, pc->ctx.cmpa); +	ehrpwm_write(pc->mmio_base, CMPB, pc->ctx.cmpb); +	ehrpwm_write(pc->mmio_base, AQCTLA, pc->ctx.aqctla); +	ehrpwm_write(pc->mmio_base, AQCTLB, pc->ctx.aqctlb); +	ehrpwm_write(pc->mmio_base, AQSFRC, pc->ctx.aqsfrc); +	ehrpwm_write(pc->mmio_base, AQCSFRC, pc->ctx.aqcsfrc); +	ehrpwm_write(pc->mmio_base, TBCTL, pc->ctx.tbctl); +} + +static int ehrpwm_pwm_suspend(struct device *dev) +{ +	struct ehrpwm_pwm_chip *pc = dev_get_drvdata(dev); +	int i; + +	ehrpwm_pwm_save_context(pc); +	for (i = 0; i < pc->chip.npwm; i++) { +		struct pwm_device *pwm = &pc->chip.pwms[i]; + +		if (!test_bit(PWMF_ENABLED, &pwm->flags)) +			continue; + +		/* Disable explicitly if PWM is running */ +		pm_runtime_put_sync(dev); +	} +	return 0; +} + +static int ehrpwm_pwm_resume(struct device *dev) +{ +	struct ehrpwm_pwm_chip *pc = dev_get_drvdata(dev); +	int i; + +	for (i = 0; i < pc->chip.npwm; i++) { +		struct pwm_device *pwm = &pc->chip.pwms[i]; + +		if (!test_bit(PWMF_ENABLED, &pwm->flags)) +			continue; + +		/* Enable explicitly if PWM was running */ +		pm_runtime_get_sync(dev); +	} +	ehrpwm_pwm_restore_context(pc); +	return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(ehrpwm_pwm_pm_ops, ehrpwm_pwm_suspend, +		ehrpwm_pwm_resume); + +static struct platform_driver ehrpwm_pwm_driver = { +	.driver = { +		.name	= "ehrpwm", +		.owner	= THIS_MODULE, +		.of_match_table = ehrpwm_of_match, +		.pm	= &ehrpwm_pwm_pm_ops, +	}, +	.probe = ehrpwm_pwm_probe, +	.remove = ehrpwm_pwm_remove, +}; + +module_platform_driver(ehrpwm_pwm_driver); + +MODULE_DESCRIPTION("EHRPWM PWM driver"); +MODULE_AUTHOR("Texas Instruments"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-tipwmss.c b/drivers/pwm/pwm-tipwmss.c new file mode 100644 index 00000000000..3b119bc2c3c --- /dev/null +++ b/drivers/pwm/pwm-tipwmss.c @@ -0,0 +1,136 @@ +/* + * TI PWM Subsystem driver + * + * Copyright (C) 2012 Texas Instruments Incorporated - 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. + * + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/pm_runtime.h> +#include <linux/of_device.h> + +#include "pwm-tipwmss.h" + +#define PWMSS_CLKCONFIG		0x8	/* Clock gating reg */ +#define PWMSS_CLKSTATUS		0xc	/* Clock gating status reg */ + +struct pwmss_info { +	void __iomem	*mmio_base; +	struct mutex	pwmss_lock; +	u16		pwmss_clkconfig; +}; + +u16 pwmss_submodule_state_change(struct device *dev, int set) +{ +	struct pwmss_info *info = dev_get_drvdata(dev); +	u16 val; + +	mutex_lock(&info->pwmss_lock); +	val = readw(info->mmio_base + PWMSS_CLKCONFIG); +	val |= set; +	writew(val , info->mmio_base + PWMSS_CLKCONFIG); +	mutex_unlock(&info->pwmss_lock); + +	return readw(info->mmio_base + PWMSS_CLKSTATUS); +} +EXPORT_SYMBOL(pwmss_submodule_state_change); + +static const struct of_device_id pwmss_of_match[] = { +	{ .compatible	= "ti,am33xx-pwmss" }, +	{}, +}; +MODULE_DEVICE_TABLE(of, pwmss_of_match); + +static int pwmss_probe(struct platform_device *pdev) +{ +	int ret; +	struct resource *r; +	struct pwmss_info *info; +	struct device_node *node = pdev->dev.of_node; + +	info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); +	if (!info) { +		dev_err(&pdev->dev, "failed to allocate memory\n"); +		return -ENOMEM; +	} + +	mutex_init(&info->pwmss_lock); + +	r = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	info->mmio_base = devm_ioremap_resource(&pdev->dev, r); +	if (IS_ERR(info->mmio_base)) +		return PTR_ERR(info->mmio_base); + +	pm_runtime_enable(&pdev->dev); +	pm_runtime_get_sync(&pdev->dev); +	platform_set_drvdata(pdev, info); + +	/* Populate all the child nodes here... */ +	ret = of_platform_populate(node, NULL, NULL, &pdev->dev); +	if (ret) +		dev_err(&pdev->dev, "no child node found\n"); + +	return ret; +} + +static int pwmss_remove(struct platform_device *pdev) +{ +	struct pwmss_info *info = platform_get_drvdata(pdev); + +	pm_runtime_put_sync(&pdev->dev); +	pm_runtime_disable(&pdev->dev); +	mutex_destroy(&info->pwmss_lock); +	return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int pwmss_suspend(struct device *dev) +{ +	struct pwmss_info *info = dev_get_drvdata(dev); + +	info->pwmss_clkconfig = readw(info->mmio_base + PWMSS_CLKCONFIG); +	pm_runtime_put_sync(dev); +	return 0; +} + +static int pwmss_resume(struct device *dev) +{ +	struct pwmss_info *info = dev_get_drvdata(dev); + +	pm_runtime_get_sync(dev); +	writew(info->pwmss_clkconfig, info->mmio_base + PWMSS_CLKCONFIG); +	return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(pwmss_pm_ops, pwmss_suspend, pwmss_resume); + +static struct platform_driver pwmss_driver = { +	.driver	= { +		.name	= "pwmss", +		.owner	= THIS_MODULE, +		.pm	= &pwmss_pm_ops, +		.of_match_table	= pwmss_of_match, +	}, +	.probe	= pwmss_probe, +	.remove	= pwmss_remove, +}; + +module_platform_driver(pwmss_driver); + +MODULE_DESCRIPTION("PWM Subsystem driver"); +MODULE_AUTHOR("Texas Instruments"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-tipwmss.h b/drivers/pwm/pwm-tipwmss.h new file mode 100644 index 00000000000..10ad8040408 --- /dev/null +++ b/drivers/pwm/pwm-tipwmss.h @@ -0,0 +1,39 @@ +/* + * TI PWM Subsystem driver + * + * Copyright (C) 2012 Texas Instruments Incorporated - 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. + * + */ + +#ifndef __TIPWMSS_H +#define __TIPWMSS_H + +/* PWM substem clock gating */ +#define PWMSS_ECAPCLK_EN	BIT(0) +#define PWMSS_ECAPCLK_STOP_REQ	BIT(1) +#define PWMSS_EPWMCLK_EN	BIT(8) +#define PWMSS_EPWMCLK_STOP_REQ	BIT(9) + +#define PWMSS_ECAPCLK_EN_ACK	BIT(0) +#define PWMSS_EPWMCLK_EN_ACK	BIT(8) + +#ifdef CONFIG_PWM_TIPWMSS +extern u16 pwmss_submodule_state_change(struct device *dev, int set); +#else +static inline u16 pwmss_submodule_state_change(struct device *dev, int set) +{ +	/* return success status value */ +	return 0xFFFF; +} +#endif +#endif	/* __TIPWMSS_H */ diff --git a/drivers/pwm/pwm-twl-led.c b/drivers/pwm/pwm-twl-led.c new file mode 100644 index 00000000000..b964470025c --- /dev/null +++ b/drivers/pwm/pwm-twl-led.c @@ -0,0 +1,348 @@ +/* + * Driver for TWL4030/6030 Pulse Width Modulator used as LED driver + * + * Copyright (C) 2012 Texas Instruments + * Author: Peter Ujfalusi <peter.ujfalusi@ti.com> + * + * This driver is a complete rewrite of the former pwm-twl6030.c authorded by: + * Hemanth V <hemanthv@ti.com> + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/i2c/twl.h> +#include <linux/slab.h> + +/* + * This driver handles the PWM driven LED terminals of TWL4030 and TWL6030. + * To generate the signal on TWL4030: + *  - LEDA uses PWMA + *  - LEDB uses PWMB + * TWL6030 has one LED pin with dedicated LEDPWM + */ + +#define TWL4030_LED_MAX		0x7f +#define TWL6030_LED_MAX		0xff + +/* Registers, bits and macro for TWL4030 */ +#define TWL4030_LEDEN_REG	0x00 +#define TWL4030_PWMA_REG	0x01 + +#define TWL4030_LEDXON		(1 << 0) +#define TWL4030_LEDXPWM		(1 << 4) +#define TWL4030_LED_PINS	(TWL4030_LEDXON | TWL4030_LEDXPWM) +#define TWL4030_LED_TOGGLE(led, x)	((x) << (led)) + +/* Register, bits and macro for TWL6030 */ +#define TWL6030_LED_PWM_CTRL1	0xf4 +#define TWL6030_LED_PWM_CTRL2	0xf5 + +#define TWL6040_LED_MODE_HW	0x00 +#define TWL6040_LED_MODE_ON	0x01 +#define TWL6040_LED_MODE_OFF	0x02 +#define TWL6040_LED_MODE_MASK	0x03 + +struct twl_pwmled_chip { +	struct pwm_chip chip; +	struct mutex mutex; +}; + +static inline struct twl_pwmled_chip *to_twl(struct pwm_chip *chip) +{ +	return container_of(chip, struct twl_pwmled_chip, chip); +} + +static int twl4030_pwmled_config(struct pwm_chip *chip, struct pwm_device *pwm, +			      int duty_ns, int period_ns) +{ +	int duty_cycle = DIV_ROUND_UP(duty_ns * TWL4030_LED_MAX, period_ns) + 1; +	u8 pwm_config[2] = { 1, 0 }; +	int base, ret; + +	/* +	 * To configure the duty period: +	 * On-cycle is set to 1 (the minimum allowed value) +	 * The off time of 0 is not configurable, so the mapping is: +	 * 0 -> off cycle = 2, +	 * 1 -> off cycle = 2, +	 * 2 -> off cycle = 3, +	 * 126 - > off cycle 127, +	 * 127 - > off cycle 1 +	 * When on cycle == off cycle the PWM will be always on +	 */ +	if (duty_cycle == 1) +		duty_cycle = 2; +	else if (duty_cycle > TWL4030_LED_MAX) +		duty_cycle = 1; + +	base = pwm->hwpwm * 2 + TWL4030_PWMA_REG; + +	pwm_config[1] = duty_cycle; + +	ret = twl_i2c_write(TWL4030_MODULE_LED, pwm_config, base, 2); +	if (ret < 0) +		dev_err(chip->dev, "%s: Failed to configure PWM\n", pwm->label); + +	return ret; +} + +static int twl4030_pwmled_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct twl_pwmled_chip *twl = to_twl(chip); +	int ret; +	u8 val; + +	mutex_lock(&twl->mutex); +	ret = twl_i2c_read_u8(TWL4030_MODULE_LED, &val, TWL4030_LEDEN_REG); +	if (ret < 0) { +		dev_err(chip->dev, "%s: Failed to read LEDEN\n", pwm->label); +		goto out; +	} + +	val |= TWL4030_LED_TOGGLE(pwm->hwpwm, TWL4030_LED_PINS); + +	ret = twl_i2c_write_u8(TWL4030_MODULE_LED, val, TWL4030_LEDEN_REG); +	if (ret < 0) +		dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label); + +out: +	mutex_unlock(&twl->mutex); +	return ret; +} + +static void twl4030_pwmled_disable(struct pwm_chip *chip, +				   struct pwm_device *pwm) +{ +	struct twl_pwmled_chip *twl = to_twl(chip); +	int ret; +	u8 val; + +	mutex_lock(&twl->mutex); +	ret = twl_i2c_read_u8(TWL4030_MODULE_LED, &val, TWL4030_LEDEN_REG); +	if (ret < 0) { +		dev_err(chip->dev, "%s: Failed to read LEDEN\n", pwm->label); +		goto out; +	} + +	val &= ~TWL4030_LED_TOGGLE(pwm->hwpwm, TWL4030_LED_PINS); + +	ret = twl_i2c_write_u8(TWL4030_MODULE_LED, val, TWL4030_LEDEN_REG); +	if (ret < 0) +		dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label); + +out: +	mutex_unlock(&twl->mutex); +} + +static int twl6030_pwmled_config(struct pwm_chip *chip, struct pwm_device *pwm, +			      int duty_ns, int period_ns) +{ +	int duty_cycle = (duty_ns * TWL6030_LED_MAX) / period_ns; +	u8 on_time; +	int ret; + +	on_time = duty_cycle & 0xff; + +	ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, on_time, +			       TWL6030_LED_PWM_CTRL1); +	if (ret < 0) +		dev_err(chip->dev, "%s: Failed to configure PWM\n", pwm->label); + +	return ret; +} + +static int twl6030_pwmled_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct twl_pwmled_chip *twl = to_twl(chip); +	int ret; +	u8 val; + +	mutex_lock(&twl->mutex); +	ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2); +	if (ret < 0) { +		dev_err(chip->dev, "%s: Failed to read PWM_CTRL2\n", +			pwm->label); +		goto out; +	} + +	val &= ~TWL6040_LED_MODE_MASK; +	val |= TWL6040_LED_MODE_ON; + +	ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_LED_PWM_CTRL2); +	if (ret < 0) +		dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label); + +out: +	mutex_unlock(&twl->mutex); +	return ret; +} + +static void twl6030_pwmled_disable(struct pwm_chip *chip, +				   struct pwm_device *pwm) +{ +	struct twl_pwmled_chip *twl = to_twl(chip); +	int ret; +	u8 val; + +	mutex_lock(&twl->mutex); +	ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2); +	if (ret < 0) { +		dev_err(chip->dev, "%s: Failed to read PWM_CTRL2\n", +			pwm->label); +		goto out; +	} + +	val &= ~TWL6040_LED_MODE_MASK; +	val |= TWL6040_LED_MODE_OFF; + +	ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_LED_PWM_CTRL2); +	if (ret < 0) +		dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label); + +out: +	mutex_unlock(&twl->mutex); +} + +static int twl6030_pwmled_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct twl_pwmled_chip *twl = to_twl(chip); +	int ret; +	u8 val; + +	mutex_lock(&twl->mutex); +	ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2); +	if (ret < 0) { +		dev_err(chip->dev, "%s: Failed to read PWM_CTRL2\n", +			pwm->label); +		goto out; +	} + +	val &= ~TWL6040_LED_MODE_MASK; +	val |= TWL6040_LED_MODE_OFF; + +	ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_LED_PWM_CTRL2); +	if (ret < 0) +		dev_err(chip->dev, "%s: Failed to request PWM\n", pwm->label); + +out: +	mutex_unlock(&twl->mutex); +	return ret; +} + +static void twl6030_pwmled_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct twl_pwmled_chip *twl = to_twl(chip); +	int ret; +	u8 val; + +	mutex_lock(&twl->mutex); +	ret = twl_i2c_read_u8(TWL6030_MODULE_ID1, &val, TWL6030_LED_PWM_CTRL2); +	if (ret < 0) { +		dev_err(chip->dev, "%s: Failed to read PWM_CTRL2\n", +			pwm->label); +		goto out; +	} + +	val &= ~TWL6040_LED_MODE_MASK; +	val |= TWL6040_LED_MODE_HW; + +	ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_LED_PWM_CTRL2); +	if (ret < 0) +		dev_err(chip->dev, "%s: Failed to free PWM\n", pwm->label); + +out: +	mutex_unlock(&twl->mutex); +} + +static const struct pwm_ops twl4030_pwmled_ops = { +	.enable = twl4030_pwmled_enable, +	.disable = twl4030_pwmled_disable, +	.config = twl4030_pwmled_config, +	.owner = THIS_MODULE, +}; + +static const struct pwm_ops twl6030_pwmled_ops = { +	.enable = twl6030_pwmled_enable, +	.disable = twl6030_pwmled_disable, +	.config = twl6030_pwmled_config, +	.request = twl6030_pwmled_request, +	.free = twl6030_pwmled_free, +	.owner = THIS_MODULE, +}; + +static int twl_pwmled_probe(struct platform_device *pdev) +{ +	struct twl_pwmled_chip *twl; +	int ret; + +	twl = devm_kzalloc(&pdev->dev, sizeof(*twl), GFP_KERNEL); +	if (!twl) +		return -ENOMEM; + +	if (twl_class_is_4030()) { +		twl->chip.ops = &twl4030_pwmled_ops; +		twl->chip.npwm = 2; +	} else { +		twl->chip.ops = &twl6030_pwmled_ops; +		twl->chip.npwm = 1; +	} + +	twl->chip.dev = &pdev->dev; +	twl->chip.base = -1; +	twl->chip.can_sleep = true; + +	mutex_init(&twl->mutex); + +	ret = pwmchip_add(&twl->chip); +	if (ret < 0) +		return ret; + +	platform_set_drvdata(pdev, twl); + +	return 0; +} + +static int twl_pwmled_remove(struct platform_device *pdev) +{ +	struct twl_pwmled_chip *twl = platform_get_drvdata(pdev); + +	return pwmchip_remove(&twl->chip); +} + +#ifdef CONFIG_OF +static const struct of_device_id twl_pwmled_of_match[] = { +	{ .compatible = "ti,twl4030-pwmled" }, +	{ .compatible = "ti,twl6030-pwmled" }, +	{ }, +}; +MODULE_DEVICE_TABLE(of, twl_pwmled_of_match); +#endif + +static struct platform_driver twl_pwmled_driver = { +	.driver = { +		.name = "twl-pwmled", +		.of_match_table = of_match_ptr(twl_pwmled_of_match), +	}, +	.probe = twl_pwmled_probe, +	.remove = twl_pwmled_remove, +}; +module_platform_driver(twl_pwmled_driver); + +MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>"); +MODULE_DESCRIPTION("PWM driver for TWL4030 and TWL6030 LED outputs"); +MODULE_ALIAS("platform:twl-pwmled"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-twl.c b/drivers/pwm/pwm-twl.c new file mode 100644 index 00000000000..04f76725d59 --- /dev/null +++ b/drivers/pwm/pwm-twl.c @@ -0,0 +1,352 @@ +/* + * Driver for TWL4030/6030 Generic Pulse Width Modulator + * + * Copyright (C) 2012 Texas Instruments + * Author: Peter Ujfalusi <peter.ujfalusi@ti.com> + * + * 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. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/i2c/twl.h> +#include <linux/slab.h> + +/* + * This driver handles the PWMs of TWL4030 and TWL6030. + * The TRM names for the PWMs on TWL4030 are: PWM0, PWM1 + * TWL6030 also have two PWMs named in the TRM as PWM1, PWM2 + */ + +#define TWL_PWM_MAX		0x7f + +/* Registers, bits and macro for TWL4030 */ +#define TWL4030_GPBR1_REG	0x0c +#define TWL4030_PMBR1_REG	0x0d + +/* GPBR1 register bits */ +#define TWL4030_PWMXCLK_ENABLE	(1 << 0) +#define TWL4030_PWMX_ENABLE	(1 << 2) +#define TWL4030_PWMX_BITS	(TWL4030_PWMX_ENABLE | TWL4030_PWMXCLK_ENABLE) +#define TWL4030_PWM_TOGGLE(pwm, x)	((x) << (pwm)) + +/* PMBR1 register bits */ +#define TWL4030_GPIO6_PWM0_MUTE_MASK		(0x03 << 2) +#define TWL4030_GPIO6_PWM0_MUTE_PWM0		(0x01 << 2) +#define TWL4030_GPIO7_VIBRASYNC_PWM1_MASK	(0x03 << 4) +#define TWL4030_GPIO7_VIBRASYNC_PWM1_PWM1	(0x03 << 4) + +/* Register, bits and macro for TWL6030 */ +#define TWL6030_TOGGLE3_REG	0x92 + +#define TWL6030_PWMXR		(1 << 0) +#define TWL6030_PWMXS		(1 << 1) +#define TWL6030_PWMXEN		(1 << 2) +#define TWL6030_PWM_TOGGLE(pwm, x)	((x) << (pwm * 3)) + +struct twl_pwm_chip { +	struct pwm_chip chip; +	struct mutex mutex; +	u8 twl6030_toggle3; +	u8 twl4030_pwm_mux; +}; + +static inline struct twl_pwm_chip *to_twl(struct pwm_chip *chip) +{ +	return container_of(chip, struct twl_pwm_chip, chip); +} + +static int twl_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, +			      int duty_ns, int period_ns) +{ +	int duty_cycle = DIV_ROUND_UP(duty_ns * TWL_PWM_MAX, period_ns) + 1; +	u8 pwm_config[2] = { 1, 0 }; +	int base, ret; + +	/* +	 * To configure the duty period: +	 * On-cycle is set to 1 (the minimum allowed value) +	 * The off time of 0 is not configurable, so the mapping is: +	 * 0 -> off cycle = 2, +	 * 1 -> off cycle = 2, +	 * 2 -> off cycle = 3, +	 * 126 - > off cycle 127, +	 * 127 - > off cycle 1 +	 * When on cycle == off cycle the PWM will be always on +	 */ +	if (duty_cycle == 1) +		duty_cycle = 2; +	else if (duty_cycle > TWL_PWM_MAX) +		duty_cycle = 1; + +	base = pwm->hwpwm * 3; + +	pwm_config[1] = duty_cycle; + +	ret = twl_i2c_write(TWL_MODULE_PWM, pwm_config, base, 2); +	if (ret < 0) +		dev_err(chip->dev, "%s: Failed to configure PWM\n", pwm->label); + +	return ret; +} + +static int twl4030_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct twl_pwm_chip *twl = to_twl(chip); +	int ret; +	u8 val; + +	mutex_lock(&twl->mutex); +	ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_GPBR1_REG); +	if (ret < 0) { +		dev_err(chip->dev, "%s: Failed to read GPBR1\n", pwm->label); +		goto out; +	} + +	val |= TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMXCLK_ENABLE); + +	ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_GPBR1_REG); +	if (ret < 0) +		dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label); + +	val |= TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMX_ENABLE); + +	ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_GPBR1_REG); +	if (ret < 0) +		dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label); + +out: +	mutex_unlock(&twl->mutex); +	return ret; +} + +static void twl4030_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct twl_pwm_chip *twl = to_twl(chip); +	int ret; +	u8 val; + +	mutex_lock(&twl->mutex); +	ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_GPBR1_REG); +	if (ret < 0) { +		dev_err(chip->dev, "%s: Failed to read GPBR1\n", pwm->label); +		goto out; +	} + +	val &= ~TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMX_ENABLE); + +	ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_GPBR1_REG); +	if (ret < 0) +		dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label); + +	val &= ~TWL4030_PWM_TOGGLE(pwm->hwpwm, TWL4030_PWMXCLK_ENABLE); + +	ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_GPBR1_REG); +	if (ret < 0) +		dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label); + +out: +	mutex_unlock(&twl->mutex); +} + +static int twl4030_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct twl_pwm_chip *twl = to_twl(chip); +	int ret; +	u8 val, mask, bits; + +	if (pwm->hwpwm == 1) { +		mask = TWL4030_GPIO7_VIBRASYNC_PWM1_MASK; +		bits = TWL4030_GPIO7_VIBRASYNC_PWM1_PWM1; +	} else { +		mask = TWL4030_GPIO6_PWM0_MUTE_MASK; +		bits = TWL4030_GPIO6_PWM0_MUTE_PWM0; +	} + +	mutex_lock(&twl->mutex); +	ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_PMBR1_REG); +	if (ret < 0) { +		dev_err(chip->dev, "%s: Failed to read PMBR1\n", pwm->label); +		goto out; +	} + +	/* Save the current MUX configuration for the PWM */ +	twl->twl4030_pwm_mux &= ~mask; +	twl->twl4030_pwm_mux |= (val & mask); + +	/* Select PWM functionality */ +	val &= ~mask; +	val |= bits; + +	ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_PMBR1_REG); +	if (ret < 0) +		dev_err(chip->dev, "%s: Failed to request PWM\n", pwm->label); + +out: +	mutex_unlock(&twl->mutex); +	return ret; +} + +static void twl4030_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct twl_pwm_chip *twl = to_twl(chip); +	int ret; +	u8 val, mask; + +	if (pwm->hwpwm == 1) +		mask = TWL4030_GPIO7_VIBRASYNC_PWM1_MASK; +	else +		mask = TWL4030_GPIO6_PWM0_MUTE_MASK; + +	mutex_lock(&twl->mutex); +	ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, &val, TWL4030_PMBR1_REG); +	if (ret < 0) { +		dev_err(chip->dev, "%s: Failed to read PMBR1\n", pwm->label); +		goto out; +	} + +	/* Restore the MUX configuration for the PWM */ +	val &= ~mask; +	val |= (twl->twl4030_pwm_mux & mask); + +	ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, val, TWL4030_PMBR1_REG); +	if (ret < 0) +		dev_err(chip->dev, "%s: Failed to free PWM\n", pwm->label); + +out: +	mutex_unlock(&twl->mutex); +} + +static int twl6030_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct twl_pwm_chip *twl = to_twl(chip); +	int ret; +	u8 val; + +	mutex_lock(&twl->mutex); +	val = twl->twl6030_toggle3; +	val |= TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXS | TWL6030_PWMXEN); +	val &= ~TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXR); + +	ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_TOGGLE3_REG); +	if (ret < 0) { +		dev_err(chip->dev, "%s: Failed to enable PWM\n", pwm->label); +		goto out; +	} + +	twl->twl6030_toggle3 = val; +out: +	mutex_unlock(&twl->mutex); +	return ret; +} + +static void twl6030_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct twl_pwm_chip *twl = to_twl(chip); +	int ret; +	u8 val; + +	mutex_lock(&twl->mutex); +	val = twl->twl6030_toggle3; +	val |= TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXR); +	val &= ~TWL6030_PWM_TOGGLE(pwm->hwpwm, TWL6030_PWMXS | TWL6030_PWMXEN); + +	ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_TOGGLE3_REG); +	if (ret < 0) { +		dev_err(chip->dev, "%s: Failed to disable PWM\n", pwm->label); +		goto out; +	} + +	twl->twl6030_toggle3 = val; +out: +	mutex_unlock(&twl->mutex); +} + +static const struct pwm_ops twl4030_pwm_ops = { +	.config = twl_pwm_config, +	.enable = twl4030_pwm_enable, +	.disable = twl4030_pwm_disable, +	.request = twl4030_pwm_request, +	.free = twl4030_pwm_free, +	.owner = THIS_MODULE, +}; + +static const struct pwm_ops twl6030_pwm_ops = { +	.config = twl_pwm_config, +	.enable = twl6030_pwm_enable, +	.disable = twl6030_pwm_disable, +	.owner = THIS_MODULE, +}; + +static int twl_pwm_probe(struct platform_device *pdev) +{ +	struct twl_pwm_chip *twl; +	int ret; + +	twl = devm_kzalloc(&pdev->dev, sizeof(*twl), GFP_KERNEL); +	if (!twl) +		return -ENOMEM; + +	if (twl_class_is_4030()) +		twl->chip.ops = &twl4030_pwm_ops; +	else +		twl->chip.ops = &twl6030_pwm_ops; + +	twl->chip.dev = &pdev->dev; +	twl->chip.base = -1; +	twl->chip.npwm = 2; +	twl->chip.can_sleep = true; + +	mutex_init(&twl->mutex); + +	ret = pwmchip_add(&twl->chip); +	if (ret < 0) +		return ret; + +	platform_set_drvdata(pdev, twl); + +	return 0; +} + +static int twl_pwm_remove(struct platform_device *pdev) +{ +	struct twl_pwm_chip *twl = platform_get_drvdata(pdev); + +	return pwmchip_remove(&twl->chip); +} + +#ifdef CONFIG_OF +static const struct of_device_id twl_pwm_of_match[] = { +	{ .compatible = "ti,twl4030-pwm" }, +	{ .compatible = "ti,twl6030-pwm" }, +	{ }, +}; +MODULE_DEVICE_TABLE(of, twl_pwm_of_match); +#endif + +static struct platform_driver twl_pwm_driver = { +	.driver = { +		.name = "twl-pwm", +		.of_match_table = of_match_ptr(twl_pwm_of_match), +	}, +	.probe = twl_pwm_probe, +	.remove = twl_pwm_remove, +}; +module_platform_driver(twl_pwm_driver); + +MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>"); +MODULE_DESCRIPTION("PWM driver for TWL4030 and TWL6030"); +MODULE_ALIAS("platform:twl-pwm"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-vt8500.c b/drivers/pwm/pwm-vt8500.c new file mode 100644 index 00000000000..652e6b5b859 --- /dev/null +++ b/drivers/pwm/pwm-vt8500.c @@ -0,0 +1,277 @@ +/* + * drivers/pwm/pwm-vt8500.c + * + * Copyright (C) 2012 Tony Prisk <linux@prisktech.co.nz> + * Copyright (C) 2010 Alexey Charkov <alchark@gmail.com> + * + * 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 <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/pwm.h> +#include <linux/delay.h> +#include <linux/clk.h> + +#include <asm/div64.h> + +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_address.h> + +/* + * SoC architecture allocates register space for 4 PWMs but only + * 2 are currently implemented. + */ +#define VT8500_NR_PWMS	2 + +#define REG_CTRL(pwm)		(((pwm) << 4) + 0x00) +#define REG_SCALAR(pwm)		(((pwm) << 4) + 0x04) +#define REG_PERIOD(pwm)		(((pwm) << 4) + 0x08) +#define REG_DUTY(pwm)		(((pwm) << 4) + 0x0C) +#define REG_STATUS		0x40 + +#define CTRL_ENABLE		BIT(0) +#define CTRL_INVERT		BIT(1) +#define CTRL_AUTOLOAD		BIT(2) +#define CTRL_STOP_IMM		BIT(3) +#define CTRL_LOAD_PRESCALE	BIT(4) +#define CTRL_LOAD_PERIOD	BIT(5) + +#define STATUS_CTRL_UPDATE	BIT(0) +#define STATUS_SCALAR_UPDATE	BIT(1) +#define STATUS_PERIOD_UPDATE	BIT(2) +#define STATUS_DUTY_UPDATE	BIT(3) +#define STATUS_ALL_UPDATE	0x0F + +struct vt8500_chip { +	struct pwm_chip chip; +	void __iomem *base; +	struct clk *clk; +}; + +#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(struct vt8500_chip *vt8500, int nr, u8 bitmask) +{ +	int loops = msecs_to_loops(10); +	u32 mask = bitmask << (nr << 8); + +	while ((readl(vt8500->base + REG_STATUS) & mask) && --loops) +		cpu_relax(); + +	if (unlikely(!loops)) +		dev_warn(vt8500->chip.dev, "Waiting for status bits 0x%x to clear timed out\n", +			 mask); +} + +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; +	int err; +	u32 val; + +	err = clk_enable(vt8500->clk); +	if (err < 0) { +		dev_err(chip->dev, "failed to enable clock\n"); +		return err; +	} + +	c = clk_get_rate(vt8500->clk); +	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) { +		clk_disable(vt8500->clk); +		return -EINVAL; +	} + +	c = (unsigned long long)pv * duty_ns; +	do_div(c, period_ns); +	dc = c; + +	writel(prescale, vt8500->base + REG_SCALAR(pwm->hwpwm)); +	pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_SCALAR_UPDATE); + +	writel(pv, vt8500->base + REG_PERIOD(pwm->hwpwm)); +	pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_PERIOD_UPDATE); + +	writel(dc, vt8500->base + REG_DUTY(pwm->hwpwm)); +	pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_DUTY_UPDATE); + +	val = readl(vt8500->base + REG_CTRL(pwm->hwpwm)); +	val |= CTRL_AUTOLOAD; +	writel(val, vt8500->base + REG_CTRL(pwm->hwpwm)); +	pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_CTRL_UPDATE); + +	clk_disable(vt8500->clk); +	return 0; +} + +static int vt8500_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct vt8500_chip *vt8500 = to_vt8500_chip(chip); +	int err; +	u32 val; + +	err = clk_enable(vt8500->clk); +	if (err < 0) { +		dev_err(chip->dev, "failed to enable clock\n"); +		return err; +	} + +	val = readl(vt8500->base + REG_CTRL(pwm->hwpwm)); +	val |= CTRL_ENABLE; +	writel(val, vt8500->base + REG_CTRL(pwm->hwpwm)); +	pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_CTRL_UPDATE); + +	return 0; +} + +static void vt8500_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ +	struct vt8500_chip *vt8500 = to_vt8500_chip(chip); +	u32 val; + +	val = readl(vt8500->base + REG_CTRL(pwm->hwpwm)); +	val &= ~CTRL_ENABLE; +	writel(val, vt8500->base + REG_CTRL(pwm->hwpwm)); +	pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_CTRL_UPDATE); + +	clk_disable(vt8500->clk); +} + +static int vt8500_pwm_set_polarity(struct pwm_chip *chip, +				   struct pwm_device *pwm, +				   enum pwm_polarity polarity) +{ +	struct vt8500_chip *vt8500 = to_vt8500_chip(chip); +	u32 val; + +	val = readl(vt8500->base + REG_CTRL(pwm->hwpwm)); + +	if (polarity == PWM_POLARITY_INVERSED) +		val |= CTRL_INVERT; +	else +		val &= ~CTRL_INVERT; + +	writel(val, vt8500->base + REG_CTRL(pwm->hwpwm)); +	pwm_busy_wait(vt8500, pwm->hwpwm, STATUS_CTRL_UPDATE); + +	return 0; +} + +static struct pwm_ops vt8500_pwm_ops = { +	.enable = vt8500_pwm_enable, +	.disable = vt8500_pwm_disable, +	.config = vt8500_pwm_config, +	.set_polarity = vt8500_pwm_set_polarity, +	.owner = THIS_MODULE, +}; + +static const struct of_device_id vt8500_pwm_dt_ids[] = { +	{ .compatible = "via,vt8500-pwm", }, +	{ /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(of, vt8500_pwm_dt_ids); + +static int vt8500_pwm_probe(struct platform_device *pdev) +{ +	struct vt8500_chip *chip; +	struct resource *r; +	struct device_node *np = pdev->dev.of_node; +	int ret; + +	if (!np) { +		dev_err(&pdev->dev, "invalid devicetree node\n"); +		return -EINVAL; +	} + +	chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); +	if (chip == NULL) +		return -ENOMEM; + +	chip->chip.dev = &pdev->dev; +	chip->chip.ops = &vt8500_pwm_ops; +	chip->chip.of_xlate = of_pwm_xlate_with_flags; +	chip->chip.of_pwm_n_cells = 3; +	chip->chip.base = -1; +	chip->chip.npwm = VT8500_NR_PWMS; + +	chip->clk = devm_clk_get(&pdev->dev, NULL); +	if (IS_ERR(chip->clk)) { +		dev_err(&pdev->dev, "clock source not specified\n"); +		return PTR_ERR(chip->clk); +	} + +	r = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	chip->base = devm_ioremap_resource(&pdev->dev, r); +	if (IS_ERR(chip->base)) +		return PTR_ERR(chip->base); + +	ret = clk_prepare(chip->clk); +	if (ret < 0) { +		dev_err(&pdev->dev, "failed to prepare clock\n"); +		return ret; +	} + +	ret = pwmchip_add(&chip->chip); +	if (ret < 0) { +		dev_err(&pdev->dev, "failed to add PWM chip\n"); +		return ret; +	} + +	platform_set_drvdata(pdev, chip); +	return ret; +} + +static int vt8500_pwm_remove(struct platform_device *pdev) +{ +	struct vt8500_chip *chip; + +	chip = platform_get_drvdata(pdev); +	if (chip == NULL) +		return -ENODEV; + +	clk_unprepare(chip->clk); + +	return pwmchip_remove(&chip->chip); +} + +static struct platform_driver vt8500_pwm_driver = { +	.probe		= vt8500_pwm_probe, +	.remove		= vt8500_pwm_remove, +	.driver		= { +		.name	= "vt8500-pwm", +		.owner	= THIS_MODULE, +		.of_match_table = vt8500_pwm_dt_ids, +	}, +}; +module_platform_driver(vt8500_pwm_driver); + +MODULE_DESCRIPTION("VT8500 PWM Driver"); +MODULE_AUTHOR("Tony Prisk <linux@prisktech.co.nz>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/sysfs.c b/drivers/pwm/sysfs.c new file mode 100644 index 00000000000..4bd0c639e16 --- /dev/null +++ b/drivers/pwm/sysfs.c @@ -0,0 +1,347 @@ +/* + * A simple sysfs interface for the generic PWM framework + * + * Copyright (C) 2013 H Hartley Sweeten <hsweeten@visionengravers.com> + * + * Based on previous work by Lars Poeschel <poeschel@lemonage.de> + * + * 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. + */ + +#include <linux/device.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/kdev_t.h> +#include <linux/pwm.h> + +struct pwm_export { +	struct device child; +	struct pwm_device *pwm; +}; + +static struct pwm_export *child_to_pwm_export(struct device *child) +{ +	return container_of(child, struct pwm_export, child); +} + +static struct pwm_device *child_to_pwm_device(struct device *child) +{ +	struct pwm_export *export = child_to_pwm_export(child); + +	return export->pwm; +} + +static ssize_t pwm_period_show(struct device *child, +			       struct device_attribute *attr, +			       char *buf) +{ +	const struct pwm_device *pwm = child_to_pwm_device(child); + +	return sprintf(buf, "%u\n", pwm->period); +} + +static ssize_t pwm_period_store(struct device *child, +				struct device_attribute *attr, +				const char *buf, size_t size) +{ +	struct pwm_device *pwm = child_to_pwm_device(child); +	unsigned int val; +	int ret; + +	ret = kstrtouint(buf, 0, &val); +	if (ret) +		return ret; + +	ret = pwm_config(pwm, pwm->duty_cycle, val); + +	return ret ? : size; +} + +static ssize_t pwm_duty_cycle_show(struct device *child, +				   struct device_attribute *attr, +				   char *buf) +{ +	const struct pwm_device *pwm = child_to_pwm_device(child); + +	return sprintf(buf, "%u\n", pwm->duty_cycle); +} + +static ssize_t pwm_duty_cycle_store(struct device *child, +				    struct device_attribute *attr, +				    const char *buf, size_t size) +{ +	struct pwm_device *pwm = child_to_pwm_device(child); +	unsigned int val; +	int ret; + +	ret = kstrtouint(buf, 0, &val); +	if (ret) +		return ret; + +	ret = pwm_config(pwm, val, pwm->period); + +	return ret ? : size; +} + +static ssize_t pwm_enable_show(struct device *child, +			       struct device_attribute *attr, +			       char *buf) +{ +	const struct pwm_device *pwm = child_to_pwm_device(child); +	int enabled = test_bit(PWMF_ENABLED, &pwm->flags); + +	return sprintf(buf, "%d\n", enabled); +} + +static ssize_t pwm_enable_store(struct device *child, +				struct device_attribute *attr, +				const char *buf, size_t size) +{ +	struct pwm_device *pwm = child_to_pwm_device(child); +	int val, ret; + +	ret = kstrtoint(buf, 0, &val); +	if (ret) +		return ret; + +	switch (val) { +	case 0: +		pwm_disable(pwm); +		break; +	case 1: +		ret = pwm_enable(pwm); +		break; +	default: +		ret = -EINVAL; +		break; +	} + +	return ret ? : size; +} + +static ssize_t pwm_polarity_show(struct device *child, +				 struct device_attribute *attr, +				 char *buf) +{ +	const struct pwm_device *pwm = child_to_pwm_device(child); + +	return sprintf(buf, "%s\n", pwm->polarity ? "inversed" : "normal"); +} + +static ssize_t pwm_polarity_store(struct device *child, +				  struct device_attribute *attr, +				  const char *buf, size_t size) +{ +	struct pwm_device *pwm = child_to_pwm_device(child); +	enum pwm_polarity polarity; +	int ret; + +	if (sysfs_streq(buf, "normal")) +		polarity = PWM_POLARITY_NORMAL; +	else if (sysfs_streq(buf, "inversed")) +		polarity = PWM_POLARITY_INVERSED; +	else +		return -EINVAL; + +	ret = pwm_set_polarity(pwm, polarity); + +	return ret ? : size; +} + +static DEVICE_ATTR(period, 0644, pwm_period_show, pwm_period_store); +static DEVICE_ATTR(duty_cycle, 0644, pwm_duty_cycle_show, pwm_duty_cycle_store); +static DEVICE_ATTR(enable, 0644, pwm_enable_show, pwm_enable_store); +static DEVICE_ATTR(polarity, 0644, pwm_polarity_show, pwm_polarity_store); + +static struct attribute *pwm_attrs[] = { +	&dev_attr_period.attr, +	&dev_attr_duty_cycle.attr, +	&dev_attr_enable.attr, +	&dev_attr_polarity.attr, +	NULL +}; +ATTRIBUTE_GROUPS(pwm); + +static void pwm_export_release(struct device *child) +{ +	struct pwm_export *export = child_to_pwm_export(child); + +	kfree(export); +} + +static int pwm_export_child(struct device *parent, struct pwm_device *pwm) +{ +	struct pwm_export *export; +	int ret; + +	if (test_and_set_bit(PWMF_EXPORTED, &pwm->flags)) +		return -EBUSY; + +	export = kzalloc(sizeof(*export), GFP_KERNEL); +	if (!export) { +		clear_bit(PWMF_EXPORTED, &pwm->flags); +		return -ENOMEM; +	} + +	export->pwm = pwm; + +	export->child.release = pwm_export_release; +	export->child.parent = parent; +	export->child.devt = MKDEV(0, 0); +	export->child.groups = pwm_groups; +	dev_set_name(&export->child, "pwm%u", pwm->hwpwm); + +	ret = device_register(&export->child); +	if (ret) { +		clear_bit(PWMF_EXPORTED, &pwm->flags); +		kfree(export); +		return ret; +	} + +	return 0; +} + +static int pwm_unexport_match(struct device *child, void *data) +{ +	return child_to_pwm_device(child) == data; +} + +static int pwm_unexport_child(struct device *parent, struct pwm_device *pwm) +{ +	struct device *child; + +	if (!test_and_clear_bit(PWMF_EXPORTED, &pwm->flags)) +		return -ENODEV; + +	child = device_find_child(parent, pwm, pwm_unexport_match); +	if (!child) +		return -ENODEV; + +	/* for device_find_child() */ +	put_device(child); +	device_unregister(child); +	pwm_put(pwm); + +	return 0; +} + +static ssize_t pwm_export_store(struct device *parent, +				struct device_attribute *attr, +				const char *buf, size_t len) +{ +	struct pwm_chip *chip = dev_get_drvdata(parent); +	struct pwm_device *pwm; +	unsigned int hwpwm; +	int ret; + +	ret = kstrtouint(buf, 0, &hwpwm); +	if (ret < 0) +		return ret; + +	if (hwpwm >= chip->npwm) +		return -ENODEV; + +	pwm = pwm_request_from_chip(chip, hwpwm, "sysfs"); +	if (IS_ERR(pwm)) +		return PTR_ERR(pwm); + +	ret = pwm_export_child(parent, pwm); +	if (ret < 0) +		pwm_put(pwm); + +	return ret ? : len; +} +static DEVICE_ATTR(export, 0200, NULL, pwm_export_store); + +static ssize_t pwm_unexport_store(struct device *parent, +				  struct device_attribute *attr, +				  const char *buf, size_t len) +{ +	struct pwm_chip *chip = dev_get_drvdata(parent); +	unsigned int hwpwm; +	int ret; + +	ret = kstrtouint(buf, 0, &hwpwm); +	if (ret < 0) +		return ret; + +	if (hwpwm >= chip->npwm) +		return -ENODEV; + +	ret = pwm_unexport_child(parent, &chip->pwms[hwpwm]); + +	return ret ? : len; +} +static DEVICE_ATTR(unexport, 0200, NULL, pwm_unexport_store); + +static ssize_t npwm_show(struct device *parent, struct device_attribute *attr, +			 char *buf) +{ +	const struct pwm_chip *chip = dev_get_drvdata(parent); + +	return sprintf(buf, "%u\n", chip->npwm); +} +static DEVICE_ATTR_RO(npwm); + +static struct attribute *pwm_chip_attrs[] = { +	&dev_attr_export.attr, +	&dev_attr_unexport.attr, +	&dev_attr_npwm.attr, +	NULL, +}; +ATTRIBUTE_GROUPS(pwm_chip); + +static struct class pwm_class = { +	.name		= "pwm", +	.owner		= THIS_MODULE, +	.dev_groups	= pwm_chip_groups, +}; + +static int pwmchip_sysfs_match(struct device *parent, const void *data) +{ +	return dev_get_drvdata(parent) == data; +} + +void pwmchip_sysfs_export(struct pwm_chip *chip) +{ +	struct device *parent; + +	/* +	 * If device_create() fails the pwm_chip is still usable by +	 * the kernel its just not exported. +	 */ +	parent = device_create(&pwm_class, chip->dev, MKDEV(0, 0), chip, +			       "pwmchip%d", chip->base); +	if (IS_ERR(parent)) { +		dev_warn(chip->dev, +			 "device_create failed for pwm_chip sysfs export\n"); +	} +} + +void pwmchip_sysfs_unexport(struct pwm_chip *chip) +{ +	struct device *parent; + +	parent = class_find_device(&pwm_class, NULL, chip, +				   pwmchip_sysfs_match); +	if (parent) { +		/* for class_find_device() */ +		put_device(parent); +		device_unregister(parent); +	} +} + +static int __init pwm_sysfs_init(void) +{ +	return class_register(&pwm_class); +} +subsys_initcall(pwm_sysfs_init);  | 
