diff options
Diffstat (limited to 'drivers/pwm')
-rw-r--r-- | drivers/pwm/Kconfig | 28 | ||||
-rw-r--r-- | drivers/pwm/Makefile | 3 | ||||
-rw-r--r-- | drivers/pwm/core.c | 8 | ||||
-rw-r--r-- | drivers/pwm/pwm-ab8500.c | 13 | ||||
-rw-r--r-- | drivers/pwm/pwm-atmel.c | 1 | ||||
-rw-r--r-- | drivers/pwm/pwm-bcm-kona.c | 318 | ||||
-rw-r--r-- | drivers/pwm/pwm-fsl-ftm.c | 1 | ||||
-rw-r--r-- | drivers/pwm/pwm-imx.c | 5 | ||||
-rw-r--r-- | drivers/pwm/pwm-lp3943.c | 1 | ||||
-rw-r--r-- | drivers/pwm/pwm-lpss.c | 169 | ||||
-rw-r--r-- | drivers/pwm/pwm-mxs.c | 1 | ||||
-rw-r--r-- | drivers/pwm/pwm-pxa.c | 4 | ||||
-rw-r--r-- | drivers/pwm/pwm-renesas-tpu.c | 23 | ||||
-rw-r--r-- | drivers/pwm/pwm-rockchip.c | 264 | ||||
-rw-r--r-- | drivers/pwm/pwm-samsung.c | 3 | ||||
-rw-r--r-- | drivers/pwm/pwm-spear.c | 6 | ||||
-rw-r--r-- | drivers/pwm/pwm-sti.c | 418 | ||||
-rw-r--r-- | drivers/pwm/pwm-tegra.c | 4 | ||||
-rw-r--r-- | drivers/pwm/pwm-tiecap.c | 4 | ||||
-rw-r--r-- | drivers/pwm/pwm-tiehrpwm.c | 10 | ||||
-rw-r--r-- | drivers/pwm/pwm-tipwmss.c | 4 | ||||
-rw-r--r-- | drivers/pwm/pwm-twl.c | 8 | ||||
-rw-r--r-- | drivers/pwm/pwm-vt8500.c | 4 |
23 files changed, 1189 insertions, 111 deletions
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 5b34ff29ea38..b800783800a3 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -43,7 +43,7 @@ config PWM_AB8500 config PWM_ATMEL tristate "Atmel PWM support" - depends on ARCH_AT91 + depends on ARCH_AT91 || AVR32 help Generic PWM framework driver for Atmel SoC. @@ -62,6 +62,15 @@ config PWM_ATMEL_TCB 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 @@ -197,6 +206,13 @@ config PWM_RENESAS_TPU To compile this driver as a module, choose M here: the module will be called pwm-renesas-tpu. +config PWM_ROCKCHIP + tristate "Rockchip PWM support" + depends on ARCH_ROCKCHIP + help + Generic PWM framework driver for the PWM controller found on + Rockchip SoCs. + config PWM_SAMSUNG tristate "Samsung PWM support" depends on PLAT_SAMSUNG @@ -217,6 +233,16 @@ config PWM_SPEAR To compile this driver as a module, choose M here: the module will be called pwm-spear. +config PWM_STI + tristate "STiH4xx PWM support" + depends on ARCH_STI + depends on OF + help + Generic PWM framework driver for STiH4xx SoCs. + + To compile this driver as a module, choose M here: the module + will be called pwm-sti. + config PWM_TEGRA tristate "NVIDIA Tegra PWM support" depends on ARCH_TEGRA diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index e57d2c38a794..f8c577d41091 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -3,6 +3,7 @@ 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 @@ -17,8 +18,10 @@ 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_ROCKCHIP) += pwm-rockchip.o obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o +obj-$(CONFIG_PWM_STI) += pwm-sti.o obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o obj-$(CONFIG_PWM_TIECAP) += pwm-tiecap.o obj-$(CONFIG_PWM_TIEHRPWM) += pwm-tiehrpwm.o diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index a80471399c20..4b66bf09ee55 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -661,10 +661,16 @@ struct pwm_device *pwm_get(struct device *dev, const char *con_id) } } + 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); - mutex_unlock(&pwm_lookup_lock); return pwm; } diff --git a/drivers/pwm/pwm-ab8500.c b/drivers/pwm/pwm-ab8500.c index 1d07a6f99375..4c07a8420b37 100644 --- a/drivers/pwm/pwm-ab8500.c +++ b/drivers/pwm/pwm-ab8500.c @@ -20,10 +20,6 @@ #define AB8500_PWM_OUT_CTRL2_REG 0x61 #define AB8500_PWM_OUT_CTRL7_REG 0x66 -/* backlight driver constants */ -#define ENABLE_PWM 1 -#define DISABLE_PWM 0 - struct ab8500_pwm_chip { struct pwm_chip chip; }; @@ -64,7 +60,7 @@ static int ab8500_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) ret = abx500_mask_and_set_register_interruptible(chip->dev, AB8500_MISC, AB8500_PWM_OUT_CTRL7_REG, - 1 << (chip->base - 1), ENABLE_PWM); + 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); @@ -77,11 +73,10 @@ static void ab8500_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) ret = abx500_mask_and_set_register_interruptible(chip->dev, AB8500_MISC, AB8500_PWM_OUT_CTRL7_REG, - 1 << (chip->base - 1), DISABLE_PWM); + 1 << (chip->base - 1), 0); if (ret < 0) dev_err(chip->dev, "%s: Failed to disable PWM, Error %d\n", pwm->label, ret); - return; } static const struct pwm_ops ab8500_pwm_ops = { @@ -101,10 +96,8 @@ static int ab8500_pwm_probe(struct platform_device *pdev) * device which is required for ab8500 read and write */ ab8500 = devm_kzalloc(&pdev->dev, sizeof(*ab8500), GFP_KERNEL); - if (ab8500 == NULL) { - dev_err(&pdev->dev, "failed to allocate memory\n"); + if (ab8500 == NULL) return -ENOMEM; - } ab8500->chip.dev = &pdev->dev; ab8500->chip.ops = &ab8500_pwm_ops; diff --git a/drivers/pwm/pwm-atmel.c b/drivers/pwm/pwm-atmel.c index 0adc952cc4ef..6e700a541ca3 100644 --- a/drivers/pwm/pwm-atmel.c +++ b/drivers/pwm/pwm-atmel.c @@ -357,6 +357,7 @@ static int atmel_pwm_probe(struct platform_device *pdev) 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); diff --git a/drivers/pwm/pwm-bcm-kona.c b/drivers/pwm/pwm-bcm-kona.c new file mode 100644 index 000000000000..02bc048892a9 --- /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-fsl-ftm.c b/drivers/pwm/pwm-fsl-ftm.c index 420169e96b5f..a18bc8fea385 100644 --- a/drivers/pwm/pwm-fsl-ftm.c +++ b/drivers/pwm/pwm-fsl-ftm.c @@ -454,6 +454,7 @@ static int fsl_pwm_probe(struct platform_device *pdev) 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) { diff --git a/drivers/pwm/pwm-imx.c b/drivers/pwm/pwm-imx.c index cc4773344874..5449d9150d40 100644 --- a/drivers/pwm/pwm-imx.c +++ b/drivers/pwm/pwm-imx.c @@ -241,10 +241,8 @@ static int imx_pwm_probe(struct platform_device *pdev) return -ENODEV; imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL); - if (imx == NULL) { - dev_err(&pdev->dev, "failed to allocate memory\n"); + if (imx == NULL) return -ENOMEM; - } imx->clk_per = devm_clk_get(&pdev->dev, "per"); if (IS_ERR(imx->clk_per)) { @@ -264,6 +262,7 @@ static int imx_pwm_probe(struct platform_device *pdev) imx->chip.dev = &pdev->dev; imx->chip.base = -1; imx->chip.npwm = 1; + imx->chip.can_sleep = true; r = platform_get_resource(pdev, IORESOURCE_MEM, 0); imx->mmio_base = devm_ioremap_resource(&pdev->dev, r); diff --git a/drivers/pwm/pwm-lp3943.c b/drivers/pwm/pwm-lp3943.c index a40b9c34e9ff..2c39b0e50fa4 100644 --- a/drivers/pwm/pwm-lp3943.c +++ b/drivers/pwm/pwm-lp3943.c @@ -278,6 +278,7 @@ static int lp3943_pwm_probe(struct platform_device *pdev) 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); diff --git a/drivers/pwm/pwm-lpss.c b/drivers/pwm/pwm-lpss.c index 449e372050a0..4df994f72d96 100644 --- a/drivers/pwm/pwm-lpss.c +++ b/drivers/pwm/pwm-lpss.c @@ -6,6 +6,7 @@ * 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 @@ -13,12 +14,14 @@ */ #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) @@ -33,7 +36,16 @@ 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) @@ -55,7 +67,7 @@ static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm, /* The equation is: base_unit = ((freq / c) * 65536) + correction */ base_unit = freq * 65536; - c = clk_get_rate(lpwm->clk); + c = lpwm->clk_rate; if (!c) return -EINVAL; @@ -83,11 +95,6 @@ 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); @@ -102,8 +109,6 @@ static void pwm_lpss_disable(struct pwm_chip *chip, struct pwm_device *pwm) 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 = { @@ -113,52 +118,38 @@ static const struct pwm_ops pwm_lpss_ops = { .owner = THIS_MODULE, }; -static const struct acpi_device_id pwm_lpss_acpi_match[] = { - { "80860F09", 0 }, - { }, -}; -MODULE_DEVICE_TABLE(acpi, pwm_lpss_acpi_match); - -static int pwm_lpss_probe(struct platform_device *pdev) +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; - struct resource *r; int ret; - lpwm = devm_kzalloc(&pdev->dev, sizeof(*lpwm), GFP_KERNEL); + lpwm = devm_kzalloc(dev, sizeof(*lpwm), GFP_KERNEL); if (!lpwm) - return -ENOMEM; - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + return ERR_PTR(-ENOMEM); - lpwm->regs = devm_ioremap_resource(&pdev->dev, r); + lpwm->regs = devm_ioremap_resource(dev, r); if (IS_ERR(lpwm->regs)) - return PTR_ERR(lpwm->regs); - - lpwm->clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(lpwm->clk)) { - dev_err(&pdev->dev, "failed to get PWM clock\n"); - return PTR_ERR(lpwm->clk); - } + return ERR_CAST(lpwm->regs); - lpwm->chip.dev = &pdev->dev; + lpwm->clk_rate = info->clk_rate; + 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(&pdev->dev, "failed to add PWM chip: %d\n", ret); - return ret; + dev_err(dev, "failed to add PWM chip: %d\n", ret); + return ERR_PTR(ret); } - platform_set_drvdata(pdev, lpwm); - return 0; + return lpwm; } -static int pwm_lpss_remove(struct platform_device *pdev) +static int pwm_lpss_remove(struct pwm_lpss_chip *lpwm) { - struct pwm_lpss_chip *lpwm = platform_get_drvdata(pdev); u32 ctrl; ctrl = readl(lpwm->regs + PWM); @@ -167,15 +158,111 @@ static int pwm_lpss_remove(struct platform_device *pdev) return pwmchip_remove(&lpwm->chip); } -static struct platform_driver pwm_lpss_driver = { +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) +{ + const struct pwm_lpss_boardinfo *info; + const struct acpi_device_id *id; + struct pwm_lpss_chip *lpwm; + struct resource *r; + + id = acpi_match_device(pdev->dev.driver->acpi_match_table, &pdev->dev); + if (!id) + return -ENODEV; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + info = (struct pwm_lpss_boardinfo *)id->driver_data; + lpwm = pwm_lpss_probe(&pdev->dev, r, info); + 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", (unsigned long)&byt_info }, + { }, +}; +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, - .remove = pwm_lpss_remove, + .probe = pwm_lpss_probe_platform, + .remove = pwm_lpss_remove_platform, }; -module_platform_driver(pwm_lpss_driver); + +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>"); diff --git a/drivers/pwm/pwm-mxs.c b/drivers/pwm/pwm-mxs.c index 9475bc7a6f97..4f1bb4e0a426 100644 --- a/drivers/pwm/pwm-mxs.c +++ b/drivers/pwm/pwm-mxs.c @@ -147,6 +147,7 @@ static int mxs_pwm_probe(struct platform_device *pdev) 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); diff --git a/drivers/pwm/pwm-pxa.c b/drivers/pwm/pwm-pxa.c index cd356d870244..0b312ec420b6 100644 --- a/drivers/pwm/pwm-pxa.c +++ b/drivers/pwm/pwm-pxa.c @@ -179,10 +179,8 @@ static int pwm_probe(struct platform_device *pdev) return -EINVAL; pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); - if (pwm == NULL) { - dev_err(&pdev->dev, "failed to allocate memory\n"); + if (pwm == NULL) return -ENOMEM; - } pwm->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(pwm->clk)) diff --git a/drivers/pwm/pwm-renesas-tpu.c b/drivers/pwm/pwm-renesas-tpu.c index aff6ba9b49e7..3b71b42e89d5 100644 --- a/drivers/pwm/pwm-renesas-tpu.c +++ b/drivers/pwm/pwm-renesas-tpu.c @@ -21,13 +21,14 @@ #include <linux/module.h> #include <linux/mutex.h> #include <linux/of.h> -#include <linux/platform_data/pwm-renesas-tpu.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 */ @@ -87,7 +88,6 @@ struct tpu_pwm_device { struct tpu_device { struct platform_device *pdev; - enum pwm_polarity polarities[TPU_CHANNEL_MAX]; struct pwm_chip chip; spinlock_t lock; @@ -229,7 +229,7 @@ static int tpu_pwm_request(struct pwm_chip *chip, struct pwm_device *_pwm) pwm->tpu = tpu; pwm->channel = _pwm->hwpwm; - pwm->polarity = tpu->polarities[pwm->channel]; + pwm->polarity = PWM_POLARITY_NORMAL; pwm->prescaler = 0; pwm->period = 0; pwm->duty = 0; @@ -388,16 +388,6 @@ static const struct pwm_ops tpu_pwm_ops = { * Probe and remove */ -static void tpu_parse_pdata(struct tpu_device *tpu) -{ - struct tpu_pwm_platform_data *pdata = tpu->pdev->dev.platform_data; - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(tpu->polarities); ++i) - tpu->polarities[i] = pdata ? pdata->channels[i].polarity - : PWM_POLARITY_NORMAL; -} - static int tpu_probe(struct platform_device *pdev) { struct tpu_device *tpu; @@ -405,17 +395,12 @@ static int tpu_probe(struct platform_device *pdev) int ret; tpu = devm_kzalloc(&pdev->dev, sizeof(*tpu), GFP_KERNEL); - if (tpu == NULL) { - dev_err(&pdev->dev, "failed to allocate driver data\n"); + if (tpu == NULL) return -ENOMEM; - } spin_lock_init(&tpu->lock); tpu->pdev = pdev; - /* Initialize device configuration from platform data. */ - tpu_parse_pdata(tpu); - /* Map memory, get clock and pin control. */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); tpu->base = devm_ioremap_resource(&pdev->dev, res); diff --git a/drivers/pwm/pwm-rockchip.c b/drivers/pwm/pwm-rockchip.c new file mode 100644 index 000000000000..bdd8644c01cf --- /dev/null +++ b/drivers/pwm/pwm-rockchip.c @@ -0,0 +1,264 @@ +/* + * PWM driver for Rockchip SoCs + * + * Copyright (C) 2014 Beniamino Galvani <b.galvani@gmail.com> + * Copyright (C) 2014 ROCKCHIP, Inc. + * + * 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/clk.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/time.h> + +#define PWM_CTRL_TIMER_EN (1 << 0) +#define PWM_CTRL_OUTPUT_EN (1 << 3) + +#define PWM_ENABLE (1 << 0) +#define PWM_CONTINUOUS (1 << 1) +#define PWM_DUTY_POSITIVE (1 << 3) +#define PWM_INACTIVE_NEGATIVE (0 << 4) +#define PWM_OUTPUT_LEFT (0 << 5) +#define PWM_LP_DISABLE (0 << 8) + +struct rockchip_pwm_chip { + struct pwm_chip chip; + struct clk *clk; + const struct rockchip_pwm_data *data; + void __iomem *base; +}; + +struct rockchip_pwm_regs { + unsigned long duty; + unsigned long period; + unsigned long cntr; + unsigned long ctrl; +}; + +struct rockchip_pwm_data { + struct rockchip_pwm_regs regs; + unsigned int prescaler; + + void (*set_enable)(struct pwm_chip *chip, bool enable); +}; + +static inline struct rockchip_pwm_chip *to_rockchip_pwm_chip(struct pwm_chip *c) +{ + return container_of(c, struct rockchip_pwm_chip, chip); +} + +static void rockchip_pwm_set_enable_v1(struct pwm_chip *chip, bool enable) +{ + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + u32 enable_conf = PWM_CTRL_OUTPUT_EN | PWM_CTRL_TIMER_EN; + u32 val; + + val = readl_relaxed(pc->base + pc->data->regs.ctrl); + + if (enable) + val |= enable_conf; + else + val &= ~enable_conf; + + writel_relaxed(val, pc->base + pc->data->regs.ctrl); +} + +static void rockchip_pwm_set_enable_v2(struct pwm_chip *chip, bool enable) +{ + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + u32 enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE | + PWM_CONTINUOUS | PWM_DUTY_POSITIVE | + PWM_INACTIVE_NEGATIVE; + u32 val; + + val = readl_relaxed(pc->base + pc->data->regs.ctrl); + + if (enable) + val |= enable_conf; + else + val &= ~enable_conf; + + writel_relaxed(val, pc->base + pc->data->regs.ctrl); +} + +static int rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + unsigned long period, duty; + u64 clk_rate, div; + int ret; + + clk_rate = clk_get_rate(pc->clk); + + /* + * Since period and duty cycle registers have a width of 32 + * bits, every possible input period can be obtained using the + * default prescaler value for all practical clock rate values. + */ + div = clk_rate * period_ns; + do_div(div, pc->data->prescaler * NSEC_PER_SEC); + period = div; + + div = clk_rate * duty_ns; + do_div(div, pc->data->prescaler * NSEC_PER_SEC); + duty = div; + + ret = clk_enable(pc->clk); + if (ret) + return ret; + + writel(period, pc->base + pc->data->regs.period); + writel(duty, pc->base + pc->data->regs.duty); + writel(0, pc->base + pc->data->regs.cntr); + + clk_disable(pc->clk); + + return 0; +} + +static int rockchip_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + int ret; + + ret = clk_enable(pc->clk); + if (ret) + return ret; + + pc->data->set_enable(chip, true); + + return 0; +} + +static void rockchip_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + + pc->data->set_enable(chip, false); + + clk_disable(pc->clk); +} + +static const struct pwm_ops rockchip_pwm_ops = { + .config = rockchip_pwm_config, + .enable = rockchip_pwm_enable, + .disable = rockchip_pwm_disable, + .owner = THIS_MODULE, +}; + +static const struct rockchip_pwm_data pwm_data_v1 = { + .regs = { + .duty = 0x04, + .period = 0x08, + .cntr = 0x00, + .ctrl = 0x0c, + }, + .prescaler = 2, + .set_enable = rockchip_pwm_set_enable_v1, +}; + +static const struct rockchip_pwm_data pwm_data_v2 = { + .regs = { + .duty = 0x08, + .period = 0x04, + .cntr = 0x00, + .ctrl = 0x0c, + }, + .prescaler = 1, + .set_enable = rockchip_pwm_set_enable_v2, +}; + +static const struct rockchip_pwm_data pwm_data_vop = { + .regs = { + .duty = 0x08, + .period = 0x04, + .cntr = 0x0c, + .ctrl = 0x00, + }, + .prescaler = 1, + .set_enable = rockchip_pwm_set_enable_v2, +}; + +static const struct of_device_id rockchip_pwm_dt_ids[] = { + { .compatible = "rockchip,rk2928-pwm", .data = &pwm_data_v1}, + { .compatible = "rockchip,rk3288-pwm", .data = &pwm_data_v2}, + { .compatible = "rockchip,vop-pwm", .data = &pwm_data_vop}, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rockchip_pwm_dt_ids); + +static int rockchip_pwm_probe(struct platform_device *pdev) +{ + const struct of_device_id *id; + struct rockchip_pwm_chip *pc; + struct resource *r; + int ret; + + id = of_match_device(rockchip_pwm_dt_ids, &pdev->dev); + if (!id) + return -EINVAL; + + pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); + if (!pc) + return -ENOMEM; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pc->base = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(pc->base)) + return PTR_ERR(pc->base); + + pc->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(pc->clk)) + return PTR_ERR(pc->clk); + + ret = clk_prepare(pc->clk); + if (ret) + return ret; + + platform_set_drvdata(pdev, pc); + + pc->data = id->data; + pc->chip.dev = &pdev->dev; + pc->chip.ops = &rockchip_pwm_ops; + pc->chip.base = -1; + pc->chip.npwm = 1; + + 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 rockchip_pwm_remove(struct platform_device *pdev) +{ + struct rockchip_pwm_chip *pc = platform_get_drvdata(pdev); + + clk_unprepare(pc->clk); + + return pwmchip_remove(&pc->chip); +} + +static struct platform_driver rockchip_pwm_driver = { + .driver = { + .name = "rockchip-pwm", + .of_match_table = rockchip_pwm_dt_ids, + }, + .probe = rockchip_pwm_probe, + .remove = rockchip_pwm_remove, +}; +module_platform_driver(rockchip_pwm_driver); + +MODULE_AUTHOR("Beniamino Galvani <b.galvani@gmail.com>"); +MODULE_DESCRIPTION("Rockchip SoC PWM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-samsung.c b/drivers/pwm/pwm-samsung.c index d66529a995a1..ba6b650cf8dc 100644 --- a/drivers/pwm/pwm-samsung.c +++ b/drivers/pwm/pwm-samsung.c @@ -335,9 +335,6 @@ static int pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm, writel(tcnt, our_chip->base + REG_TCNTB(pwm->hwpwm)); writel(tcmp, our_chip->base + REG_TCMPB(pwm->hwpwm)); - if (test_bit(PWMF_ENABLED, &pwm->flags)) - pwm_samsung_enable(chip, pwm); - chan->period_ns = period_ns; chan->tin_ns = tin_ns; chan->duty_ns = duty_ns; diff --git a/drivers/pwm/pwm-spear.c b/drivers/pwm/pwm-spear.c index cb2d4f0f9711..6fd93e6a4122 100644 --- a/drivers/pwm/pwm-spear.c +++ b/drivers/pwm/pwm-spear.c @@ -179,10 +179,8 @@ static int spear_pwm_probe(struct platform_device *pdev) u32 val; pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); - if (!pc) { - dev_err(&pdev->dev, "failed to allocate memory\n"); + if (!pc) return -ENOMEM; - } r = platform_get_resource(pdev, IORESOURCE_MEM, 0); pc->mmio_base = devm_ioremap_resource(&pdev->dev, r); @@ -222,7 +220,7 @@ static int spear_pwm_probe(struct platform_device *pdev) } ret = pwmchip_add(&pc->chip); - if (!ret) { + if (ret < 0) { clk_unprepare(pc->clk); dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); } diff --git a/drivers/pwm/pwm-sti.c b/drivers/pwm/pwm-sti.c new file mode 100644 index 000000000000..b95115cdaea7 --- /dev/null +++ b/drivers/pwm/pwm-sti.c @@ -0,0 +1,418 @@ +/* + * PWM device driver for ST SoCs. + * Author: Ajit Pal Singh <ajitpal.singh@st.com> + * + * Copyright (C) 2013-2014 STMicroelectronics (R&D) Limited + * + * 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/math64.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/time.h> + +#define STI_DS_REG(ch) (4 * (ch)) /* Channel's Duty Cycle register */ +#define STI_PWMCR 0x50 /* Control/Config register */ +#define STI_INTEN 0x54 /* Interrupt Enable/Disable register */ +#define PWM_PRESCALE_LOW_MASK 0x0f +#define PWM_PRESCALE_HIGH_MASK 0xf0 + +/* Regfield IDs */ +enum { + PWMCLK_PRESCALE_LOW, + PWMCLK_PRESCALE_HIGH, + PWM_EN, + PWM_INT_EN, + + /* Keep last */ + MAX_REGFIELDS +}; + +struct sti_pwm_compat_data { + const struct reg_field *reg_fields; + unsigned int num_chan; + unsigned int max_pwm_cnt; + unsigned int max_prescale; +}; + +struct sti_pwm_chip { + struct device *dev; + struct clk *clk; + unsigned long clk_rate; + struct regmap *regmap; + struct sti_pwm_compat_data *cdata; + struct regmap_field *prescale_low; + struct regmap_field *prescale_high; + struct regmap_field *pwm_en; + struct regmap_field *pwm_int_en; + struct pwm_chip chip; + struct pwm_device *cur; + unsigned int en_count; + struct mutex sti_pwm_lock; /* To sync between enable/disable calls */ + void __iomem *mmio; +}; + +static const struct reg_field sti_pwm_regfields[MAX_REGFIELDS] = { + [PWMCLK_PRESCALE_LOW] = REG_FIELD(STI_PWMCR, 0, 3), + [PWMCLK_PRESCALE_HIGH] = REG_FIELD(STI_PWMCR, 11, 14), + [PWM_EN] = REG_FIELD(STI_PWMCR, 9, 9), + [PWM_INT_EN] = REG_FIELD(STI_INTEN, 0, 0), +}; + +static inline struct sti_pwm_chip *to_sti_pwmchip(struct pwm_chip *chip) +{ + return container_of(chip, struct sti_pwm_chip, chip); +} + +/* + * Calculate the prescaler value corresponding to the period. + */ +static int sti_pwm_get_prescale(struct sti_pwm_chip *pc, unsigned long period, + unsigned int *prescale) +{ + struct sti_pwm_compat_data *cdata = pc->cdata; + unsigned long val; + unsigned int ps; + + /* + * prescale = ((period_ns * clk_rate) / (10^9 * (max_pwm_count + 1)) - 1 + */ + val = NSEC_PER_SEC / pc->clk_rate; + val *= cdata->max_pwm_cnt + 1; + + if (period % val) { + return -EINVAL; + } else { + ps = period / val - 1; + if (ps > cdata->max_prescale) + return -EINVAL; + } + *prescale = ps; + + return 0; +} + +/* Calculate the number of PWM devices configured with a period. */ +static unsigned int sti_pwm_count_configured(struct pwm_chip *chip) +{ + struct pwm_device *pwm; + unsigned int ncfg = 0; + unsigned int i; + + for (i = 0; i < chip->npwm; i++) { + pwm = &chip->pwms[i]; + if (test_bit(PWMF_REQUESTED, &pwm->flags)) { + if (pwm_get_period(pwm)) + ncfg++; + } + } + + return ncfg; +} + +/* + * For STiH4xx PWM IP, the PWM period is fixed to 256 local clock cycles. + * The only way to change the period (apart from changing the PWM input clock) + * is to change the PWM clock prescaler. + * The prescaler is of 8 bits, so 256 prescaler values and hence + * 256 possible period values are supported (for a particular clock rate). + * The requested period will be applied only if it matches one of these + * 256 values. + */ +static int sti_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct sti_pwm_chip *pc = to_sti_pwmchip(chip); + struct sti_pwm_compat_data *cdata = pc->cdata; + struct pwm_device *cur = pc->cur; + struct device *dev = pc->dev; + unsigned int prescale = 0, pwmvalx; + int ret; + unsigned int ncfg; + bool period_same = false; + + ncfg = sti_pwm_count_configured(chip); + if (ncfg) + period_same = (period_ns == pwm_get_period(cur)); + + /* Allow configuration changes if one of the + * following conditions satisfy. + * 1. No channels have been configured. + * 2. Only one channel has been configured and the new request + * is for the same channel. + * 3. Only one channel has been configured and the new request is + * for a new channel and period of the new channel is same as + * the current configured period. + * 4. More than one channels are configured and period of the new + * requestis the same as the current period. + */ + if (!ncfg || + ((ncfg == 1) && (pwm->hwpwm == cur->hwpwm)) || + ((ncfg == 1) && (pwm->hwpwm != cur->hwpwm) && period_same) || + ((ncfg > 1) && period_same)) { + /* Enable clock before writing to PWM registers. */ + ret = clk_enable(pc->clk); + if (ret) + return ret; + + if (!period_same) { + ret = sti_pwm_get_prescale(pc, period_ns, &prescale); + if (ret) + goto clk_dis; + + ret = + regmap_field_write(pc->prescale_low, + prescale & PWM_PRESCALE_LOW_MASK); + if (ret) + goto clk_dis; + + ret = + regmap_field_write(pc->prescale_high, + (prescale & PWM_PRESCALE_HIGH_MASK) >> 4); + if (ret) + goto clk_dis; + } + + /* + * When PWMVal == 0, PWM pulse = 1 local clock cycle. + * When PWMVal == max_pwm_count, + * PWM pulse = (max_pwm_count + 1) local cycles, + * that is continuous pulse: signal never goes low. + */ + pwmvalx = cdata->max_pwm_cnt * duty_ns / period_ns; + + ret = regmap_write(pc->regmap, STI_DS_REG(pwm->hwpwm), pwmvalx); + if (ret) + goto clk_dis; + + ret = regmap_field_write(pc->pwm_int_en, 0); + + pc->cur = pwm; + + dev_dbg(dev, "prescale:%u, period:%i, duty:%i, pwmvalx:%u\n", + prescale, period_ns, duty_ns, pwmvalx); + } else { + return -EINVAL; + } + +clk_dis: + clk_disable(pc->clk); + return ret; +} + +static int sti_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct sti_pwm_chip *pc = to_sti_pwmchip(chip); + struct device *dev = pc->dev; + int ret = 0; + + /* + * Since we have a common enable for all PWM channels, + * do not enable if already enabled. + */ + mutex_lock(&pc->sti_pwm_lock); + if (!pc->en_count) { + ret = clk_enable(pc->clk); + if (ret) + goto out; + + ret = regmap_field_write(pc->pwm_en, 1); + if (ret) { + dev_err(dev, "failed to enable PWM device:%d\n", + pwm->hwpwm); + goto out; + } + } + pc->en_count++; +out: + mutex_unlock(&pc->sti_pwm_lock); + return ret; +} + +static void sti_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct sti_pwm_chip *pc = to_sti_pwmchip(chip); + + mutex_lock(&pc->sti_pwm_lock); + if (--pc->en_count) { + mutex_unlock(&pc->sti_pwm_lock); + return; + } + regmap_field_write(pc->pwm_en, 0); + + clk_disable(pc->clk); + mutex_unlock(&pc->sti_pwm_lock); +} + +static const struct pwm_ops sti_pwm_ops = { + .config = sti_pwm_config, + .enable = sti_pwm_enable, + .disable = sti_pwm_disable, + .owner = THIS_MODULE, +}; + +static int sti_pwm_probe_dt(struct sti_pwm_chip *pc) +{ + struct device *dev = pc->dev; + const struct reg_field *reg_fields; + struct device_node *np = dev->of_node; + struct sti_pwm_compat_data *cdata = pc->cdata; + u32 num_chan; + + of_property_read_u32(np, "st,pwm-num-chan", &num_chan); + if (num_chan) + cdata->num_chan = num_chan; + + reg_fields = cdata->reg_fields; + + pc->prescale_low = devm_regmap_field_alloc(dev, pc->regmap, + reg_fields[PWMCLK_PRESCALE_LOW]); + if (IS_ERR(pc->prescale_low)) + return PTR_ERR(pc->prescale_low); + + pc->prescale_high = devm_regmap_field_alloc(dev, pc->regmap, + reg_fields[PWMCLK_PRESCALE_HIGH]); + if (IS_ERR(pc->prescale_high)) + return PTR_ERR(pc->prescale_high); + + pc->pwm_en = devm_regmap_field_alloc(dev, pc->regmap, + reg_fields[PWM_EN]); + if (IS_ERR(pc->pwm_en)) + return PTR_ERR(pc->pwm_en); + + pc->pwm_int_en = devm_regmap_field_alloc(dev, pc->regmap, + reg_fields[PWM_INT_EN]); + if (IS_ERR(pc->pwm_int_en)) + return PTR_ERR(pc->pwm_int_en); + + return 0; +} + +static const struct regmap_config sti_pwm_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +static int sti_pwm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sti_pwm_compat_data *cdata; + struct sti_pwm_chip *pc; + struct resource *res; + int ret; + + pc = devm_kzalloc(dev, sizeof(*pc), GFP_KERNEL); + if (!pc) + return -ENOMEM; + + cdata = devm_kzalloc(dev, sizeof(*cdata), GFP_KERNEL); + if (!cdata) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + pc->mmio = devm_ioremap_resource(dev, res); + if (IS_ERR(pc->mmio)) + return PTR_ERR(pc->mmio); + + pc->regmap = devm_regmap_init_mmio(dev, pc->mmio, + &sti_pwm_regmap_config); + if (IS_ERR(pc->regmap)) + return PTR_ERR(pc->regmap); + + /* + * Setup PWM data with default values: some values could be replaced + * with specific ones provided from Device Tree. + */ + cdata->reg_fields = &sti_pwm_regfields[0]; + cdata->max_prescale = 0xff; + cdata->max_pwm_cnt = 255; + cdata->num_chan = 1; + + pc->cdata = cdata; + pc->dev = dev; + pc->en_count = 0; + mutex_init(&pc->sti_pwm_lock); + + ret = sti_pwm_probe_dt(pc); + if (ret) + return ret; + + pc->clk = of_clk_get_by_name(dev->of_node, "pwm"); + if (IS_ERR(pc->clk)) { + dev_err(dev, "failed to get PWM clock\n"); + return PTR_ERR(pc->clk); + } + + pc->clk_rate = clk_get_rate(pc->clk); + if (!pc->clk_rate) { + dev_err(dev, "failed to get clock rate\n"); + return -EINVAL; + } + + ret = clk_prepare(pc->clk); + if (ret) { + dev_err(dev, "failed to prepare clock\n"); + return ret; + } + + pc->chip.dev = dev; + pc->chip.ops = &sti_pwm_ops; + pc->chip.base = -1; + pc->chip.npwm = pc->cdata->num_chan; + pc->chip.can_sleep = true; + + ret = pwmchip_add(&pc->chip); + if (ret < 0) { + clk_unprepare(pc->clk); + return ret; + } + + platform_set_drvdata(pdev, pc); + + return 0; +} + +static int sti_pwm_remove(struct platform_device *pdev) +{ + struct sti_pwm_chip *pc = platform_get_drvdata(pdev); + unsigned int i; + + for (i = 0; i < pc->cdata->num_chan; i++) + pwm_disable(&pc->chip.pwms[i]); + + clk_unprepare(pc->clk); + + return pwmchip_remove(&pc->chip); +} + +static const struct of_device_id sti_pwm_of_match[] = { + { .compatible = "st,sti-pwm", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, sti_pwm_of_match); + +static struct platform_driver sti_pwm_driver = { + .driver = { + .name = "sti-pwm", + .of_match_table = sti_pwm_of_match, + }, + .probe = sti_pwm_probe, + .remove = sti_pwm_remove, +}; +module_platform_driver(sti_pwm_driver); + +MODULE_AUTHOR("Ajit Pal Singh <ajitpal.singh@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics ST PWM driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c index 74298c561c4e..61d86b9498ca 100644 --- a/drivers/pwm/pwm-tegra.c +++ b/drivers/pwm/pwm-tegra.c @@ -173,10 +173,8 @@ static int tegra_pwm_probe(struct platform_device *pdev) int ret; pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); - if (!pwm) { - dev_err(&pdev->dev, "failed to allocate memory\n"); + if (!pwm) return -ENOMEM; - } pwm->dev = &pdev->dev; diff --git a/drivers/pwm/pwm-tiecap.c b/drivers/pwm/pwm-tiecap.c index 032092c7a6ae..74efbe7f20c3 100644 --- a/drivers/pwm/pwm-tiecap.c +++ b/drivers/pwm/pwm-tiecap.c @@ -209,10 +209,8 @@ static int ecap_pwm_probe(struct platform_device *pdev) u16 status; pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); - if (!pc) { - dev_err(&pdev->dev, "failed to allocate memory\n"); + if (!pc) return -ENOMEM; - } clk = devm_clk_get(&pdev->dev, "fck"); if (IS_ERR(clk)) { diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c index aee4471424d1..cb75133085a8 100644 --- a/drivers/pwm/pwm-tiehrpwm.c +++ b/drivers/pwm/pwm-tiehrpwm.c @@ -138,12 +138,12 @@ static inline struct ehrpwm_pwm_chip *to_ehrpwm_pwm_chip(struct pwm_chip *chip) return container_of(chip, struct ehrpwm_pwm_chip, chip); } -static u16 ehrpwm_read(void __iomem *base, int offset) +static inline u16 ehrpwm_read(void __iomem *base, int offset) { return readw(base + offset); } -static void ehrpwm_write(void __iomem *base, int offset, unsigned int val) +static inline void ehrpwm_write(void __iomem *base, int offset, unsigned int val) { writew(val & 0xFFFF, base + offset); } @@ -440,10 +440,8 @@ static int ehrpwm_pwm_probe(struct platform_device *pdev) u16 status; pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); - if (!pc) { - dev_err(&pdev->dev, "failed to allocate memory\n"); + if (!pc) return -ENOMEM; - } clk = devm_clk_get(&pdev->dev, "fck"); if (IS_ERR(clk)) { @@ -531,6 +529,7 @@ static int ehrpwm_pwm_remove(struct platform_device *pdev) 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); @@ -557,7 +556,6 @@ static void ehrpwm_pwm_restore_context(struct ehrpwm_pwm_chip *pc) ehrpwm_write(pc->mmio_base, TBCTL, pc->ctx.tbctl); } -#ifdef CONFIG_PM_SLEEP static int ehrpwm_pwm_suspend(struct device *dev) { struct ehrpwm_pwm_chip *pc = dev_get_drvdata(dev); diff --git a/drivers/pwm/pwm-tipwmss.c b/drivers/pwm/pwm-tipwmss.c index 3b119bc2c3c6..67481dc6da3f 100644 --- a/drivers/pwm/pwm-tipwmss.c +++ b/drivers/pwm/pwm-tipwmss.c @@ -62,10 +62,8 @@ static int pwmss_probe(struct platform_device *pdev) 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"); + if (!info) return -ENOMEM; - } mutex_init(&info->pwmss_lock); diff --git a/drivers/pwm/pwm-twl.c b/drivers/pwm/pwm-twl.c index b99a50e626a6..04f76725d591 100644 --- a/drivers/pwm/pwm-twl.c +++ b/drivers/pwm/pwm-twl.c @@ -265,14 +265,6 @@ static void twl6030_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, val, TWL6030_TOGGLE3_REG); if (ret < 0) { - dev_err(chip->dev, "%s: Failed to read TOGGLE3\n", pwm->label); - goto out; - } - - 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; } diff --git a/drivers/pwm/pwm-vt8500.c b/drivers/pwm/pwm-vt8500.c index 323125abf3f4..652e6b5b859b 100644 --- a/drivers/pwm/pwm-vt8500.c +++ b/drivers/pwm/pwm-vt8500.c @@ -211,10 +211,8 @@ static int vt8500_pwm_probe(struct platform_device *pdev) } chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); - if (chip == NULL) { - dev_err(&pdev->dev, "failed to allocate memory\n"); + if (chip == NULL) return -ENOMEM; - } chip->chip.dev = &pdev->dev; chip->chip.ops = &vt8500_pwm_ops; |