diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2016-01-21 19:45:02 (GMT) |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2016-01-21 19:45:02 (GMT) |
commit | 859e762544902c3e09b3f8d994aab80ea5ff5042 (patch) | |
tree | 538b0655591a3f3573dd33ae0c9b1a15600ed2f1 | |
parent | 96461fdb3ace6753d29eaa43387bcd6d540710e9 (diff) | |
parent | ff01c944cfa939f3474c28d88223213494aedf0b (diff) | |
download | linux-859e762544902c3e09b3f8d994aab80ea5ff5042.tar.xz |
Merge tag 'pwm/for-4.5-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm
Pull pwm updates from Thierry Reding:
"This set of changes contains a new driver for OMAP (using the
dual-mode timers) as well as an assortment of fixes all across the
board"
* tag 'pwm/for-4.5-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm:
pwm: Mark all devices as "might sleep"
pwm: omap-dmtimer: Potential NULL dereference on error
pwm: add HAS_IOMEM dependency to PWM_FSL_FTM
pwm: Add PWM driver for OMAP using dual-mode timers
pwm: rcar: Improve accuracy of frequency division setting
pwm: lpc32xx: return ERANGE, if requested period is not supported
pwm: lpc32xx: fix and simplify duty cycle and period calculations
pwm: lpc32xx: make device usable with common clock framework
pwm: lpc32xx: correct number of PWM channels from 2 to 1
dt: lpc32xx: pwm: update documentation of LPC32xx PWM device
dt: lpc32xx: pwm: correct LPC32xx PWM device node example
pwm: fsl-ftm: Fix clock enable/disable when using PM
pwm: lpss: Rework the sequence of programming PWM_SW_UPDATE
pwm: lpss: Select core part automatically
pwm: lpss: Update PWM setting for Broxton
pwm: bcm2835: Fix email address specification
pwm: bcm2835: Prevent division by zero
pwm: bcm2835: Calculate scaler in ->config()
pwm: lpss: Remove ->free() callback
-rw-r--r-- | Documentation/devicetree/bindings/pwm/lpc32xx-pwm.txt | 9 | ||||
-rw-r--r-- | Documentation/devicetree/bindings/pwm/pwm-omap-dmtimer.txt | 18 | ||||
-rw-r--r-- | drivers/pwm/Kconfig | 25 | ||||
-rw-r--r-- | drivers/pwm/Makefile | 1 | ||||
-rw-r--r-- | drivers/pwm/core.c | 2 | ||||
-rw-r--r-- | drivers/pwm/pwm-bcm2835.c | 18 | ||||
-rw-r--r-- | drivers/pwm/pwm-fsl-ftm.c | 58 | ||||
-rw-r--r-- | drivers/pwm/pwm-lpc32xx.c | 59 | ||||
-rw-r--r-- | drivers/pwm/pwm-lpss.c | 57 | ||||
-rw-r--r-- | drivers/pwm/pwm-lpss.h | 1 | ||||
-rw-r--r-- | drivers/pwm/pwm-omap-dmtimer.c | 327 | ||||
-rw-r--r-- | drivers/pwm/pwm-rcar.c | 2 | ||||
-rw-r--r-- | include/linux/platform_data/pwm_omap_dmtimer.h | 69 |
13 files changed, 539 insertions, 107 deletions
diff --git a/Documentation/devicetree/bindings/pwm/lpc32xx-pwm.txt b/Documentation/devicetree/bindings/pwm/lpc32xx-pwm.txt index cfe1db3..74b5bc5 100644 --- a/Documentation/devicetree/bindings/pwm/lpc32xx-pwm.txt +++ b/Documentation/devicetree/bindings/pwm/lpc32xx-pwm.txt @@ -6,7 +6,12 @@ Required properties: Examples: -pwm@0x4005C000 { +pwm@4005c000 { compatible = "nxp,lpc3220-pwm"; - reg = <0x4005C000 0x8>; + reg = <0x4005c000 0x4>; +}; + +pwm@4005c004 { + compatible = "nxp,lpc3220-pwm"; + reg = <0x4005c004 0x4>; }; diff --git a/Documentation/devicetree/bindings/pwm/pwm-omap-dmtimer.txt b/Documentation/devicetree/bindings/pwm/pwm-omap-dmtimer.txt new file mode 100644 index 0000000..5befb53 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/pwm-omap-dmtimer.txt @@ -0,0 +1,18 @@ +* OMAP PWM for dual-mode timers + +Required properties: +- compatible: Shall contain "ti,omap-dmtimer-pwm". +- ti,timers: phandle to PWM capable OMAP timer. See arm/omap/timer.txt for info + about these timers. +- #pwm-cells: Should be 3. See pwm.txt in this directory for a description of + the cells format. + +Optional properties: +- ti,prescaler: Should be a value between 0 and 7, see the timers datasheet + +Example: + pwm9: dmtimer-pwm@9 { + compatible = "ti,omap-dmtimer-pwm"; + ti,timers = <&timer9>; + #pwm-cells = <3>; + }; diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 2f4641a..8cf0dae 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -148,6 +148,7 @@ config PWM_EP93XX config PWM_FSL_FTM tristate "Freescale FlexTimer Module (FTM) PWM support" + depends on HAS_IOMEM depends on OF select REGMAP_MMIO help @@ -222,18 +223,12 @@ config PWM_LPC32XX will be called pwm-lpc32xx. config PWM_LPSS - tristate "Intel LPSS PWM support" - depends on X86 - 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. + tristate config PWM_LPSS_PCI tristate "Intel LPSS PWM PCI driver" - depends on PWM_LPSS && PCI + depends on X86 && PCI + select PWM_LPSS help The PCI driver for Intel Low Power Subsystem PWM controller. @@ -242,7 +237,8 @@ config PWM_LPSS_PCI config PWM_LPSS_PLATFORM tristate "Intel LPSS PWM platform driver" - depends on PWM_LPSS && ACPI + depends on X86 && ACPI + select PWM_LPSS help The platform driver for Intel Low Power Subsystem PWM controller. @@ -270,6 +266,15 @@ config PWM_MXS To compile this driver as a module, choose M here: the module will be called pwm-mxs. +config PWM_OMAP_DMTIMER + tristate "OMAP Dual-Mode Timer PWM support" + depends on OF && ARCH_OMAP && OMAP_DM_TIMER + help + Generic PWM framework driver for OMAP Dual-Mode Timer PWM output + + To compile this driver as a module, choose M here: the module + will be called pwm-omap-dmtimer + config PWM_PCA9685 tristate "NXP PCA9685 PWM driver" depends on I2C diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 69b8275..dd35bc1 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -24,6 +24,7 @@ obj-$(CONFIG_PWM_LPSS_PCI) += pwm-lpss-pci.o obj-$(CONFIG_PWM_LPSS_PLATFORM) += pwm-lpss-platform.o obj-$(CONFIG_PWM_MTK_DISP) += pwm-mtk-disp.o obj-$(CONFIG_PWM_MXS) += pwm-mxs.o +obj-$(CONFIG_PWM_OMAP_DMTIMER) += pwm-omap-dmtimer.o obj-$(CONFIG_PWM_PCA9685) += pwm-pca9685.o obj-$(CONFIG_PWM_PUV3) += pwm-puv3.o obj-$(CONFIG_PWM_PXA) += pwm-pxa.o diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index d24ca5f..7831bc6 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -889,7 +889,7 @@ EXPORT_SYMBOL_GPL(devm_pwm_put); */ bool pwm_can_sleep(struct pwm_device *pwm) { - return pwm->chip->can_sleep; + return true; } EXPORT_SYMBOL_GPL(pwm_can_sleep); diff --git a/drivers/pwm/pwm-bcm2835.c b/drivers/pwm/pwm-bcm2835.c index b4c7f95..c5dbf16 100644 --- a/drivers/pwm/pwm-bcm2835.c +++ b/drivers/pwm/pwm-bcm2835.c @@ -29,7 +29,6 @@ struct bcm2835_pwm { struct pwm_chip chip; struct device *dev; - unsigned long scaler; void __iomem *base; struct clk *clk; }; @@ -66,6 +65,15 @@ static int bcm2835_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) { struct bcm2835_pwm *pc = to_bcm2835_pwm(chip); + unsigned long rate = clk_get_rate(pc->clk); + unsigned long scaler; + + if (!rate) { + dev_err(pc->dev, "failed to get clock rate\n"); + return -EINVAL; + } + + scaler = NSEC_PER_SEC / rate; if (period_ns <= MIN_PERIOD) { dev_err(pc->dev, "period %d not supported, minimum %d\n", @@ -73,8 +81,8 @@ static int bcm2835_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, return -EINVAL; } - writel(duty_ns / pc->scaler, pc->base + DUTY(pwm->hwpwm)); - writel(period_ns / pc->scaler, pc->base + PERIOD(pwm->hwpwm)); + writel(duty_ns / scaler, pc->base + DUTY(pwm->hwpwm)); + writel(period_ns / scaler, pc->base + PERIOD(pwm->hwpwm)); return 0; } @@ -156,8 +164,6 @@ static int bcm2835_pwm_probe(struct platform_device *pdev) if (ret) return ret; - pc->scaler = NSEC_PER_SEC / clk_get_rate(pc->clk); - pc->chip.dev = &pdev->dev; pc->chip.ops = &bcm2835_pwm_ops; pc->chip.npwm = 2; @@ -200,6 +206,6 @@ static struct platform_driver bcm2835_pwm_driver = { }; module_platform_driver(bcm2835_pwm_driver); -MODULE_AUTHOR("Bart Tanghe <bart.tanghe@thomasmore.be"); +MODULE_AUTHOR("Bart Tanghe <bart.tanghe@thomasmore.be>"); MODULE_DESCRIPTION("Broadcom BCM2835 PWM driver"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-fsl-ftm.c b/drivers/pwm/pwm-fsl-ftm.c index f9dfc8b..7225ac6 100644 --- a/drivers/pwm/pwm-fsl-ftm.c +++ b/drivers/pwm/pwm-fsl-ftm.c @@ -80,7 +80,6 @@ struct fsl_pwm_chip { struct mutex lock; - unsigned int use_count; unsigned int cnt_select; unsigned int clk_ps; @@ -300,9 +299,6 @@ static int fsl_counter_clock_enable(struct fsl_pwm_chip *fpc) { int ret; - if (fpc->use_count++ != 0) - return 0; - /* select counter clock source */ regmap_update_bits(fpc->regmap, FTM_SC, FTM_SC_CLK_MASK, FTM_SC_CLK(fpc->cnt_select)); @@ -334,25 +330,6 @@ static int fsl_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) return ret; } -static void fsl_counter_clock_disable(struct fsl_pwm_chip *fpc) -{ - /* - * 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 */ - regmap_update_bits(fpc->regmap, FTM_SC, FTM_SC_CLK_MASK, 0); - - 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); @@ -362,7 +339,8 @@ static void fsl_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) regmap_update_bits(fpc->regmap, FTM_OUTMASK, BIT(pwm->hwpwm), BIT(pwm->hwpwm)); - fsl_counter_clock_disable(fpc); + clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_CNTEN]); + clk_disable_unprepare(fpc->clk[fpc->cnt_select]); regmap_read(fpc->regmap, FTM_OUTMASK, &val); if ((val & 0xFF) == 0xFF) @@ -492,17 +470,24 @@ static int fsl_pwm_remove(struct platform_device *pdev) static int fsl_pwm_suspend(struct device *dev) { struct fsl_pwm_chip *fpc = dev_get_drvdata(dev); - u32 val; + int i; regcache_cache_only(fpc->regmap, true); regcache_mark_dirty(fpc->regmap); - /* read from cache */ - regmap_read(fpc->regmap, FTM_OUTMASK, &val); - if ((val & 0xFF) != 0xFF) { + for (i = 0; i < fpc->chip.npwm; i++) { + struct pwm_device *pwm = &fpc->chip.pwms[i]; + + if (!test_bit(PWMF_REQUESTED, &pwm->flags)) + continue; + + clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_SYS]); + + if (!pwm_is_enabled(pwm)) + continue; + clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_CNTEN]); clk_disable_unprepare(fpc->clk[fpc->cnt_select]); - clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_SYS]); } return 0; @@ -511,12 +496,19 @@ static int fsl_pwm_suspend(struct device *dev) static int fsl_pwm_resume(struct device *dev) { struct fsl_pwm_chip *fpc = dev_get_drvdata(dev); - u32 val; + int i; + + for (i = 0; i < fpc->chip.npwm; i++) { + struct pwm_device *pwm = &fpc->chip.pwms[i]; + + if (!test_bit(PWMF_REQUESTED, &pwm->flags)) + continue; - /* read from cache */ - regmap_read(fpc->regmap, FTM_OUTMASK, &val); - if ((val & 0xFF) != 0xFF) { clk_prepare_enable(fpc->clk[FSL_PWM_CLK_SYS]); + + if (!pwm_is_enabled(pwm)) + continue; + clk_prepare_enable(fpc->clk[fpc->cnt_select]); clk_prepare_enable(fpc->clk[FSL_PWM_CLK_CNTEN]); } diff --git a/drivers/pwm/pwm-lpc32xx.c b/drivers/pwm/pwm-lpc32xx.c index 9fde60c..4d470c1 100644 --- a/drivers/pwm/pwm-lpc32xx.c +++ b/drivers/pwm/pwm-lpc32xx.c @@ -24,9 +24,7 @@ struct lpc32xx_pwm_chip { void __iomem *base; }; -#define PWM_ENABLE (1 << 31) -#define PWM_RELOADV(x) (((x) & 0xFF) << 8) -#define PWM_DUTY(x) ((x) & 0xFF) +#define PWM_ENABLE BIT(31) #define to_lpc32xx_pwm_chip(_chip) \ container_of(_chip, struct lpc32xx_pwm_chip, chip) @@ -38,40 +36,27 @@ static int lpc32xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, 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; + c = clk_get_rate(lpc32xx->clk); + + /* The highest acceptable divisor is 256, which is represented by 0 */ + period_cycles = div64_u64(c * period_ns, + (unsigned long long)NSEC_PER_SEC * 256); + if (!period_cycles || period_cycles > 256) + return -ERANGE; + if (period_cycles == 256) + period_cycles = 0; + + /* Compute 256 x #duty/period value and care for corner cases */ + duty_cycles = div64_u64((unsigned long long)(period_ns - duty_ns) * 256, + period_ns); + if (!duty_cycles) + duty_cycles = 1; + if (duty_cycles > 255) + duty_cycles = 255; val = readl(lpc32xx->base + (pwm->hwpwm << 2)); val &= ~0xFFFF; - val |= PWM_RELOADV(period_cycles) | PWM_DUTY(duty_cycles); + val |= (period_cycles << 8) | duty_cycles; writel(val, lpc32xx->base + (pwm->hwpwm << 2)); return 0; @@ -83,7 +68,7 @@ static int lpc32xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) u32 val; int ret; - ret = clk_enable(lpc32xx->clk); + ret = clk_prepare_enable(lpc32xx->clk); if (ret) return ret; @@ -103,7 +88,7 @@ static void lpc32xx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) val &= ~PWM_ENABLE; writel(val, lpc32xx->base + (pwm->hwpwm << 2)); - clk_disable(lpc32xx->clk); + clk_disable_unprepare(lpc32xx->clk); } static const struct pwm_ops lpc32xx_pwm_ops = { @@ -134,7 +119,7 @@ static int lpc32xx_pwm_probe(struct platform_device *pdev) lpc32xx->chip.dev = &pdev->dev; lpc32xx->chip.ops = &lpc32xx_pwm_ops; - lpc32xx->chip.npwm = 2; + lpc32xx->chip.npwm = 1; lpc32xx->chip.base = -1; ret = pwmchip_add(&lpc32xx->chip); diff --git a/drivers/pwm/pwm-lpss.c b/drivers/pwm/pwm-lpss.c index 2504410..295b963 100644 --- a/drivers/pwm/pwm-lpss.c +++ b/drivers/pwm/pwm-lpss.c @@ -13,10 +13,12 @@ * published by the Free Software Foundation. */ +#include <linux/delay.h> #include <linux/io.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/pm_runtime.h> +#include <linux/time.h> #include "pwm-lpss.h" @@ -24,11 +26,8 @@ #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 /* Size of each PWM register space if multiple */ #define PWM_SIZE 0x400 @@ -36,13 +35,14 @@ struct pwm_lpss_chip { struct pwm_chip chip; void __iomem *regs; - unsigned long clk_rate; + const struct pwm_lpss_boardinfo *info; }; /* BayTrail */ const struct pwm_lpss_boardinfo pwm_lpss_byt_info = { .clk_rate = 25000000, .npwm = 1, + .base_unit_bits = 16, }; EXPORT_SYMBOL_GPL(pwm_lpss_byt_info); @@ -50,6 +50,7 @@ EXPORT_SYMBOL_GPL(pwm_lpss_byt_info); const struct pwm_lpss_boardinfo pwm_lpss_bsw_info = { .clk_rate = 19200000, .npwm = 1, + .base_unit_bits = 16, }; EXPORT_SYMBOL_GPL(pwm_lpss_bsw_info); @@ -57,6 +58,7 @@ EXPORT_SYMBOL_GPL(pwm_lpss_bsw_info); const struct pwm_lpss_boardinfo pwm_lpss_bxt_info = { .clk_rate = 19200000, .npwm = 4, + .base_unit_bits = 22, }; EXPORT_SYMBOL_GPL(pwm_lpss_bxt_info); @@ -79,28 +81,37 @@ static inline void pwm_lpss_write(const struct pwm_device *pwm, u32 value) writel(value, lpwm->regs + pwm->hwpwm * PWM_SIZE + PWM); } +static void pwm_lpss_update(struct pwm_device *pwm) +{ + pwm_lpss_write(pwm, pwm_lpss_read(pwm) | PWM_SW_UPDATE); + /* Give it some time to propagate */ + usleep_range(10, 50); +} + 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; + unsigned long c, base_unit_range; + unsigned long long base_unit, freq = NSEC_PER_SEC; u32 ctrl; do_div(freq, period_ns); - /* The equation is: base_unit = ((freq / c) * 65536) + correction */ - base_unit = freq * 65536; + /* + * The equation is: + * base_unit = ((freq / c) * base_unit_range) + correction + */ + base_unit_range = BIT(lpwm->info->base_unit_bits); + base_unit = freq * base_unit_range; - c = lpwm->clk_rate; + c = lpwm->info->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; @@ -109,13 +120,20 @@ static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm, pm_runtime_get_sync(chip->dev); ctrl = pwm_lpss_read(pwm); - ctrl &= ~(PWM_BASE_UNIT_MASK | PWM_ON_TIME_DIV_MASK); - ctrl |= (u16) base_unit << PWM_BASE_UNIT_SHIFT; + ctrl &= ~PWM_ON_TIME_DIV_MASK; + ctrl &= ~((base_unit_range - 1) << PWM_BASE_UNIT_SHIFT); + base_unit &= (base_unit_range - 1); + ctrl |= (u32) base_unit << PWM_BASE_UNIT_SHIFT; ctrl |= on_time_div; - /* request PWM to update on next cycle */ - ctrl |= PWM_SW_UPDATE; pwm_lpss_write(pwm, ctrl); + /* + * If the PWM is already enabled we need to notify the hardware + * about the change by setting PWM_SW_UPDATE. + */ + if (pwm_is_enabled(pwm)) + pwm_lpss_update(pwm); + pm_runtime_put(chip->dev); return 0; @@ -124,6 +142,12 @@ static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm, static int pwm_lpss_enable(struct pwm_chip *chip, struct pwm_device *pwm) { pm_runtime_get_sync(chip->dev); + + /* + * Hardware must first see PWM_SW_UPDATE before the PWM can be + * enabled. + */ + pwm_lpss_update(pwm); pwm_lpss_write(pwm, pwm_lpss_read(pwm) | PWM_ENABLE); return 0; } @@ -135,7 +159,6 @@ static void pwm_lpss_disable(struct pwm_chip *chip, struct pwm_device *pwm) } static const struct pwm_ops pwm_lpss_ops = { - .free = pwm_lpss_disable, .config = pwm_lpss_config, .enable = pwm_lpss_enable, .disable = pwm_lpss_disable, @@ -156,7 +179,7 @@ struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r, if (IS_ERR(lpwm->regs)) return ERR_CAST(lpwm->regs); - lpwm->clk_rate = info->clk_rate; + lpwm->info = info; lpwm->chip.dev = dev; lpwm->chip.ops = &pwm_lpss_ops; lpwm->chip.base = -1; diff --git a/drivers/pwm/pwm-lpss.h b/drivers/pwm/pwm-lpss.h index e8cf337..04766e0 100644 --- a/drivers/pwm/pwm-lpss.h +++ b/drivers/pwm/pwm-lpss.h @@ -21,6 +21,7 @@ struct pwm_lpss_chip; struct pwm_lpss_boardinfo { unsigned long clk_rate; unsigned int npwm; + unsigned long base_unit_bits; }; extern const struct pwm_lpss_boardinfo pwm_lpss_byt_info; diff --git a/drivers/pwm/pwm-omap-dmtimer.c b/drivers/pwm/pwm-omap-dmtimer.c new file mode 100644 index 0000000..826634e --- /dev/null +++ b/drivers/pwm/pwm-omap-dmtimer.c @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2015 Neil Armstrong <narmstrong@baylibre.com> + * Copyright (c) 2014 Joachim Eastwood <manabian@gmail.com> + * Copyright (c) 2012 NeilBrown <neilb@suse.de> + * Heavily based on earlier code which is: + * Copyright (c) 2010 Grant Erickson <marathon96@gmail.com> + * + * Also based on pwm-samsung.c + * + * 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. + * + * Description: + * This file is the core OMAP support for the generic, Linux + * PWM driver / controller, using the OMAP's dual-mode timers. + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_data/pwm_omap_dmtimer.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/pwm.h> +#include <linux/slab.h> +#include <linux/time.h> + +#define DM_TIMER_LOAD_MIN 0xfffffffe + +struct pwm_omap_dmtimer_chip { + struct pwm_chip chip; + struct mutex mutex; + pwm_omap_dmtimer *dm_timer; + struct pwm_omap_dmtimer_pdata *pdata; + struct platform_device *dm_timer_pdev; +}; + +static inline struct pwm_omap_dmtimer_chip * +to_pwm_omap_dmtimer_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct pwm_omap_dmtimer_chip, chip); +} + +static int pwm_omap_dmtimer_calc_value(unsigned long clk_rate, int ns) +{ + u64 c = (u64)clk_rate * ns; + + do_div(c, NSEC_PER_SEC); + + return DM_TIMER_LOAD_MIN - c; +} + +static void pwm_omap_dmtimer_start(struct pwm_omap_dmtimer_chip *omap) +{ + /* + * According to OMAP 4 TRM section 22.2.4.10 the counter should be + * started at 0xFFFFFFFE when overflow and match is used to ensure + * that the PWM line is toggled on the first event. + * + * Note that omap_dm_timer_enable/disable is for register access and + * not the timer counter itself. + */ + omap->pdata->enable(omap->dm_timer); + omap->pdata->write_counter(omap->dm_timer, DM_TIMER_LOAD_MIN); + omap->pdata->disable(omap->dm_timer); + + omap->pdata->start(omap->dm_timer); +} + +static int pwm_omap_dmtimer_enable(struct pwm_chip *chip, + struct pwm_device *pwm) +{ + struct pwm_omap_dmtimer_chip *omap = to_pwm_omap_dmtimer_chip(chip); + + mutex_lock(&omap->mutex); + pwm_omap_dmtimer_start(omap); + mutex_unlock(&omap->mutex); + + return 0; +} + +static void pwm_omap_dmtimer_disable(struct pwm_chip *chip, + struct pwm_device *pwm) +{ + struct pwm_omap_dmtimer_chip *omap = to_pwm_omap_dmtimer_chip(chip); + + mutex_lock(&omap->mutex); + omap->pdata->stop(omap->dm_timer); + mutex_unlock(&omap->mutex); +} + +static int pwm_omap_dmtimer_config(struct pwm_chip *chip, + struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct pwm_omap_dmtimer_chip *omap = to_pwm_omap_dmtimer_chip(chip); + int load_value, match_value; + struct clk *fclk; + unsigned long clk_rate; + bool timer_active; + + dev_dbg(chip->dev, "duty cycle: %d, period %d\n", duty_ns, period_ns); + + mutex_lock(&omap->mutex); + if (duty_ns == pwm_get_duty_cycle(pwm) && + period_ns == pwm_get_period(pwm)) { + /* No change - don't cause any transients. */ + mutex_unlock(&omap->mutex); + return 0; + } + + fclk = omap->pdata->get_fclk(omap->dm_timer); + if (!fclk) { + dev_err(chip->dev, "invalid pmtimer fclk\n"); + mutex_unlock(&omap->mutex); + return -EINVAL; + } + + clk_rate = clk_get_rate(fclk); + if (!clk_rate) { + dev_err(chip->dev, "invalid pmtimer fclk rate\n"); + mutex_unlock(&omap->mutex); + return -EINVAL; + } + + dev_dbg(chip->dev, "clk rate: %luHz\n", clk_rate); + + /* + * Calculate the appropriate load and match values based on the + * specified period and duty cycle. The load value determines the + * cycle time and the match value determines the duty cycle. + */ + load_value = pwm_omap_dmtimer_calc_value(clk_rate, period_ns); + match_value = pwm_omap_dmtimer_calc_value(clk_rate, + period_ns - duty_ns); + + /* + * We MUST stop the associated dual-mode timer before attempting to + * write its registers, but calls to omap_dm_timer_start/stop must + * be balanced so check if timer is active before calling timer_stop. + */ + timer_active = pm_runtime_active(&omap->dm_timer_pdev->dev); + if (timer_active) + omap->pdata->stop(omap->dm_timer); + + omap->pdata->set_load(omap->dm_timer, true, load_value); + omap->pdata->set_match(omap->dm_timer, true, match_value); + + dev_dbg(chip->dev, "load value: %#08x (%d), match value: %#08x (%d)\n", + load_value, load_value, match_value, match_value); + + omap->pdata->set_pwm(omap->dm_timer, + pwm->polarity == PWM_POLARITY_INVERSED, + true, + PWM_OMAP_DMTIMER_TRIGGER_OVERFLOW_AND_COMPARE); + + /* If config was called while timer was running it must be reenabled. */ + if (timer_active) + pwm_omap_dmtimer_start(omap); + + mutex_unlock(&omap->mutex); + + return 0; +} + +static int pwm_omap_dmtimer_set_polarity(struct pwm_chip *chip, + struct pwm_device *pwm, + enum pwm_polarity polarity) +{ + struct pwm_omap_dmtimer_chip *omap = to_pwm_omap_dmtimer_chip(chip); + + /* + * PWM core will not call set_polarity while PWM is enabled so it's + * safe to reconfigure the timer here without stopping it first. + */ + mutex_lock(&omap->mutex); + omap->pdata->set_pwm(omap->dm_timer, + polarity == PWM_POLARITY_INVERSED, + true, + PWM_OMAP_DMTIMER_TRIGGER_OVERFLOW_AND_COMPARE); + mutex_unlock(&omap->mutex); + + return 0; +} + +static const struct pwm_ops pwm_omap_dmtimer_ops = { + .enable = pwm_omap_dmtimer_enable, + .disable = pwm_omap_dmtimer_disable, + .config = pwm_omap_dmtimer_config, + .set_polarity = pwm_omap_dmtimer_set_polarity, + .owner = THIS_MODULE, +}; + +static int pwm_omap_dmtimer_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *timer; + struct pwm_omap_dmtimer_chip *omap; + struct pwm_omap_dmtimer_pdata *pdata; + pwm_omap_dmtimer *dm_timer; + u32 prescaler; + int status; + + pdata = dev_get_platdata(&pdev->dev); + if (!pdata) { + dev_err(&pdev->dev, "Missing dmtimer platform data\n"); + return -EINVAL; + } + + if (!pdata->request_by_node || + !pdata->free || + !pdata->enable || + !pdata->disable || + !pdata->get_fclk || + !pdata->start || + !pdata->stop || + !pdata->set_load || + !pdata->set_match || + !pdata->set_pwm || + !pdata->set_prescaler || + !pdata->write_counter) { + dev_err(&pdev->dev, "Incomplete dmtimer pdata structure\n"); + return -EINVAL; + } + + timer = of_parse_phandle(np, "ti,timers", 0); + if (!timer) + return -ENODEV; + + if (!of_get_property(timer, "ti,timer-pwm", NULL)) { + dev_err(&pdev->dev, "Missing ti,timer-pwm capability\n"); + return -ENODEV; + } + + dm_timer = pdata->request_by_node(timer); + if (!dm_timer) + return -EPROBE_DEFER; + + omap = devm_kzalloc(&pdev->dev, sizeof(*omap), GFP_KERNEL); + if (!omap) { + pdata->free(dm_timer); + return -ENOMEM; + } + + omap->pdata = pdata; + omap->dm_timer = dm_timer; + + omap->dm_timer_pdev = of_find_device_by_node(timer); + if (!omap->dm_timer_pdev) { + dev_err(&pdev->dev, "Unable to find timer pdev\n"); + omap->pdata->free(dm_timer); + return -EINVAL; + } + + /* + * Ensure that the timer is stopped before we allow PWM core to call + * pwm_enable. + */ + if (pm_runtime_active(&omap->dm_timer_pdev->dev)) + omap->pdata->stop(omap->dm_timer); + + /* setup dmtimer prescaler */ + if (!of_property_read_u32(pdev->dev.of_node, "ti,prescaler", + &prescaler)) + omap->pdata->set_prescaler(omap->dm_timer, prescaler); + + omap->chip.dev = &pdev->dev; + omap->chip.ops = &pwm_omap_dmtimer_ops; + omap->chip.base = -1; + omap->chip.npwm = 1; + omap->chip.of_xlate = of_pwm_xlate_with_flags; + omap->chip.of_pwm_n_cells = 3; + + mutex_init(&omap->mutex); + + status = pwmchip_add(&omap->chip); + if (status < 0) { + dev_err(&pdev->dev, "failed to register PWM\n"); + omap->pdata->free(omap->dm_timer); + return status; + } + + platform_set_drvdata(pdev, omap); + + return 0; +} + +static int pwm_omap_dmtimer_remove(struct platform_device *pdev) +{ + struct pwm_omap_dmtimer_chip *omap = platform_get_drvdata(pdev); + + if (pm_runtime_active(&omap->dm_timer_pdev->dev)) + omap->pdata->stop(omap->dm_timer); + + omap->pdata->free(omap->dm_timer); + + mutex_destroy(&omap->mutex); + + return pwmchip_remove(&omap->chip); +} + +static const struct of_device_id pwm_omap_dmtimer_of_match[] = { + {.compatible = "ti,omap-dmtimer-pwm"}, + {} +}; +MODULE_DEVICE_TABLE(of, pwm_omap_dmtimer_of_match); + +static struct platform_driver pwm_omap_dmtimer_driver = { + .driver = { + .name = "omap-dmtimer-pwm", + .of_match_table = of_match_ptr(pwm_omap_dmtimer_of_match), + }, + .probe = pwm_omap_dmtimer_probe, + .remove = pwm_omap_dmtimer_remove, +}; +module_platform_driver(pwm_omap_dmtimer_driver); + +MODULE_AUTHOR("Grant Erickson <marathon96@gmail.com>"); +MODULE_AUTHOR("NeilBrown <neilb@suse.de>"); +MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("OMAP PWM Driver using Dual-mode Timers"); diff --git a/drivers/pwm/pwm-rcar.c b/drivers/pwm/pwm-rcar.c index 6e99a63..7b8ac06 100644 --- a/drivers/pwm/pwm-rcar.c +++ b/drivers/pwm/pwm-rcar.c @@ -81,7 +81,7 @@ static int rcar_pwm_get_clock_division(struct rcar_pwm_chip *rp, int period_ns) max = (unsigned long long)NSEC_PER_SEC * RCAR_PWM_MAX_CYCLE * (1 << div); do_div(max, clk_rate); - if (period_ns < max) + if (period_ns <= max) break; } diff --git a/include/linux/platform_data/pwm_omap_dmtimer.h b/include/linux/platform_data/pwm_omap_dmtimer.h new file mode 100644 index 0000000..5938421 --- /dev/null +++ b/include/linux/platform_data/pwm_omap_dmtimer.h @@ -0,0 +1,69 @@ +/* + * include/linux/platform_data/pwm_omap_dmtimer.h + * + * OMAP Dual-Mode Timer PWM platform data + * + * Copyright (C) 2010 Texas Instruments Incorporated - http://www.ti.com/ + * Tarun Kanti DebBarma <tarun.kanti@ti.com> + * Thara Gopinath <thara@ti.com> + * + * Platform device conversion and hwmod support. + * + * Copyright (C) 2005 Nokia Corporation + * Author: Lauri Leukkunen <lauri.leukkunen@nokia.com> + * PWM and clock framework support by Timo Teras. + * + * 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * 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. + */ + +#ifndef __PWM_OMAP_DMTIMER_PDATA_H +#define __PWM_OMAP_DMTIMER_PDATA_H + +/* trigger types */ +#define PWM_OMAP_DMTIMER_TRIGGER_NONE 0x00 +#define PWM_OMAP_DMTIMER_TRIGGER_OVERFLOW 0x01 +#define PWM_OMAP_DMTIMER_TRIGGER_OVERFLOW_AND_COMPARE 0x02 + +struct omap_dm_timer; +typedef struct omap_dm_timer pwm_omap_dmtimer; + +struct pwm_omap_dmtimer_pdata { + pwm_omap_dmtimer *(*request_by_node)(struct device_node *np); + int (*free)(pwm_omap_dmtimer *timer); + + void (*enable)(pwm_omap_dmtimer *timer); + void (*disable)(pwm_omap_dmtimer *timer); + + struct clk *(*get_fclk)(pwm_omap_dmtimer *timer); + + int (*start)(pwm_omap_dmtimer *timer); + int (*stop)(pwm_omap_dmtimer *timer); + + int (*set_load)(pwm_omap_dmtimer *timer, int autoreload, + unsigned int value); + int (*set_match)(pwm_omap_dmtimer *timer, int enable, + unsigned int match); + int (*set_pwm)(pwm_omap_dmtimer *timer, int def_on, + int toggle, int trigger); + int (*set_prescaler)(pwm_omap_dmtimer *timer, int prescaler); + + int (*write_counter)(pwm_omap_dmtimer *timer, unsigned int value); +}; + +#endif /* __PWM_OMAP_DMTIMER_PDATA_H */ |