From d2048c49152a414efcf8b2696896bc8803ef2f1e Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 18 Dec 2014 10:09:42 +0100 Subject: pwm: atmel-hlcdc: Depend on HAVE_CLK The include/linux/clk.h header defines dummy implementations for the various clk_*() functions if HAVE_CLK is not selected to improve build coverage in randconfig builds. The dummy implementation of clk_get_rate() returns 0, which causes the Atmel HLCDC PWM driver's atmel_hlcdc_pwm_config() implementation to end up calling: do_div(clk_period_ns, 0) On x86, do_div(n, base) will end up evaluating to this: n >>= ilog2(base) with base = 0, the implementation of ilog2() will call ____ilog2_NaN(), which is purposely undefined and results in a linker failure: ERROR: "____ilog2_NaN" [drivers/pwm/pwm-atmel-hlcdc.ko] undefined! The implementation of do_div() checks that base is a power of 2 before calling ilog2(). The compiler doesn't optimize this away, presumably because is_power_of_2() is an inline function and the compiler doesn't or can't inspect it closely enough. ilog2() being a macro it still ends up generating the ____ilog2_NaN() because of the constant 0. The root of the problem is that the driver really should be checking before possibly dividing by zero. That should eventually be fixed, but for now just assume that the clock runs at a sensible frequency when available. Reported-by: Jim Davis Acked-by: Boris Brezillon Signed-off-by: Thierry Reding --- drivers/pwm/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index a3ecf5809634..468af1b4ca30 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -53,6 +53,7 @@ config PWM_ATMEL config PWM_ATMEL_HLCDC_PWM tristate "Atmel HLCDC PWM support" depends on MFD_ATMEL_HLCDC + depends on HAVE_CLK help Generic PWM framework driver for the PWM output of the HLCDC (Atmel High-end LCD Controller). This PWM output is mainly used -- cgit v1.2.3 From df6922adec987b1c7f07870a00f899fbc9a7d4cc Mon Sep 17 00:00:00 2001 From: Boris BREZILLON Date: Thu, 18 Dec 2014 21:05:30 +0100 Subject: pwm: atmel-hlcdc: Prevent division by zero The slow and system clock should never return a rate of zero, but this might happen if the clocks property defined in the DT is referencing the wrong clocks. Prevent any division by zero from happening by testing the clk_freq value before calling do_div(). Signed-off-by: Boris Brezillon Signed-off-by: Thierry Reding --- drivers/pwm/pwm-atmel-hlcdc.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/pwm/pwm-atmel-hlcdc.c b/drivers/pwm/pwm-atmel-hlcdc.c index e7a785fadcdf..522f7075bb1a 100644 --- a/drivers/pwm/pwm-atmel-hlcdc.c +++ b/drivers/pwm/pwm-atmel-hlcdc.c @@ -64,6 +64,9 @@ static int atmel_hlcdc_pwm_config(struct pwm_chip *c, if (!chip->errata || !chip->errata->slow_clk_erratum) { clk_freq = clk_get_rate(new_clk); + if (!clk_freq) + return -EINVAL; + clk_period_ns = (u64)NSEC_PER_SEC * 256; do_div(clk_period_ns, clk_freq); } @@ -73,6 +76,9 @@ static int atmel_hlcdc_pwm_config(struct pwm_chip *c, clk_period_ns > period_ns) { new_clk = hlcdc->sys_clk; clk_freq = clk_get_rate(new_clk); + if (!clk_freq) + return -EINVAL; + clk_period_ns = (u64)NSEC_PER_SEC * 256; do_div(clk_period_ns, clk_freq); } -- cgit v1.2.3 From 09853ce7bc1003a490c7ee74a5705d7a7cf16b7d Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Wed, 17 Dec 2014 22:15:39 +0100 Subject: pwm: Add Allwinner SoC support This adds a generic PWM framework driver for the PWM controller found on Allwinner SoCs. Signed-off-by: Alexandre Belloni Acked-by: Maxime Ripard Signed-off-by: Thierry Reding --- drivers/pwm/Kconfig | 10 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-sun4i.c | 366 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 377 insertions(+) create mode 100644 drivers/pwm/pwm-sun4i.c diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 468af1b4ca30..1ba937fb7496 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -284,6 +284,16 @@ config PWM_STI To compile this driver as a module, choose M here: the module will be called pwm-sti. +config PWM_SUN4I + tristate "Allwinner PWM support" + depends on ARCH_SUNXI || COMPILE_TEST + depends on HAS_IOMEM && COMMON_CLK + help + Generic PWM framework driver for Allwinner SoCs. + + To compile this driver as a module, choose M here: the module + will be called pwm-sun4i. + config PWM_TEGRA tristate "NVIDIA Tegra PWM support" depends on ARCH_TEGRA diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 65259ac1e8de..260be018713f 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -26,6 +26,7 @@ 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_SUN4I) += pwm-sun4i.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/pwm-sun4i.c b/drivers/pwm/pwm-sun4i.c new file mode 100644 index 000000000000..cd9dde563018 --- /dev/null +++ b/drivers/pwm/pwm-sun4i.c @@ -0,0 +1,366 @@ +/* + * Driver for Allwinner sun4i Pulse Width Modulation Controller + * + * Copyright (C) 2014 Alexandre Belloni + * + * Licensed under GPLv2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PWM_CTRL_REG 0x0 + +#define PWM_CH_PRD_BASE 0x4 +#define PWM_CH_PRD_OFFSET 0x4 +#define PWM_CH_PRD(ch) (PWM_CH_PRD_BASE + PWM_CH_PRD_OFFSET * (ch)) + +#define PWMCH_OFFSET 15 +#define PWM_PRESCAL_MASK GENMASK(3, 0) +#define PWM_PRESCAL_OFF 0 +#define PWM_EN BIT(4) +#define PWM_ACT_STATE BIT(5) +#define PWM_CLK_GATING BIT(6) +#define PWM_MODE BIT(7) +#define PWM_PULSE BIT(8) +#define PWM_BYPASS BIT(9) + +#define PWM_RDY_BASE 28 +#define PWM_RDY_OFFSET 1 +#define PWM_RDY(ch) BIT(PWM_RDY_BASE + PWM_RDY_OFFSET * (ch)) + +#define PWM_PRD(prd) (((prd) - 1) << 16) +#define PWM_PRD_MASK GENMASK(15, 0) + +#define PWM_DTY_MASK GENMASK(15, 0) + +#define BIT_CH(bit, chan) ((bit) << ((chan) * PWMCH_OFFSET)) + +static const u32 prescaler_table[] = { + 120, + 180, + 240, + 360, + 480, + 0, + 0, + 0, + 12000, + 24000, + 36000, + 48000, + 72000, + 0, + 0, + 0, /* Actually 1 but tested separately */ +}; + +struct sun4i_pwm_data { + bool has_prescaler_bypass; + bool has_rdy; +}; + +struct sun4i_pwm_chip { + struct pwm_chip chip; + struct clk *clk; + void __iomem *base; + spinlock_t ctrl_lock; + const struct sun4i_pwm_data *data; +}; + +static inline struct sun4i_pwm_chip *to_sun4i_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct sun4i_pwm_chip, chip); +} + +static inline u32 sun4i_pwm_readl(struct sun4i_pwm_chip *chip, + unsigned long offset) +{ + return readl(chip->base + offset); +} + +static inline void sun4i_pwm_writel(struct sun4i_pwm_chip *chip, + u32 val, unsigned long offset) +{ + writel(val, chip->base + offset); +} + +static int sun4i_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct sun4i_pwm_chip *sun4i_pwm = to_sun4i_pwm_chip(chip); + u32 prd, dty, val, clk_gate; + u64 clk_rate, div = 0; + unsigned int prescaler = 0; + int err; + + clk_rate = clk_get_rate(sun4i_pwm->clk); + + if (sun4i_pwm->data->has_prescaler_bypass) { + /* First, test without any prescaler when available */ + prescaler = PWM_PRESCAL_MASK; + /* + * When not using any prescaler, the clock period in nanoseconds + * is not an integer so round it half up instead of + * truncating to get less surprising values. + */ + div = clk_rate * period_ns + NSEC_PER_SEC/2; + do_div(div, NSEC_PER_SEC); + if (div - 1 > PWM_PRD_MASK) + prescaler = 0; + } + + if (prescaler == 0) { + /* Go up from the first divider */ + for (prescaler = 0; prescaler < PWM_PRESCAL_MASK; prescaler++) { + if (!prescaler_table[prescaler]) + continue; + div = clk_rate; + do_div(div, prescaler_table[prescaler]); + div = div * period_ns; + do_div(div, NSEC_PER_SEC); + if (div - 1 <= PWM_PRD_MASK) + break; + } + + if (div - 1 > PWM_PRD_MASK) { + dev_err(chip->dev, "period exceeds the maximum value\n"); + return -EINVAL; + } + } + + prd = div; + div *= duty_ns; + do_div(div, period_ns); + dty = div; + + err = clk_prepare_enable(sun4i_pwm->clk); + if (err) { + dev_err(chip->dev, "failed to enable PWM clock\n"); + return err; + } + + spin_lock(&sun4i_pwm->ctrl_lock); + val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG); + + if (sun4i_pwm->data->has_rdy && (val & PWM_RDY(pwm->hwpwm))) { + spin_unlock(&sun4i_pwm->ctrl_lock); + clk_disable_unprepare(sun4i_pwm->clk); + return -EBUSY; + } + + clk_gate = val & BIT_CH(PWM_CLK_GATING, pwm->hwpwm); + if (clk_gate) { + val &= ~BIT_CH(PWM_CLK_GATING, pwm->hwpwm); + sun4i_pwm_writel(sun4i_pwm, val, PWM_CTRL_REG); + } + + val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG); + val &= ~BIT_CH(PWM_PRESCAL_MASK, pwm->hwpwm); + val |= BIT_CH(prescaler, pwm->hwpwm); + sun4i_pwm_writel(sun4i_pwm, val, PWM_CTRL_REG); + + val = (dty & PWM_DTY_MASK) | PWM_PRD(prd); + sun4i_pwm_writel(sun4i_pwm, val, PWM_CH_PRD(pwm->hwpwm)); + + if (clk_gate) { + val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG); + val |= clk_gate; + sun4i_pwm_writel(sun4i_pwm, val, PWM_CTRL_REG); + } + + spin_unlock(&sun4i_pwm->ctrl_lock); + clk_disable_unprepare(sun4i_pwm->clk); + + return 0; +} + +static int sun4i_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm, + enum pwm_polarity polarity) +{ + struct sun4i_pwm_chip *sun4i_pwm = to_sun4i_pwm_chip(chip); + u32 val; + int ret; + + ret = clk_prepare_enable(sun4i_pwm->clk); + if (ret) { + dev_err(chip->dev, "failed to enable PWM clock\n"); + return ret; + } + + spin_lock(&sun4i_pwm->ctrl_lock); + val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG); + + if (polarity != PWM_POLARITY_NORMAL) + val &= ~BIT_CH(PWM_ACT_STATE, pwm->hwpwm); + else + val |= BIT_CH(PWM_ACT_STATE, pwm->hwpwm); + + sun4i_pwm_writel(sun4i_pwm, val, PWM_CTRL_REG); + + spin_unlock(&sun4i_pwm->ctrl_lock); + clk_disable_unprepare(sun4i_pwm->clk); + + return 0; +} + +static int sun4i_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct sun4i_pwm_chip *sun4i_pwm = to_sun4i_pwm_chip(chip); + u32 val; + int ret; + + ret = clk_prepare_enable(sun4i_pwm->clk); + if (ret) { + dev_err(chip->dev, "failed to enable PWM clock\n"); + return ret; + } + + spin_lock(&sun4i_pwm->ctrl_lock); + val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG); + val |= BIT_CH(PWM_EN, pwm->hwpwm); + val |= BIT_CH(PWM_CLK_GATING, pwm->hwpwm); + sun4i_pwm_writel(sun4i_pwm, val, PWM_CTRL_REG); + spin_unlock(&sun4i_pwm->ctrl_lock); + + return 0; +} + +static void sun4i_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct sun4i_pwm_chip *sun4i_pwm = to_sun4i_pwm_chip(chip); + u32 val; + + spin_lock(&sun4i_pwm->ctrl_lock); + val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG); + val &= ~BIT_CH(PWM_EN, pwm->hwpwm); + val &= ~BIT_CH(PWM_CLK_GATING, pwm->hwpwm); + sun4i_pwm_writel(sun4i_pwm, val, PWM_CTRL_REG); + spin_unlock(&sun4i_pwm->ctrl_lock); + + clk_disable_unprepare(sun4i_pwm->clk); +} + +static const struct pwm_ops sun4i_pwm_ops = { + .config = sun4i_pwm_config, + .set_polarity = sun4i_pwm_set_polarity, + .enable = sun4i_pwm_enable, + .disable = sun4i_pwm_disable, + .owner = THIS_MODULE, +}; + +static const struct sun4i_pwm_data sun4i_pwm_data_a10 = { + .has_prescaler_bypass = false, + .has_rdy = false, +}; + +static const struct sun4i_pwm_data sun4i_pwm_data_a20 = { + .has_prescaler_bypass = true, + .has_rdy = true, +}; + +static const struct of_device_id sun4i_pwm_dt_ids[] = { + { + .compatible = "allwinner,sun4i-a10-pwm", + .data = &sun4i_pwm_data_a10, + }, { + .compatible = "allwinner,sun7i-a20-pwm", + .data = &sun4i_pwm_data_a20, + }, { + /* sentinel */ + }, +}; +MODULE_DEVICE_TABLE(of, sun4i_pwm_dt_ids); + +static int sun4i_pwm_probe(struct platform_device *pdev) +{ + struct sun4i_pwm_chip *pwm; + struct resource *res; + u32 val; + int i, ret; + const struct of_device_id *match; + + match = of_match_device(sun4i_pwm_dt_ids, &pdev->dev); + + pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); + if (!pwm) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pwm->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(pwm->base)) + return PTR_ERR(pwm->base); + + 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 = &sun4i_pwm_ops; + pwm->chip.base = -1; + pwm->chip.npwm = 2; + pwm->chip.can_sleep = true; + pwm->chip.of_xlate = of_pwm_xlate_with_flags; + pwm->chip.of_pwm_n_cells = 3; + pwm->data = match->data; + + spin_lock_init(&pwm->ctrl_lock); + + ret = pwmchip_add(&pwm->chip); + if (ret < 0) { + dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, pwm); + + ret = clk_prepare_enable(pwm->clk); + if (ret) { + dev_err(&pdev->dev, "failed to enable PWM clock\n"); + goto clk_error; + } + + val = sun4i_pwm_readl(pwm, PWM_CTRL_REG); + for (i = 0; i < pwm->chip.npwm; i++) + if (!(val & BIT_CH(PWM_ACT_STATE, i))) + pwm->chip.pwms[i].polarity = PWM_POLARITY_INVERSED; + clk_disable_unprepare(pwm->clk); + + return 0; + +clk_error: + pwmchip_remove(&pwm->chip); + return ret; +} + +static int sun4i_pwm_remove(struct platform_device *pdev) +{ + struct sun4i_pwm_chip *pwm = platform_get_drvdata(pdev); + + return pwmchip_remove(&pwm->chip); +} + +static struct platform_driver sun4i_pwm_driver = { + .driver = { + .name = "sun4i-pwm", + .of_match_table = sun4i_pwm_dt_ids, + }, + .probe = sun4i_pwm_probe, + .remove = sun4i_pwm_remove, +}; +module_platform_driver(sun4i_pwm_driver); + +MODULE_ALIAS("platform:sun4i-pwm"); +MODULE_AUTHOR("Alexandre Belloni "); +MODULE_DESCRIPTION("Allwinner sun4i PWM driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From ed73598531337bce238915280396c8e0dcee93bc Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Wed, 17 Dec 2014 22:15:40 +0100 Subject: pwm: sunxi: document OF bindings This is the documentation for the Allwinner SoCs PWM bindings. Signed-off-by: Alexandre Belloni Acked-by: Maxime Ripard Signed-off-by: Thierry Reding --- Documentation/devicetree/bindings/pwm/pwm-sun4i.txt | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 Documentation/devicetree/bindings/pwm/pwm-sun4i.txt diff --git a/Documentation/devicetree/bindings/pwm/pwm-sun4i.txt b/Documentation/devicetree/bindings/pwm/pwm-sun4i.txt new file mode 100644 index 000000000000..ae0273e19506 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/pwm-sun4i.txt @@ -0,0 +1,20 @@ +Allwinner sun4i and sun7i SoC PWM controller + +Required properties: + - compatible: should be one of: + - "allwinner,sun4i-a10-pwm" + - "allwinner,sun7i-a20-pwm" + - reg: physical base address and length of the controller's registers + - #pwm-cells: should be 3. See pwm.txt in this directory for a description of + the cells format. + - clocks: From common clock binding, handle to the parent clock. + +Example: + + pwm: pwm@01c20e00 { + compatible = "allwinner,sun7i-a20-pwm"; + reg = <0x01c20e00 0xc>; + clocks = <&osc24M>; + #pwm-cells = <3>; + status = "disabled"; + }; -- cgit v1.2.3 From cd264b6a6559caa74579b5afca11379ddde92dca Mon Sep 17 00:00:00 2001 From: Ajit Pal Singh Date: Thu, 29 Jan 2015 14:34:43 +0530 Subject: pwm: sti: Maintain a bitmap of configured devices This patch introduces a bitmap which is used to keep track of the pwm channels which have been configured in a pwm chip. The method used earlier to find the number of configured channels, was to count the pwmdevices with PWMF_REQUESTED field set and period value configured. This was not correct and failed when of_pwm_get()/pwm_get() and then pwm_config() was used. Signed-off-by: Ajit Pal Singh Signed-off-by: Thierry Reding --- drivers/pwm/pwm-sti.c | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/drivers/pwm/pwm-sti.c b/drivers/pwm/pwm-sti.c index b95115cdaea7..92abbd56b9f7 100644 --- a/drivers/pwm/pwm-sti.c +++ b/drivers/pwm/pwm-sti.c @@ -57,6 +57,7 @@ struct sti_pwm_chip { struct regmap_field *pwm_int_en; struct pwm_chip chip; struct pwm_device *cur; + unsigned long configured; unsigned int en_count; struct mutex sti_pwm_lock; /* To sync between enable/disable calls */ void __iomem *mmio; @@ -102,24 +103,6 @@ static int sti_pwm_get_prescale(struct sti_pwm_chip *pc, unsigned long period, 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) @@ -141,7 +124,7 @@ static int sti_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, unsigned int ncfg; bool period_same = false; - ncfg = sti_pwm_count_configured(chip); + ncfg = hweight_long(pc->configured); if (ncfg) period_same = (period_ns == pwm_get_period(cur)); @@ -197,6 +180,7 @@ static int sti_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, ret = regmap_field_write(pc->pwm_int_en, 0); + set_bit(pwm->hwpwm, &pc->configured); pc->cur = pwm; dev_dbg(dev, "prescale:%u, period:%i, duty:%i, pwmvalx:%u\n", @@ -254,10 +238,18 @@ static void sti_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) mutex_unlock(&pc->sti_pwm_lock); } +static void sti_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct sti_pwm_chip *pc = to_sti_pwmchip(chip); + + clear_bit(pwm->hwpwm, &pc->configured); +} + static const struct pwm_ops sti_pwm_ops = { .config = sti_pwm_config, .enable = sti_pwm_enable, .disable = sti_pwm_disable, + .free = sti_pwm_free, .owner = THIS_MODULE, }; -- cgit v1.2.3 From 277bb6a29e0037c3e7d9e70945eb22003006935d Mon Sep 17 00:00:00 2001 From: Naidu Tellapati Date: Fri, 9 Jan 2015 14:54:47 -0300 Subject: pwm: Imagination Technologies PWM DAC driver The Pistachio SOC from Imagination Technologies includes a Pulse Width Modulation DAC which produces 1 to 4 digital bit-outputs which represent digital waveforms. These PWM outputs are primarily in charge of controlling backlight LED devices. Reviewed-by: Andrew Bresticker Signed-off-by: Naidu Tellapati Signed-off-by: Sai Masarapu Signed-off-by: Ezequiel Garcia Reviewed-by: Vladimir Zapolskiy [thierry.reding: fixup license header as discussed on list] Signed-off-by: Thierry Reding --- drivers/pwm/Kconfig | 13 +++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-img.c | 249 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 263 insertions(+) create mode 100644 drivers/pwm/pwm-img.c diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 1ba937fb7496..b1541f40fd8d 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -131,6 +131,19 @@ config PWM_FSL_FTM To compile this driver as a module, choose M here: the module will be called pwm-fsl-ftm. +config PWM_IMG + tristate "Imagination Technologies PWM driver" + depends on HAS_IOMEM + depends on MFD_SYSCON + depends on COMMON_CLK + depends on MIPS || COMPILE_TEST + help + Generic PWM framework driver for Imagination Technologies + PWM block which supports 4 channels. + + To compile this driver as a module, choose M here: the module + will be called pwm-img + config PWM_IMX tristate "i.MX PWM support" depends on ARCH_MXC diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 260be018713f..ec50eb5b5a8f 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -10,6 +10,7 @@ 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_IMG) += pwm-img.o obj-$(CONFIG_PWM_IMX) += pwm-imx.o obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o obj-$(CONFIG_PWM_LP3943) += pwm-lp3943.o diff --git a/drivers/pwm/pwm-img.c b/drivers/pwm/pwm-img.c new file mode 100644 index 000000000000..476171a768d6 --- /dev/null +++ b/drivers/pwm/pwm-img.c @@ -0,0 +1,249 @@ +/* + * Imagination Technologies Pulse Width Modulator driver + * + * Copyright (c) 2014-2015, Imagination Technologies + * + * Based on drivers/pwm/pwm-tegra.c, Copyright (c) 2010, NVIDIA 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; either version 2 of the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* PWM registers */ +#define PWM_CTRL_CFG 0x0000 +#define PWM_CTRL_CFG_NO_SUB_DIV 0 +#define PWM_CTRL_CFG_SUB_DIV0 1 +#define PWM_CTRL_CFG_SUB_DIV1 2 +#define PWM_CTRL_CFG_SUB_DIV0_DIV1 3 +#define PWM_CTRL_CFG_DIV_SHIFT(ch) ((ch) * 2 + 4) +#define PWM_CTRL_CFG_DIV_MASK 0x3 + +#define PWM_CH_CFG(ch) (0x4 + (ch) * 4) +#define PWM_CH_CFG_TMBASE_SHIFT 0 +#define PWM_CH_CFG_DUTY_SHIFT 16 + +#define PERIP_PWM_PDM_CONTROL 0x0140 +#define PERIP_PWM_PDM_CONTROL_CH_MASK 0x1 +#define PERIP_PWM_PDM_CONTROL_CH_SHIFT(ch) ((ch) * 4) + +#define MAX_TMBASE_STEPS 65536 + +struct img_pwm_chip { + struct device *dev; + struct pwm_chip chip; + struct clk *pwm_clk; + struct clk *sys_clk; + void __iomem *base; + struct regmap *periph_regs; +}; + +static inline struct img_pwm_chip *to_img_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct img_pwm_chip, chip); +} + +static inline void img_pwm_writel(struct img_pwm_chip *chip, + u32 reg, u32 val) +{ + writel(val, chip->base + reg); +} + +static inline u32 img_pwm_readl(struct img_pwm_chip *chip, + u32 reg) +{ + return readl(chip->base + reg); +} + +static int img_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + u32 val, div, duty, timebase; + unsigned long mul, output_clk_hz, input_clk_hz; + struct img_pwm_chip *pwm_chip = to_img_pwm_chip(chip); + + input_clk_hz = clk_get_rate(pwm_chip->pwm_clk); + output_clk_hz = DIV_ROUND_UP(NSEC_PER_SEC, period_ns); + + mul = DIV_ROUND_UP(input_clk_hz, output_clk_hz); + if (mul <= MAX_TMBASE_STEPS) { + div = PWM_CTRL_CFG_NO_SUB_DIV; + timebase = DIV_ROUND_UP(mul, 1); + } else if (mul <= MAX_TMBASE_STEPS * 8) { + div = PWM_CTRL_CFG_SUB_DIV0; + timebase = DIV_ROUND_UP(mul, 8); + } else if (mul <= MAX_TMBASE_STEPS * 64) { + div = PWM_CTRL_CFG_SUB_DIV1; + timebase = DIV_ROUND_UP(mul, 64); + } else if (mul <= MAX_TMBASE_STEPS * 512) { + div = PWM_CTRL_CFG_SUB_DIV0_DIV1; + timebase = DIV_ROUND_UP(mul, 512); + } else if (mul > MAX_TMBASE_STEPS * 512) { + dev_err(chip->dev, + "failed to configure timebase steps/divider value\n"); + return -EINVAL; + } + + duty = DIV_ROUND_UP(timebase * duty_ns, period_ns); + + val = img_pwm_readl(pwm_chip, PWM_CTRL_CFG); + val &= ~(PWM_CTRL_CFG_DIV_MASK << PWM_CTRL_CFG_DIV_SHIFT(pwm->hwpwm)); + val |= (div & PWM_CTRL_CFG_DIV_MASK) << + PWM_CTRL_CFG_DIV_SHIFT(pwm->hwpwm); + img_pwm_writel(pwm_chip, PWM_CTRL_CFG, val); + + val = (duty << PWM_CH_CFG_DUTY_SHIFT) | + (timebase << PWM_CH_CFG_TMBASE_SHIFT); + img_pwm_writel(pwm_chip, PWM_CH_CFG(pwm->hwpwm), val); + + return 0; +} + +static int img_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + u32 val; + struct img_pwm_chip *pwm_chip = to_img_pwm_chip(chip); + + val = img_pwm_readl(pwm_chip, PWM_CTRL_CFG); + val |= BIT(pwm->hwpwm); + img_pwm_writel(pwm_chip, PWM_CTRL_CFG, val); + + regmap_update_bits(pwm_chip->periph_regs, PERIP_PWM_PDM_CONTROL, + PERIP_PWM_PDM_CONTROL_CH_MASK << + PERIP_PWM_PDM_CONTROL_CH_SHIFT(pwm->hwpwm), 0); + + return 0; +} + +static void img_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + u32 val; + struct img_pwm_chip *pwm_chip = to_img_pwm_chip(chip); + + val = img_pwm_readl(pwm_chip, PWM_CTRL_CFG); + val &= ~BIT(pwm->hwpwm); + img_pwm_writel(pwm_chip, PWM_CTRL_CFG, val); +} + +static const struct pwm_ops img_pwm_ops = { + .config = img_pwm_config, + .enable = img_pwm_enable, + .disable = img_pwm_disable, + .owner = THIS_MODULE, +}; + +static int img_pwm_probe(struct platform_device *pdev) +{ + int ret; + struct resource *res; + struct img_pwm_chip *pwm; + + pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); + if (!pwm) + return -ENOMEM; + + pwm->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pwm->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(pwm->base)) + return PTR_ERR(pwm->base); + + pwm->periph_regs = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "img,cr-periph"); + if (IS_ERR(pwm->periph_regs)) + return PTR_ERR(pwm->periph_regs); + + pwm->sys_clk = devm_clk_get(&pdev->dev, "sys"); + if (IS_ERR(pwm->sys_clk)) { + dev_err(&pdev->dev, "failed to get system clock\n"); + return PTR_ERR(pwm->sys_clk); + } + + pwm->pwm_clk = devm_clk_get(&pdev->dev, "pwm"); + if (IS_ERR(pwm->pwm_clk)) { + dev_err(&pdev->dev, "failed to get pwm clock\n"); + return PTR_ERR(pwm->pwm_clk); + } + + ret = clk_prepare_enable(pwm->sys_clk); + if (ret < 0) { + dev_err(&pdev->dev, "could not prepare or enable sys clock\n"); + return ret; + } + + ret = clk_prepare_enable(pwm->pwm_clk); + if (ret < 0) { + dev_err(&pdev->dev, "could not prepare or enable pwm clock\n"); + goto disable_sysclk; + } + + pwm->chip.dev = &pdev->dev; + pwm->chip.ops = &img_pwm_ops; + pwm->chip.base = -1; + pwm->chip.npwm = 4; + + ret = pwmchip_add(&pwm->chip); + if (ret < 0) { + dev_err(&pdev->dev, "pwmchip_add failed: %d\n", ret); + goto disable_pwmclk; + } + + platform_set_drvdata(pdev, pwm); + return 0; + +disable_pwmclk: + clk_disable_unprepare(pwm->pwm_clk); +disable_sysclk: + clk_disable_unprepare(pwm->sys_clk); + return ret; +} + +static int img_pwm_remove(struct platform_device *pdev) +{ + struct img_pwm_chip *pwm_chip = platform_get_drvdata(pdev); + u32 val; + unsigned int i; + + for (i = 0; i < pwm_chip->chip.npwm; i++) { + val = img_pwm_readl(pwm_chip, PWM_CTRL_CFG); + val &= ~BIT(i); + img_pwm_writel(pwm_chip, PWM_CTRL_CFG, val); + } + + clk_disable_unprepare(pwm_chip->pwm_clk); + clk_disable_unprepare(pwm_chip->sys_clk); + + return pwmchip_remove(&pwm_chip->chip); +} + +static const struct of_device_id img_pwm_of_match[] = { + { .compatible = "img,pistachio-pwm", }, + { } +}; +MODULE_DEVICE_TABLE(of, img_pwm_of_match); + +static struct platform_driver img_pwm_driver = { + .driver = { + .name = "img-pwm", + .of_match_table = img_pwm_of_match, + }, + .probe = img_pwm_probe, + .remove = img_pwm_remove, +}; +module_platform_driver(img_pwm_driver); + +MODULE_AUTHOR("Sai Masarapu "); +MODULE_DESCRIPTION("Imagination Technologies PWM DAC driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 9c959bfe00396bc2e6143dc9a8b27feaffb78c74 Mon Sep 17 00:00:00 2001 From: Naidu Tellapati Date: Fri, 9 Jan 2015 14:54:48 -0300 Subject: pwm: Add device tree binding document for IMG PWM DAC Add binding document for IMG Pulse Width Modulator (PWM) DAC present on the Pistachio SOC. The PWM DAC has four channels. Signed-off-by: Naidu Tellapati Signed-off-by: Sai Masarapu Signed-off-by: Ezequiel Garcia Signed-off-by: Thierry Reding --- Documentation/devicetree/bindings/pwm/img-pwm.txt | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 Documentation/devicetree/bindings/pwm/img-pwm.txt diff --git a/Documentation/devicetree/bindings/pwm/img-pwm.txt b/Documentation/devicetree/bindings/pwm/img-pwm.txt new file mode 100644 index 000000000000..fade5f26fcac --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/img-pwm.txt @@ -0,0 +1,24 @@ +*Imagination Technologies PWM DAC driver + +Required properties: + - compatible: Should be "img,pistachio-pwm" + - reg: Should contain physical base address and length of pwm registers. + - clocks: Must contain an entry for each entry in clock-names. + See ../clock/clock-bindings.txt for details. + - clock-names: Must include the following entries. + - pwm: PWM operating clock. + - sys: PWM system interface clock. + - #pwm-cells: Should be 2. See pwm.txt in this directory for the + description of the cells format. + - img,cr-periph: Must contain a phandle to the peripheral control + syscon node which contains PWM control registers. + +Example: + pwm: pwm@18101300 { + compatible = "img,pistachio-pwm"; + reg = <0x18101300 0x100>; + clocks = <&pwm_clk>, <&system_clk>; + clock-names = "pwm", "sys"; + #pwm-cells = <2>; + img,cr-periph = <&cr_periph>; + }; -- cgit v1.2.3 From 8d6cc0738540f97edb021d3b76a4367519f1e5f1 Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Tue, 3 Feb 2015 11:54:28 +0100 Subject: pwm: Remove unnecessary check before of_node_put() The of_node_put() function tests whether its argument is NULL and then returns immediately. Thus the test around the call is not needed. This issue was detected by using the Coccinelle software. Signed-off-by: Markus Elfring Signed-off-by: Thierry Reding --- drivers/pwm/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index 966497d10c6e..810aef3f4c3e 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -192,7 +192,7 @@ static void of_pwmchip_add(struct pwm_chip *chip) static void of_pwmchip_remove(struct pwm_chip *chip) { - if (chip->dev && chip->dev->of_node) + if (chip->dev) of_node_put(chip->dev->of_node); } -- cgit v1.2.3 From b65af27ad89de60ca55721f9368b18d49ba1f269 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 18 Feb 2015 08:40:29 +0100 Subject: pwm: tegra: Use NSEC_PER_SEC Instead of using the literal value for the number of nanoseconds per second, use the macro instead to increase readability. Signed-off-by: Thierry Reding --- drivers/pwm/pwm-tegra.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c index 5b97cae5423a..cabd7d8e05cc 100644 --- a/drivers/pwm/pwm-tegra.c +++ b/drivers/pwm/pwm-tegra.c @@ -87,7 +87,7 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, * cycles at the PWM clock rate will take period_ns nanoseconds. */ rate = clk_get_rate(pc->clk) >> PWM_DUTY_WIDTH; - hz = 1000000000ul / period_ns; + hz = NSEC_PER_SEC / period_ns; rate = (rate + (hz / 2)) / hz; -- cgit v1.2.3