From 140fd977dc46bc750258f082cdf1cfea79dc1d14 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 21 Dec 2011 08:04:13 +0100 Subject: pwm: tegra: Add device tree support Add auxdata to instantiate the PWFM controller from a device tree, include the corresponding nodes in the dtsi files for Tegra 20 and Tegra 30 and add binding documentation. Acked-by: Stephen Warren Signed-off-by: Thierry Reding --- arch/arm/boot/dts/tegra20.dtsi | 6 ++++++ arch/arm/boot/dts/tegra30.dtsi | 6 ++++++ arch/arm/mach-tegra/board-dt-tegra20.c | 1 + arch/arm/mach-tegra/board-dt-tegra30.c | 3 +++ 4 files changed, 16 insertions(+) (limited to 'arch') diff --git a/arch/arm/boot/dts/tegra20.dtsi b/arch/arm/boot/dts/tegra20.dtsi index c417d67e902..846af573f64 100644 --- a/arch/arm/boot/dts/tegra20.dtsi +++ b/arch/arm/boot/dts/tegra20.dtsi @@ -123,6 +123,12 @@ status = "disable"; }; + pwm { + compatible = "nvidia,tegra20-pwm"; + reg = <0x7000a000 0x100>; + #pwm-cells = <2>; + }; + i2c@7000c000 { compatible = "nvidia,tegra20-i2c"; reg = <0x7000c000 0x100>; diff --git a/arch/arm/boot/dts/tegra30.dtsi b/arch/arm/boot/dts/tegra30.dtsi index 2dcc09e784b..f7f428e81e3 100644 --- a/arch/arm/boot/dts/tegra30.dtsi +++ b/arch/arm/boot/dts/tegra30.dtsi @@ -117,6 +117,12 @@ status = "disable"; }; + pwm { + compatible = "nvidia,tegra30-pwm", "nvidia,tegra20-pwm"; + reg = <0x7000a000 0x100>; + #pwm-cells = <2>; + }; + i2c@7000c000 { compatible = "nvidia,tegra30-i2c", "nvidia,tegra20-i2c"; reg = <0x7000c000 0x100>; diff --git a/arch/arm/mach-tegra/board-dt-tegra20.c b/arch/arm/mach-tegra/board-dt-tegra20.c index eb7249db50a..962f1a1b12e 100644 --- a/arch/arm/mach-tegra/board-dt-tegra20.c +++ b/arch/arm/mach-tegra/board-dt-tegra20.c @@ -64,6 +64,7 @@ struct of_dev_auxdata tegra20_auxdata_lookup[] __initdata = { &tegra_ehci2_pdata), OF_DEV_AUXDATA("nvidia,tegra20-ehci", TEGRA_USB3_BASE, "tegra-ehci.2", &tegra_ehci3_pdata), + OF_DEV_AUXDATA("nvidia,tegra20-pwm", TEGRA_PWFM_BASE, "tegra-pwm", NULL), {} }; diff --git a/arch/arm/mach-tegra/board-dt-tegra30.c b/arch/arm/mach-tegra/board-dt-tegra30.c index 4f76fa7a5da..ffc6cae3eb2 100644 --- a/arch/arm/mach-tegra/board-dt-tegra30.c +++ b/arch/arm/mach-tegra/board-dt-tegra30.c @@ -33,6 +33,8 @@ #include #include +#include + #include "board.h" #include "clock.h" @@ -52,6 +54,7 @@ struct of_dev_auxdata tegra30_auxdata_lookup[] __initdata = { OF_DEV_AUXDATA("nvidia,tegra20-i2c", 0x7000C700, "tegra-i2c.3", NULL), OF_DEV_AUXDATA("nvidia,tegra20-i2c", 0x7000D000, "tegra-i2c.4", NULL), OF_DEV_AUXDATA("nvidia,tegra30-ahub", 0x70080000, "tegra30-ahub", NULL), + OF_DEV_AUXDATA("nvidia,tegra30-pwm", TEGRA_PWFM_BASE, "tegra-pwm", NULL), {} }; -- cgit v1.2.3-70-g09d2 From a4315e3c11f97ea0fa602184fa310161309657df Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 2 Jan 2012 20:53:50 +0100 Subject: pwm: Move Blackfin PWM driver to PWM framework This commit moves the Blackfin PWM driver to the drivers/pwm sub- directory and converts it to register with the new PWM framework. Signed-off-by: Thierry Reding --- arch/blackfin/Kconfig | 10 --- arch/blackfin/kernel/Makefile | 1 - arch/blackfin/kernel/pwm.c | 100 -------------------------- drivers/pwm/Kconfig | 9 +++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-bfin.c | 164 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 174 insertions(+), 111 deletions(-) delete mode 100644 arch/blackfin/kernel/pwm.c create mode 100644 drivers/pwm/pwm-bfin.c (limited to 'arch') diff --git a/arch/blackfin/Kconfig b/arch/blackfin/Kconfig index fef96f47876..3307d2e7368 100644 --- a/arch/blackfin/Kconfig +++ b/arch/blackfin/Kconfig @@ -996,16 +996,6 @@ config BFIN_GPTIMERS To compile this driver as a module, choose M here: the module will be called gptimers. -config HAVE_PWM - tristate "Enable PWM API support" - depends on BFIN_GPTIMERS - help - Enable support for the Pulse Width Modulation framework (as - found in linux/pwm.h). - - To compile this driver as a module, choose M here: the module - will be called pwm. - choice prompt "Uncached DMA region" default DMA_UNCACHED_1M diff --git a/arch/blackfin/kernel/Makefile b/arch/blackfin/kernel/Makefile index 08e6625106b..735f24e0742 100644 --- a/arch/blackfin/kernel/Makefile +++ b/arch/blackfin/kernel/Makefile @@ -21,7 +21,6 @@ obj-$(CONFIG_FUNCTION_TRACER) += ftrace-entry.o obj-$(CONFIG_FUNCTION_GRAPH_TRACER) += ftrace.o CFLAGS_REMOVE_ftrace.o = -pg -obj-$(CONFIG_HAVE_PWM) += pwm.o obj-$(CONFIG_IPIPE) += ipipe.o obj-$(CONFIG_BFIN_GPTIMERS) += gptimers.o obj-$(CONFIG_CPLB_INFO) += cplbinfo.o diff --git a/arch/blackfin/kernel/pwm.c b/arch/blackfin/kernel/pwm.c deleted file mode 100644 index 33f5942733b..00000000000 --- a/arch/blackfin/kernel/pwm.c +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Blackfin Pulse Width Modulation (PWM) core - * - * Copyright (c) 2011 Analog Devices Inc. - * - * Licensed under the GPL-2 or later. - */ - -#include -#include -#include - -#include -#include - -struct pwm_device { - unsigned id; - unsigned short pin; -}; - -static const unsigned short pwm_to_gptimer_per[] = { - P_TMR0, P_TMR1, P_TMR2, P_TMR3, P_TMR4, P_TMR5, - P_TMR6, P_TMR7, P_TMR8, P_TMR9, P_TMR10, P_TMR11, -}; - -struct pwm_device *pwm_request(int pwm_id, const char *label) -{ - struct pwm_device *pwm; - int ret; - - /* XXX: pwm_id really should be unsigned */ - if (pwm_id < 0) - return NULL; - - pwm = kzalloc(sizeof(*pwm), GFP_KERNEL); - if (!pwm) - return pwm; - - pwm->id = pwm_id; - if (pwm->id >= ARRAY_SIZE(pwm_to_gptimer_per)) - goto err; - - pwm->pin = pwm_to_gptimer_per[pwm->id]; - ret = peripheral_request(pwm->pin, label); - if (ret) - goto err; - - return pwm; - err: - kfree(pwm); - return NULL; -} -EXPORT_SYMBOL(pwm_request); - -void pwm_free(struct pwm_device *pwm) -{ - peripheral_free(pwm->pin); - kfree(pwm); -} -EXPORT_SYMBOL(pwm_free); - -int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) -{ - unsigned long period, duty; - unsigned long long val; - - if (duty_ns < 0 || duty_ns > period_ns) - return -EINVAL; - - val = (unsigned long long)get_sclk() * period_ns; - do_div(val, NSEC_PER_SEC); - period = val; - - val = (unsigned long long)period * duty_ns; - do_div(val, period_ns); - duty = period - val; - - if (duty >= period) - duty = period - 1; - - set_gptimer_config(pwm->id, TIMER_MODE_PWM | TIMER_PERIOD_CNT); - set_gptimer_pwidth(pwm->id, duty); - set_gptimer_period(pwm->id, period); - - return 0; -} -EXPORT_SYMBOL(pwm_config); - -int pwm_enable(struct pwm_device *pwm) -{ - enable_gptimer(pwm->id); - return 0; -} -EXPORT_SYMBOL(pwm_enable); - -void pwm_disable(struct pwm_device *pwm) -{ - disable_gptimer(pwm->id); -} -EXPORT_SYMBOL(pwm_disable); diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index bda6f23af53..eb540426813 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -9,6 +9,15 @@ menuconfig PWM if PWM +config PWM_BFIN + tristate "Blackfin PWM support" + depends on BFIN_GPTIMERS + help + Generic PWM framework driver for Blackfin. + + To compile this driver as a module, choose M here: the module + will be called pwm-bfin. + config PWM_TEGRA tristate "NVIDIA Tegra PWM support" depends on ARCH_TEGRA diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 12300f5e901..251b8d22435 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -1,2 +1,3 @@ obj-$(CONFIG_PWM) += core.o +obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o diff --git a/drivers/pwm/pwm-bfin.c b/drivers/pwm/pwm-bfin.c new file mode 100644 index 00000000000..a0c6bf9070e --- /dev/null +++ b/drivers/pwm/pwm-bfin.c @@ -0,0 +1,164 @@ +/* + * Blackfin Pulse Width Modulation (PWM) core + * + * Copyright (c) 2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include + +#include +#include + +struct bfin_pwm_chip { + struct pwm_chip chip; +}; + +struct bfin_pwm { + unsigned short pin; +}; + +static const unsigned short pwm_to_gptimer_per[] = { + P_TMR0, P_TMR1, P_TMR2, P_TMR3, P_TMR4, P_TMR5, + P_TMR6, P_TMR7, P_TMR8, P_TMR9, P_TMR10, P_TMR11, +}; + +static int bfin_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct bfin_pwm *priv; + int ret; + + if (pwm->hwpwm >= ARRAY_SIZE(pwm_to_gptimer_per)) + return -EINVAL; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->pin = pwm_to_gptimer_per[pwm->hwpwm]; + + ret = peripheral_request(priv->pin, NULL); + if (ret) { + kfree(priv); + return ret; + } + + pwm_set_chip_data(pwm, priv); + + return 0; +} + +static void bfin_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct bfin_pwm *priv = pwm_get_chip_data(pwm); + + if (priv) { + peripheral_free(priv->pin); + kfree(priv); + } +} + +static int bfin_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct bfin_pwm *priv = pwm_get_chip_data(pwm); + unsigned long period, duty; + unsigned long long val; + + if (duty_ns < 0 || duty_ns > period_ns) + return -EINVAL; + + val = (unsigned long long)get_sclk() * period_ns; + do_div(val, NSEC_PER_SEC); + period = val; + + val = (unsigned long long)period * duty_ns; + do_div(val, period_ns); + duty = period - val; + + if (duty >= period) + duty = period - 1; + + set_gptimer_config(priv->pin, TIMER_MODE_PWM | TIMER_PERIOD_CNT); + set_gptimer_pwidth(priv->pin, duty); + set_gptimer_period(priv->pin, period); + + return 0; +} + +static int bfin_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct bfin_pwm *priv = pwm_get_chip_data(pwm); + + enable_gptimer(priv->pin); + + return 0; +} + +static void bfin_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct bfin_pwm *priv = pwm_get_chip_data(pwm); + + disable_gptimer(priv->pin); +} + +static struct pwm_ops bfin_pwm_ops = { + .request = bfin_pwm_request, + .free = bfin_pwm_free, + .config = bfin_pwm_config, + .enable = bfin_pwm_enable, + .disable = bfin_pwm_disable, + .owner = THIS_MODULE, +}; + +static int bfin_pwm_probe(struct platform_device *pdev) +{ + struct bfin_pwm_chip *pwm; + int ret; + + pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); + if (!pwm) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, pwm); + + pwm->chip.dev = &pdev->dev; + pwm->chip.ops = &bfin_pwm_ops; + pwm->chip.base = -1; + pwm->chip.npwm = 12; + + ret = pwmchip_add(&pwm->chip); + if (ret < 0) { + dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int __devexit bfin_pwm_remove(struct platform_device *pdev) +{ + struct bfin_pwm_chip *pwm = platform_get_drvdata(pdev); + + pwmchip_remove(&pwm->chip); + + return 0; +} + +static struct platform_driver bfin_pwm_driver = { + .driver = { + .name = "bfin-pwm", + }, + .probe = bfin_pwm_probe, + .remove = __devexit_p(bfin_pwm_remove), +}; + +module_platform_driver(bfin_pwm_driver); + +MODULE_LICENSE("GPL"); -- cgit v1.2.3-70-g09d2 From 17b2b4780f879a559b8dc96c170dd6df98fe9ab3 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 2 Jan 2012 21:22:38 +0100 Subject: pwm: Move PXA PWM driver to PWM framework This commit moves the PXA PWM driver to the drivers/pwm subdirectory and converts it to use the new PWM framework. Signed-off-by: Thierry Reding --- arch/arm/plat-pxa/Makefile | 1 - arch/arm/plat-pxa/pwm.c | 304 --------------------------------------------- drivers/pwm/Kconfig | 9 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-pxa.c | 248 ++++++++++++++++++++++++++++++++++++ 5 files changed, 258 insertions(+), 305 deletions(-) delete mode 100644 arch/arm/plat-pxa/pwm.c create mode 100644 drivers/pwm/pwm-pxa.c (limited to 'arch') diff --git a/arch/arm/plat-pxa/Makefile b/arch/arm/plat-pxa/Makefile index f302d048392..af8e484001e 100644 --- a/arch/arm/plat-pxa/Makefile +++ b/arch/arm/plat-pxa/Makefile @@ -8,5 +8,4 @@ obj-$(CONFIG_PXA3xx) += mfp.o obj-$(CONFIG_PXA95x) += mfp.o obj-$(CONFIG_ARCH_MMP) += mfp.o -obj-$(CONFIG_HAVE_PWM) += pwm.o obj-$(CONFIG_PXA_SSP) += ssp.o diff --git a/arch/arm/plat-pxa/pwm.c b/arch/arm/plat-pxa/pwm.c deleted file mode 100644 index ef32686feef..00000000000 --- a/arch/arm/plat-pxa/pwm.c +++ /dev/null @@ -1,304 +0,0 @@ -/* - * linux/arch/arm/mach-pxa/pwm.c - * - * simple driver for PWM (Pulse Width Modulator) controller - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * 2008-02-13 initial version - * eric miao - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#define HAS_SECONDARY_PWM 0x10 -#define PWM_ID_BASE(d) ((d) & 0xf) - -static const struct platform_device_id pwm_id_table[] = { - /* PWM has_secondary_pwm? */ - { "pxa25x-pwm", 0 }, - { "pxa27x-pwm", 0 | HAS_SECONDARY_PWM }, - { "pxa168-pwm", 1 }, - { "pxa910-pwm", 1 }, - { }, -}; -MODULE_DEVICE_TABLE(platform, pwm_id_table); - -/* PWM registers and bits definitions */ -#define PWMCR (0x00) -#define PWMDCR (0x04) -#define PWMPCR (0x08) - -#define PWMCR_SD (1 << 6) -#define PWMDCR_FD (1 << 10) - -struct pwm_device { - struct list_head node; - struct pwm_device *secondary; - struct platform_device *pdev; - - const char *label; - struct clk *clk; - int clk_enabled; - void __iomem *mmio_base; - - unsigned int use_count; - unsigned int pwm_id; -}; - -/* - * period_ns = 10^9 * (PRESCALE + 1) * (PV + 1) / PWM_CLK_RATE - * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE - */ -int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) -{ - unsigned long long c; - unsigned long period_cycles, prescale, pv, dc; - - if (pwm == NULL || period_ns == 0 || duty_ns > period_ns) - return -EINVAL; - - c = clk_get_rate(pwm->clk); - c = c * period_ns; - do_div(c, 1000000000); - period_cycles = c; - - if (period_cycles < 1) - period_cycles = 1; - prescale = (period_cycles - 1) / 1024; - pv = period_cycles / (prescale + 1) - 1; - - if (prescale > 63) - return -EINVAL; - - if (duty_ns == period_ns) - dc = PWMDCR_FD; - else - dc = (pv + 1) * duty_ns / period_ns; - - /* NOTE: the clock to PWM has to be enabled first - * before writing to the registers - */ - clk_enable(pwm->clk); - __raw_writel(prescale, pwm->mmio_base + PWMCR); - __raw_writel(dc, pwm->mmio_base + PWMDCR); - __raw_writel(pv, pwm->mmio_base + PWMPCR); - clk_disable(pwm->clk); - - return 0; -} -EXPORT_SYMBOL(pwm_config); - -int pwm_enable(struct pwm_device *pwm) -{ - int rc = 0; - - if (!pwm->clk_enabled) { - rc = clk_enable(pwm->clk); - if (!rc) - pwm->clk_enabled = 1; - } - return rc; -} -EXPORT_SYMBOL(pwm_enable); - -void pwm_disable(struct pwm_device *pwm) -{ - if (pwm->clk_enabled) { - clk_disable(pwm->clk); - pwm->clk_enabled = 0; - } -} -EXPORT_SYMBOL(pwm_disable); - -static DEFINE_MUTEX(pwm_lock); -static LIST_HEAD(pwm_list); - -struct pwm_device *pwm_request(int pwm_id, const char *label) -{ - struct pwm_device *pwm; - int found = 0; - - mutex_lock(&pwm_lock); - - list_for_each_entry(pwm, &pwm_list, node) { - if (pwm->pwm_id == pwm_id) { - found = 1; - break; - } - } - - if (found) { - if (pwm->use_count == 0) { - pwm->use_count++; - pwm->label = label; - } else - pwm = ERR_PTR(-EBUSY); - } else - pwm = ERR_PTR(-ENOENT); - - mutex_unlock(&pwm_lock); - return pwm; -} -EXPORT_SYMBOL(pwm_request); - -void pwm_free(struct pwm_device *pwm) -{ - mutex_lock(&pwm_lock); - - if (pwm->use_count) { - pwm->use_count--; - pwm->label = NULL; - } else - pr_warning("PWM device already freed\n"); - - mutex_unlock(&pwm_lock); -} -EXPORT_SYMBOL(pwm_free); - -static inline void __add_pwm(struct pwm_device *pwm) -{ - mutex_lock(&pwm_lock); - list_add_tail(&pwm->node, &pwm_list); - mutex_unlock(&pwm_lock); -} - -static int __devinit pwm_probe(struct platform_device *pdev) -{ - const struct platform_device_id *id = platform_get_device_id(pdev); - struct pwm_device *pwm, *secondary = NULL; - struct resource *r; - int ret = 0; - - pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL); - if (pwm == NULL) { - dev_err(&pdev->dev, "failed to allocate memory\n"); - return -ENOMEM; - } - - pwm->clk = clk_get(&pdev->dev, NULL); - if (IS_ERR(pwm->clk)) { - ret = PTR_ERR(pwm->clk); - goto err_free; - } - pwm->clk_enabled = 0; - - pwm->use_count = 0; - pwm->pwm_id = PWM_ID_BASE(id->driver_data) + pdev->id; - pwm->pdev = pdev; - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (r == NULL) { - dev_err(&pdev->dev, "no memory resource defined\n"); - ret = -ENODEV; - goto err_free_clk; - } - - r = request_mem_region(r->start, resource_size(r), pdev->name); - if (r == NULL) { - dev_err(&pdev->dev, "failed to request memory resource\n"); - ret = -EBUSY; - goto err_free_clk; - } - - pwm->mmio_base = ioremap(r->start, resource_size(r)); - if (pwm->mmio_base == NULL) { - dev_err(&pdev->dev, "failed to ioremap() registers\n"); - ret = -ENODEV; - goto err_free_mem; - } - - if (id->driver_data & HAS_SECONDARY_PWM) { - secondary = kzalloc(sizeof(struct pwm_device), GFP_KERNEL); - if (secondary == NULL) { - ret = -ENOMEM; - goto err_free_mem; - } - - *secondary = *pwm; - pwm->secondary = secondary; - - /* registers for the second PWM has offset of 0x10 */ - secondary->mmio_base = pwm->mmio_base + 0x10; - secondary->pwm_id = pdev->id + 2; - } - - __add_pwm(pwm); - if (secondary) - __add_pwm(secondary); - - platform_set_drvdata(pdev, pwm); - return 0; - -err_free_mem: - release_mem_region(r->start, resource_size(r)); -err_free_clk: - clk_put(pwm->clk); -err_free: - kfree(pwm); - return ret; -} - -static int __devexit pwm_remove(struct platform_device *pdev) -{ - struct pwm_device *pwm; - struct resource *r; - - pwm = platform_get_drvdata(pdev); - if (pwm == NULL) - return -ENODEV; - - mutex_lock(&pwm_lock); - - if (pwm->secondary) { - list_del(&pwm->secondary->node); - kfree(pwm->secondary); - } - - list_del(&pwm->node); - mutex_unlock(&pwm_lock); - - iounmap(pwm->mmio_base); - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - release_mem_region(r->start, resource_size(r)); - - clk_put(pwm->clk); - kfree(pwm); - return 0; -} - -static struct platform_driver pwm_driver = { - .driver = { - .name = "pxa25x-pwm", - .owner = THIS_MODULE, - }, - .probe = pwm_probe, - .remove = __devexit_p(pwm_remove), - .id_table = pwm_id_table, -}; - -static int __init pwm_init(void) -{ - return platform_driver_register(&pwm_driver); -} -arch_initcall(pwm_init); - -static void __exit pwm_exit(void) -{ - platform_driver_unregister(&pwm_driver); -} -module_exit(pwm_exit); - -MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index eb540426813..0ef4f3037c4 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -18,6 +18,15 @@ config PWM_BFIN To compile this driver as a module, choose M here: the module will be called pwm-bfin. +config PWM_PXA + tristate "PXA PWM support" + depends on ARCH_PXA + help + Generic PWM framework driver for PXA. + + To compile this driver as a module, choose M here: the module + will be called pwm-pxa. + config PWM_TEGRA tristate "NVIDIA Tegra PWM support" depends on ARCH_TEGRA diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 251b8d22435..e859c513add 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -1,3 +1,4 @@ obj-$(CONFIG_PWM) += core.o obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o +obj-$(CONFIG_PWM_PXA) += pwm-pxa.o obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o diff --git a/drivers/pwm/pwm-pxa.c b/drivers/pwm/pwm-pxa.c new file mode 100644 index 00000000000..d5c6ce513e2 --- /dev/null +++ b/drivers/pwm/pwm-pxa.c @@ -0,0 +1,248 @@ +/* + * linux/arch/arm/mach-pxa/pwm.c + * + * simple driver for PWM (Pulse Width Modulator) controller + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 2008-02-13 initial version + * eric miao + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define HAS_SECONDARY_PWM 0x10 +#define PWM_ID_BASE(d) ((d) & 0xf) + +static const struct platform_device_id pwm_id_table[] = { + /* PWM has_secondary_pwm? */ + { "pxa25x-pwm", 0 }, + { "pxa27x-pwm", 0 | HAS_SECONDARY_PWM }, + { "pxa168-pwm", 1 }, + { "pxa910-pwm", 1 }, + { }, +}; +MODULE_DEVICE_TABLE(platform, pwm_id_table); + +/* PWM registers and bits definitions */ +#define PWMCR (0x00) +#define PWMDCR (0x04) +#define PWMPCR (0x08) + +#define PWMCR_SD (1 << 6) +#define PWMDCR_FD (1 << 10) + +struct pxa_pwm_chip { + struct pwm_chip chip; + struct device *dev; + + struct clk *clk; + int clk_enabled; + void __iomem *mmio_base; +}; + +static inline struct pxa_pwm_chip *to_pxa_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct pxa_pwm_chip, chip); +} + +/* + * period_ns = 10^9 * (PRESCALE + 1) * (PV + 1) / PWM_CLK_RATE + * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE + */ +static int pxa_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct pxa_pwm_chip *pc = to_pxa_pwm_chip(chip); + unsigned long long c; + unsigned long period_cycles, prescale, pv, dc; + unsigned long offset; + int rc; + + if (period_ns == 0 || duty_ns > period_ns) + return -EINVAL; + + offset = pwm->hwpwm ? 0x10 : 0; + + c = clk_get_rate(pc->clk); + c = c * period_ns; + do_div(c, 1000000000); + period_cycles = c; + + if (period_cycles < 1) + period_cycles = 1; + prescale = (period_cycles - 1) / 1024; + pv = period_cycles / (prescale + 1) - 1; + + if (prescale > 63) + return -EINVAL; + + if (duty_ns == period_ns) + dc = PWMDCR_FD; + else + dc = (pv + 1) * duty_ns / period_ns; + + /* NOTE: the clock to PWM has to be enabled first + * before writing to the registers + */ + rc = clk_prepare_enable(pc->clk); + if (rc < 0) + return rc; + + writel(prescale, pc->mmio_base + offset + PWMCR); + writel(dc, pc->mmio_base + offset + PWMDCR); + writel(pv, pc->mmio_base + offset + PWMPCR); + + clk_disable_unprepare(pc->clk); + return 0; +} + +static int pxa_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pxa_pwm_chip *pc = to_pxa_pwm_chip(chip); + int rc = 0; + + if (!pc->clk_enabled) { + rc = clk_prepare_enable(pc->clk); + if (!rc) + pc->clk_enabled++; + } + return rc; +} + +static void pxa_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pxa_pwm_chip *pc = to_pxa_pwm_chip(chip); + + if (pc->clk_enabled) { + clk_disable_unprepare(pc->clk); + pc->clk_enabled--; + } +} + +static struct pwm_ops pxa_pwm_ops = { + .config = pxa_pwm_config, + .enable = pxa_pwm_enable, + .disable = pxa_pwm_disable, + .owner = THIS_MODULE, +}; + +static int __devinit pwm_probe(struct platform_device *pdev) +{ + const struct platform_device_id *id = platform_get_device_id(pdev); + struct pxa_pwm_chip *pwm; + struct resource *r; + int ret = 0; + + pwm = kzalloc(sizeof(*pwm), GFP_KERNEL); + if (pwm == NULL) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + pwm->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(pwm->clk)) { + ret = PTR_ERR(pwm->clk); + goto err_free; + } + pwm->clk_enabled = 0; + + pwm->chip.dev = &pdev->dev; + pwm->chip.ops = &pxa_pwm_ops; + pwm->chip.base = -1; + pwm->chip.npwm = (id->driver_data & HAS_SECONDARY_PWM) ? 2 : 1; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (r == NULL) { + dev_err(&pdev->dev, "no memory resource defined\n"); + ret = -ENODEV; + goto err_free_clk; + } + + r = request_mem_region(r->start, resource_size(r), pdev->name); + if (r == NULL) { + dev_err(&pdev->dev, "failed to request memory resource\n"); + ret = -EBUSY; + goto err_free_clk; + } + + pwm->mmio_base = ioremap(r->start, resource_size(r)); + if (pwm->mmio_base == NULL) { + dev_err(&pdev->dev, "failed to ioremap() registers\n"); + ret = -ENODEV; + goto err_free_mem; + } + + ret = pwmchip_add(&pwm->chip); + if (ret < 0) { + dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, pwm); + return 0; + +err_free_mem: + release_mem_region(r->start, resource_size(r)); +err_free_clk: + clk_put(pwm->clk); +err_free: + kfree(pwm); + return ret; +} + +static int __devexit pwm_remove(struct platform_device *pdev) +{ + struct pxa_pwm_chip *chip; + struct resource *r; + + chip = platform_get_drvdata(pdev); + if (chip == NULL) + return -ENODEV; + + pwmchip_remove(&chip->chip); + + iounmap(chip->mmio_base); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(r->start, resource_size(r)); + + clk_put(chip->clk); + kfree(chip); + return 0; +} + +static struct platform_driver pwm_driver = { + .driver = { + .name = "pxa25x-pwm", + .owner = THIS_MODULE, + }, + .probe = pwm_probe, + .remove = __devexit_p(pwm_remove), + .id_table = pwm_id_table, +}; + +static int __init pwm_init(void) +{ + return platform_driver_register(&pwm_driver); +} +arch_initcall(pwm_init); + +static void __exit pwm_exit(void) +{ + platform_driver_unregister(&pwm_driver); +} +module_exit(pwm_exit); + +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-70-g09d2 From 29693248edf10830db2ef82b36806e378ff75c67 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 15 Mar 2012 10:04:35 +0100 Subject: ARM i.MX: Move i.MX pwm driver to pwm framework MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the driver to drivers/pwm/ and convert it to use the framework. Signed-off-by: Sascha Hauer [eric@eukrea.com: set chip.dev to prevent probe failure] [eric@eukrea.com: fix pwmchip_add return code test] Signed-off-by: Eric Bénard Signed-off-by: Thierry Reding --- arch/arm/plat-mxc/Kconfig | 6 - arch/arm/plat-mxc/Makefile | 1 - arch/arm/plat-mxc/pwm.c | 306 --------------------------------------------- drivers/pwm/Kconfig | 9 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-imx.c | 267 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 277 insertions(+), 313 deletions(-) delete mode 100644 arch/arm/plat-mxc/pwm.c create mode 100644 drivers/pwm/pwm-imx.c (limited to 'arch') diff --git a/arch/arm/plat-mxc/Kconfig b/arch/arm/plat-mxc/Kconfig index c722f9ce691..baf9064c084 100644 --- a/arch/arm/plat-mxc/Kconfig +++ b/arch/arm/plat-mxc/Kconfig @@ -47,12 +47,6 @@ config MXC_TZIC config MXC_AVIC bool -config MXC_PWM - tristate "Enable PWM driver" - select HAVE_PWM - help - Enable support for the i.MX PWM controller(s). - config MXC_DEBUG_BOARD bool "Enable MXC debug board(for 3-stack)" help diff --git a/arch/arm/plat-mxc/Makefile b/arch/arm/plat-mxc/Makefile index e81290c27c6..46ba8de2fe4 100644 --- a/arch/arm/plat-mxc/Makefile +++ b/arch/arm/plat-mxc/Makefile @@ -11,7 +11,6 @@ obj-$(CONFIG_MXC_AVIC) += avic.o obj-$(CONFIG_IMX_HAVE_IOMUX_V1) += iomux-v1.o obj-$(CONFIG_ARCH_MXC_IOMUX_V3) += iomux-v3.o obj-$(CONFIG_IRAM_ALLOC) += iram_alloc.o -obj-$(CONFIG_MXC_PWM) += pwm.o obj-$(CONFIG_MXC_ULPI) += ulpi.o obj-$(CONFIG_MXC_USE_EPIT) += epit.o obj-$(CONFIG_MXC_DEBUG_BOARD) += 3ds_debugboard.o diff --git a/arch/arm/plat-mxc/pwm.c b/arch/arm/plat-mxc/pwm.c deleted file mode 100644 index c0cab2270dd..00000000000 --- a/arch/arm/plat-mxc/pwm.c +++ /dev/null @@ -1,306 +0,0 @@ -/* - * simple driver for PWM (Pulse Width Modulator) controller - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * Derived from pxa PWM driver by eric miao - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -/* i.MX1 and i.MX21 share the same PWM function block: */ - -#define MX1_PWMC 0x00 /* PWM Control Register */ -#define MX1_PWMS 0x04 /* PWM Sample Register */ -#define MX1_PWMP 0x08 /* PWM Period Register */ - - -/* i.MX27, i.MX31, i.MX35 share the same PWM function block: */ - -#define MX3_PWMCR 0x00 /* PWM Control Register */ -#define MX3_PWMSAR 0x0C /* PWM Sample Register */ -#define MX3_PWMPR 0x10 /* PWM Period Register */ -#define MX3_PWMCR_PRESCALER(x) (((x - 1) & 0xFFF) << 4) -#define MX3_PWMCR_DOZEEN (1 << 24) -#define MX3_PWMCR_WAITEN (1 << 23) -#define MX3_PWMCR_DBGEN (1 << 22) -#define MX3_PWMCR_CLKSRC_IPG_HIGH (2 << 16) -#define MX3_PWMCR_CLKSRC_IPG (1 << 16) -#define MX3_PWMCR_EN (1 << 0) - - - -struct pwm_device { - struct list_head node; - struct platform_device *pdev; - - const char *label; - struct clk *clk; - - int clk_enabled; - void __iomem *mmio_base; - - unsigned int use_count; - unsigned int pwm_id; -}; - -int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) -{ - if (pwm == NULL || period_ns == 0 || duty_ns > period_ns) - return -EINVAL; - - if (!(cpu_is_mx1() || cpu_is_mx21())) { - unsigned long long c; - unsigned long period_cycles, duty_cycles, prescale; - u32 cr; - - c = clk_get_rate(pwm->clk); - c = c * period_ns; - do_div(c, 1000000000); - period_cycles = c; - - prescale = period_cycles / 0x10000 + 1; - - period_cycles /= prescale; - c = (unsigned long long)period_cycles * duty_ns; - do_div(c, period_ns); - duty_cycles = c; - - /* - * according to imx pwm RM, the real period value should be - * PERIOD value in PWMPR plus 2. - */ - if (period_cycles > 2) - period_cycles -= 2; - else - period_cycles = 0; - - writel(duty_cycles, pwm->mmio_base + MX3_PWMSAR); - writel(period_cycles, pwm->mmio_base + MX3_PWMPR); - - cr = MX3_PWMCR_PRESCALER(prescale) | - MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN | - MX3_PWMCR_DBGEN | MX3_PWMCR_EN; - - if (cpu_is_mx25()) - cr |= MX3_PWMCR_CLKSRC_IPG; - else - cr |= MX3_PWMCR_CLKSRC_IPG_HIGH; - - writel(cr, pwm->mmio_base + MX3_PWMCR); - } else if (cpu_is_mx1() || cpu_is_mx21()) { - /* The PWM subsystem allows for exact frequencies. However, - * I cannot connect a scope on my device to the PWM line and - * thus cannot provide the program the PWM controller - * exactly. Instead, I'm relying on the fact that the - * Bootloader (u-boot or WinCE+haret) has programmed the PWM - * function group already. So I'll just modify the PWM sample - * register to follow the ratio of duty_ns vs. period_ns - * accordingly. - * - * This is good enough for programming the brightness of - * the LCD backlight. - * - * The real implementation would divide PERCLK[0] first by - * both the prescaler (/1 .. /128) and then by CLKSEL - * (/2 .. /16). - */ - u32 max = readl(pwm->mmio_base + MX1_PWMP); - u32 p = max * duty_ns / period_ns; - writel(max - p, pwm->mmio_base + MX1_PWMS); - } else { - BUG(); - } - - return 0; -} -EXPORT_SYMBOL(pwm_config); - -int pwm_enable(struct pwm_device *pwm) -{ - int rc = 0; - - if (!pwm->clk_enabled) { - rc = clk_prepare_enable(pwm->clk); - if (!rc) - pwm->clk_enabled = 1; - } - return rc; -} -EXPORT_SYMBOL(pwm_enable); - -void pwm_disable(struct pwm_device *pwm) -{ - writel(0, pwm->mmio_base + MX3_PWMCR); - - if (pwm->clk_enabled) { - clk_disable_unprepare(pwm->clk); - pwm->clk_enabled = 0; - } -} -EXPORT_SYMBOL(pwm_disable); - -static DEFINE_MUTEX(pwm_lock); -static LIST_HEAD(pwm_list); - -struct pwm_device *pwm_request(int pwm_id, const char *label) -{ - struct pwm_device *pwm; - int found = 0; - - mutex_lock(&pwm_lock); - - list_for_each_entry(pwm, &pwm_list, node) { - if (pwm->pwm_id == pwm_id) { - found = 1; - break; - } - } - - if (found) { - if (pwm->use_count == 0) { - pwm->use_count++; - pwm->label = label; - } else - pwm = ERR_PTR(-EBUSY); - } else - pwm = ERR_PTR(-ENOENT); - - mutex_unlock(&pwm_lock); - return pwm; -} -EXPORT_SYMBOL(pwm_request); - -void pwm_free(struct pwm_device *pwm) -{ - mutex_lock(&pwm_lock); - - if (pwm->use_count) { - pwm->use_count--; - pwm->label = NULL; - } else - pr_warning("PWM device already freed\n"); - - mutex_unlock(&pwm_lock); -} -EXPORT_SYMBOL(pwm_free); - -static int __devinit mxc_pwm_probe(struct platform_device *pdev) -{ - struct pwm_device *pwm; - struct resource *r; - int ret = 0; - - pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL); - if (pwm == NULL) { - dev_err(&pdev->dev, "failed to allocate memory\n"); - return -ENOMEM; - } - - pwm->clk = clk_get(&pdev->dev, "pwm"); - - if (IS_ERR(pwm->clk)) { - ret = PTR_ERR(pwm->clk); - goto err_free; - } - - pwm->clk_enabled = 0; - - pwm->use_count = 0; - pwm->pwm_id = pdev->id; - pwm->pdev = pdev; - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (r == NULL) { - dev_err(&pdev->dev, "no memory resource defined\n"); - ret = -ENODEV; - goto err_free_clk; - } - - r = request_mem_region(r->start, resource_size(r), pdev->name); - if (r == NULL) { - dev_err(&pdev->dev, "failed to request memory resource\n"); - ret = -EBUSY; - goto err_free_clk; - } - - pwm->mmio_base = ioremap(r->start, resource_size(r)); - if (pwm->mmio_base == NULL) { - dev_err(&pdev->dev, "failed to ioremap() registers\n"); - ret = -ENODEV; - goto err_free_mem; - } - - mutex_lock(&pwm_lock); - list_add_tail(&pwm->node, &pwm_list); - mutex_unlock(&pwm_lock); - - platform_set_drvdata(pdev, pwm); - return 0; - -err_free_mem: - release_mem_region(r->start, resource_size(r)); -err_free_clk: - clk_put(pwm->clk); -err_free: - kfree(pwm); - return ret; -} - -static int __devexit mxc_pwm_remove(struct platform_device *pdev) -{ - struct pwm_device *pwm; - struct resource *r; - - pwm = platform_get_drvdata(pdev); - if (pwm == NULL) - return -ENODEV; - - mutex_lock(&pwm_lock); - list_del(&pwm->node); - mutex_unlock(&pwm_lock); - - iounmap(pwm->mmio_base); - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - release_mem_region(r->start, resource_size(r)); - - clk_put(pwm->clk); - - kfree(pwm); - return 0; -} - -static struct platform_driver mxc_pwm_driver = { - .driver = { - .name = "mxc_pwm", - }, - .probe = mxc_pwm_probe, - .remove = __devexit_p(mxc_pwm_remove), -}; - -static int __init mxc_pwm_init(void) -{ - return platform_driver_register(&mxc_pwm_driver); -} -arch_initcall(mxc_pwm_init); - -static void __exit mxc_pwm_exit(void) -{ - platform_driver_unregister(&mxc_pwm_driver); -} -module_exit(mxc_pwm_exit); - -MODULE_LICENSE("GPL v2"); -MODULE_AUTHOR("Sascha Hauer "); diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 0ef4f3037c4..a9a97158110 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -18,6 +18,15 @@ config PWM_BFIN To compile this driver as a module, choose M here: the module will be called pwm-bfin. +config PWM_IMX + tristate "i.MX pwm support" + depends on ARCH_MXC + help + Generic PWM framework driver for i.MX. + + To compile this driver as a module, choose M here: the module + will be called pwm-imx. + config PWM_PXA tristate "PXA PWM support" depends on ARCH_PXA diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index e859c513add..3dd1b8357a0 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -1,4 +1,5 @@ obj-$(CONFIG_PWM) += core.o obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o +obj-$(CONFIG_PWM_IMX) += pwm-imx.o obj-$(CONFIG_PWM_PXA) += pwm-pxa.o obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o diff --git a/drivers/pwm/pwm-imx.c b/drivers/pwm/pwm-imx.c new file mode 100644 index 00000000000..900d14562de --- /dev/null +++ b/drivers/pwm/pwm-imx.c @@ -0,0 +1,267 @@ +/* + * simple driver for PWM (Pulse Width Modulator) controller + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Derived from pxa PWM driver by eric miao + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* i.MX1 and i.MX21 share the same PWM function block: */ + +#define MX1_PWMC 0x00 /* PWM Control Register */ +#define MX1_PWMS 0x04 /* PWM Sample Register */ +#define MX1_PWMP 0x08 /* PWM Period Register */ + + +/* i.MX27, i.MX31, i.MX35 share the same PWM function block: */ + +#define MX3_PWMCR 0x00 /* PWM Control Register */ +#define MX3_PWMSAR 0x0C /* PWM Sample Register */ +#define MX3_PWMPR 0x10 /* PWM Period Register */ +#define MX3_PWMCR_PRESCALER(x) (((x - 1) & 0xFFF) << 4) +#define MX3_PWMCR_DOZEEN (1 << 24) +#define MX3_PWMCR_WAITEN (1 << 23) +#define MX3_PWMCR_DBGEN (1 << 22) +#define MX3_PWMCR_CLKSRC_IPG_HIGH (2 << 16) +#define MX3_PWMCR_CLKSRC_IPG (1 << 16) +#define MX3_PWMCR_EN (1 << 0) + +struct imx_chip { + struct clk *clk; + + int clk_enabled; + void __iomem *mmio_base; + + struct pwm_chip chip; +}; + +#define to_imx_chip(chip) container_of(chip, struct imx_chip, chip) + +static int imx_pwm_config(struct pwm_chip *chip, + struct pwm_device *pwm, int duty_ns, int period_ns) +{ + struct imx_chip *imx = to_imx_chip(chip); + + if (!(cpu_is_mx1() || cpu_is_mx21())) { + unsigned long long c; + unsigned long period_cycles, duty_cycles, prescale; + u32 cr; + + c = clk_get_rate(imx->clk); + c = c * period_ns; + do_div(c, 1000000000); + period_cycles = c; + + prescale = period_cycles / 0x10000 + 1; + + period_cycles /= prescale; + c = (unsigned long long)period_cycles * duty_ns; + do_div(c, period_ns); + duty_cycles = c; + + /* + * according to imx pwm RM, the real period value should be + * PERIOD value in PWMPR plus 2. + */ + if (period_cycles > 2) + period_cycles -= 2; + else + period_cycles = 0; + + writel(duty_cycles, imx->mmio_base + MX3_PWMSAR); + writel(period_cycles, imx->mmio_base + MX3_PWMPR); + + cr = MX3_PWMCR_PRESCALER(prescale) | + MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN | + MX3_PWMCR_DBGEN | MX3_PWMCR_EN; + + if (cpu_is_mx25()) + cr |= MX3_PWMCR_CLKSRC_IPG; + else + cr |= MX3_PWMCR_CLKSRC_IPG_HIGH; + + writel(cr, imx->mmio_base + MX3_PWMCR); + } else if (cpu_is_mx1() || cpu_is_mx21()) { + /* The PWM subsystem allows for exact frequencies. However, + * I cannot connect a scope on my device to the PWM line and + * thus cannot provide the program the PWM controller + * exactly. Instead, I'm relying on the fact that the + * Bootloader (u-boot or WinCE+haret) has programmed the PWM + * function group already. So I'll just modify the PWM sample + * register to follow the ratio of duty_ns vs. period_ns + * accordingly. + * + * This is good enough for programming the brightness of + * the LCD backlight. + * + * The real implementation would divide PERCLK[0] first by + * both the prescaler (/1 .. /128) and then by CLKSEL + * (/2 .. /16). + */ + u32 max = readl(imx->mmio_base + MX1_PWMP); + u32 p = max * duty_ns / period_ns; + writel(max - p, imx->mmio_base + MX1_PWMS); + } else { + BUG(); + } + + return 0; +} + +static int imx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct imx_chip *imx = to_imx_chip(chip); + int rc = 0; + + if (!imx->clk_enabled) { + rc = clk_prepare_enable(imx->clk); + if (!rc) + imx->clk_enabled = 1; + } + return rc; +} + +static void imx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct imx_chip *imx = to_imx_chip(chip); + + writel(0, imx->mmio_base + MX3_PWMCR); + + if (imx->clk_enabled) { + clk_disable_unprepare(imx->clk); + imx->clk_enabled = 0; + } +} + +static struct pwm_ops imx_pwm_ops = { + .enable = imx_pwm_enable, + .disable = imx_pwm_disable, + .config = imx_pwm_config, + .owner = THIS_MODULE, +}; + +static int __devinit imx_pwm_probe(struct platform_device *pdev) +{ + struct imx_chip *imx; + struct resource *r; + int ret = 0; + + imx = kzalloc(sizeof(*imx), GFP_KERNEL); + if (imx == NULL) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + imx->clk = clk_get(&pdev->dev, "pwm"); + + if (IS_ERR(imx->clk)) { + ret = PTR_ERR(imx->clk); + goto err_free; + } + + imx->chip.ops = &imx_pwm_ops; + imx->chip.dev = &pdev->dev; + imx->chip.base = -1; + imx->chip.npwm = 1; + + imx->clk_enabled = 0; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (r == NULL) { + dev_err(&pdev->dev, "no memory resource defined\n"); + ret = -ENODEV; + goto err_free_clk; + } + + r = request_mem_region(r->start, resource_size(r), pdev->name); + if (r == NULL) { + dev_err(&pdev->dev, "failed to request memory resource\n"); + ret = -EBUSY; + goto err_free_clk; + } + + imx->mmio_base = ioremap(r->start, resource_size(r)); + if (imx->mmio_base == NULL) { + dev_err(&pdev->dev, "failed to ioremap() registers\n"); + ret = -ENODEV; + goto err_free_mem; + } + + ret = pwmchip_add(&imx->chip); + if (ret < 0) + goto err_iounmap; + + platform_set_drvdata(pdev, imx); + return 0; + +err_iounmap: + iounmap(imx->mmio_base); +err_free_mem: + release_mem_region(r->start, resource_size(r)); +err_free_clk: + clk_put(imx->clk); +err_free: + kfree(imx); + return ret; +} + +static int __devexit imx_pwm_remove(struct platform_device *pdev) +{ + struct imx_chip *imx; + struct resource *r; + int ret; + + imx = platform_get_drvdata(pdev); + if (imx == NULL) + return -ENODEV; + + ret = pwmchip_remove(&imx->chip); + if (ret) + return ret; + + iounmap(imx->mmio_base); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(r->start, resource_size(r)); + + clk_put(imx->clk); + + kfree(imx); + return 0; +} + +static struct platform_driver imx_pwm_driver = { + .driver = { + .name = "mxc_pwm", + }, + .probe = imx_pwm_probe, + .remove = __devexit_p(imx_pwm_remove), +}; + +static int __init imx_pwm_init(void) +{ + return platform_driver_register(&imx_pwm_driver); +} +arch_initcall(imx_pwm_init); + +static void __exit imx_pwm_exit(void) +{ + platform_driver_unregister(&imx_pwm_driver); +} +module_exit(imx_pwm_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Sascha Hauer "); -- cgit v1.2.3-70-g09d2 From 215c29d3d0e925189ade522d1ea6052a320d7692 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 15 Mar 2012 10:04:36 +0100 Subject: ARM Samsung: Move s3c pwm driver to pwm framework MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the driver to drivers/pwm/ and convert it to use the framework. Signed-off-by: Sascha Hauer Cc: Ben Dooks Cc: Kukjin Kim [eric@eukrea.com: fix pwmchip_add return code test] Signed-off-by: Eric Bénard Signed-off-by: Thierry Reding --- arch/arm/plat-samsung/Makefile | 4 - arch/arm/plat-samsung/pwm.c | 420 ----------------------------------------- drivers/pwm/Kconfig | 9 + drivers/pwm/Makefile | 1 + drivers/pwm/pwm-samsung.c | 366 +++++++++++++++++++++++++++++++++++ 5 files changed, 376 insertions(+), 424 deletions(-) delete mode 100644 arch/arm/plat-samsung/pwm.c create mode 100644 drivers/pwm/pwm-samsung.c (limited to 'arch') diff --git a/arch/arm/plat-samsung/Makefile b/arch/arm/plat-samsung/Makefile index 860b2db4db1..96854721319 100644 --- a/arch/arm/plat-samsung/Makefile +++ b/arch/arm/plat-samsung/Makefile @@ -63,7 +63,3 @@ obj-$(CONFIG_S5P_SLEEP) += s5p-sleep.o # PD support obj-$(CONFIG_SAMSUNG_PD) += pd.o - -# PWM support - -obj-$(CONFIG_HAVE_PWM) += pwm.o diff --git a/arch/arm/plat-samsung/pwm.c b/arch/arm/plat-samsung/pwm.c deleted file mode 100644 index c559d8438c7..00000000000 --- a/arch/arm/plat-samsung/pwm.c +++ /dev/null @@ -1,420 +0,0 @@ -/* arch/arm/plat-s3c/pwm.c - * - * Copyright (c) 2007 Ben Dooks - * Copyright (c) 2008 Simtec Electronics - * Ben Dooks , - * - * S3C series PWM device core - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License. -*/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -struct pwm_device { - struct list_head list; - struct platform_device *pdev; - - struct clk *clk_div; - struct clk *clk; - const char *label; - - unsigned int period_ns; - unsigned int duty_ns; - - unsigned char tcon_base; - unsigned char running; - unsigned char use_count; - unsigned char pwm_id; -}; - -#define pwm_dbg(_pwm, msg...) dev_dbg(&(_pwm)->pdev->dev, msg) - -static struct clk *clk_scaler[2]; - -static inline int pwm_is_tdiv(struct pwm_device *pwm) -{ - return clk_get_parent(pwm->clk) == pwm->clk_div; -} - -static DEFINE_MUTEX(pwm_lock); -static LIST_HEAD(pwm_list); - -struct pwm_device *pwm_request(int pwm_id, const char *label) -{ - struct pwm_device *pwm; - int found = 0; - - mutex_lock(&pwm_lock); - - list_for_each_entry(pwm, &pwm_list, list) { - if (pwm->pwm_id == pwm_id) { - found = 1; - break; - } - } - - if (found) { - if (pwm->use_count == 0) { - pwm->use_count = 1; - pwm->label = label; - } else - pwm = ERR_PTR(-EBUSY); - } else - pwm = ERR_PTR(-ENOENT); - - mutex_unlock(&pwm_lock); - return pwm; -} - -EXPORT_SYMBOL(pwm_request); - - -void pwm_free(struct pwm_device *pwm) -{ - mutex_lock(&pwm_lock); - - if (pwm->use_count) { - pwm->use_count--; - pwm->label = NULL; - } else - printk(KERN_ERR "PWM%d device already freed\n", pwm->pwm_id); - - mutex_unlock(&pwm_lock); -} - -EXPORT_SYMBOL(pwm_free); - -#define pwm_tcon_start(pwm) (1 << (pwm->tcon_base + 0)) -#define pwm_tcon_invert(pwm) (1 << (pwm->tcon_base + 2)) -#define pwm_tcon_autoreload(pwm) (1 << (pwm->tcon_base + 3)) -#define pwm_tcon_manulupdate(pwm) (1 << (pwm->tcon_base + 1)) - -int pwm_enable(struct pwm_device *pwm) -{ - unsigned long flags; - unsigned long tcon; - - local_irq_save(flags); - - tcon = __raw_readl(S3C2410_TCON); - tcon |= pwm_tcon_start(pwm); - __raw_writel(tcon, S3C2410_TCON); - - local_irq_restore(flags); - - pwm->running = 1; - return 0; -} - -EXPORT_SYMBOL(pwm_enable); - -void pwm_disable(struct pwm_device *pwm) -{ - unsigned long flags; - unsigned long tcon; - - local_irq_save(flags); - - tcon = __raw_readl(S3C2410_TCON); - tcon &= ~pwm_tcon_start(pwm); - __raw_writel(tcon, S3C2410_TCON); - - local_irq_restore(flags); - - pwm->running = 0; -} - -EXPORT_SYMBOL(pwm_disable); - -static unsigned long pwm_calc_tin(struct pwm_device *pwm, unsigned long freq) -{ - unsigned long tin_parent_rate; - unsigned int div; - - tin_parent_rate = clk_get_rate(clk_get_parent(pwm->clk_div)); - pwm_dbg(pwm, "tin parent at %lu\n", tin_parent_rate); - - for (div = 2; div <= 16; div *= 2) { - if ((tin_parent_rate / (div << 16)) < freq) - return tin_parent_rate / div; - } - - return tin_parent_rate / 16; -} - -#define NS_IN_HZ (1000000000UL) - -int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) -{ - unsigned long tin_rate; - unsigned long tin_ns; - unsigned long period; - unsigned long flags; - unsigned long tcon; - unsigned long tcnt; - long tcmp; - - /* We currently avoid using 64bit arithmetic by using the - * fact that anything faster than 1Hz is easily representable - * by 32bits. */ - - if (period_ns > NS_IN_HZ || duty_ns > NS_IN_HZ) - return -ERANGE; - - if (duty_ns > period_ns) - return -EINVAL; - - if (period_ns == pwm->period_ns && - duty_ns == pwm->duty_ns) - return 0; - - /* The TCMP and TCNT can be read without a lock, they're not - * shared between the timers. */ - - tcmp = __raw_readl(S3C2410_TCMPB(pwm->pwm_id)); - tcnt = __raw_readl(S3C2410_TCNTB(pwm->pwm_id)); - - period = NS_IN_HZ / period_ns; - - pwm_dbg(pwm, "duty_ns=%d, period_ns=%d (%lu)\n", - duty_ns, period_ns, period); - - /* Check to see if we are changing the clock rate of the PWM */ - - if (pwm->period_ns != period_ns) { - if (pwm_is_tdiv(pwm)) { - tin_rate = pwm_calc_tin(pwm, period); - clk_set_rate(pwm->clk_div, tin_rate); - } else - tin_rate = clk_get_rate(pwm->clk); - - pwm->period_ns = period_ns; - - pwm_dbg(pwm, "tin_rate=%lu\n", tin_rate); - - tin_ns = NS_IN_HZ / tin_rate; - tcnt = period_ns / tin_ns; - } else - tin_ns = NS_IN_HZ / clk_get_rate(pwm->clk); - - /* Note, counters count down */ - - tcmp = duty_ns / tin_ns; - tcmp = tcnt - tcmp; - /* the pwm hw only checks the compare register after a decrement, - so the pin never toggles if tcmp = tcnt */ - if (tcmp == tcnt) - tcmp--; - - pwm_dbg(pwm, "tin_ns=%lu, tcmp=%ld/%lu\n", tin_ns, tcmp, tcnt); - - if (tcmp < 0) - tcmp = 0; - - /* Update the PWM register block. */ - - local_irq_save(flags); - - __raw_writel(tcmp, S3C2410_TCMPB(pwm->pwm_id)); - __raw_writel(tcnt, S3C2410_TCNTB(pwm->pwm_id)); - - tcon = __raw_readl(S3C2410_TCON); - tcon |= pwm_tcon_manulupdate(pwm); - tcon |= pwm_tcon_autoreload(pwm); - __raw_writel(tcon, S3C2410_TCON); - - tcon &= ~pwm_tcon_manulupdate(pwm); - __raw_writel(tcon, S3C2410_TCON); - - local_irq_restore(flags); - - return 0; -} - -EXPORT_SYMBOL(pwm_config); - -static int pwm_register(struct pwm_device *pwm) -{ - pwm->duty_ns = -1; - pwm->period_ns = -1; - - mutex_lock(&pwm_lock); - list_add_tail(&pwm->list, &pwm_list); - mutex_unlock(&pwm_lock); - - return 0; -} - -static int s3c_pwm_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct pwm_device *pwm; - unsigned long flags; - unsigned long tcon; - unsigned int id = pdev->id; - int ret; - - if (id == 4) { - dev_err(dev, "TIMER4 is currently not supported\n"); - return -ENXIO; - } - - pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL); - if (pwm == NULL) { - dev_err(dev, "failed to allocate pwm_device\n"); - return -ENOMEM; - } - - pwm->pdev = pdev; - pwm->pwm_id = id; - - /* calculate base of control bits in TCON */ - pwm->tcon_base = id == 0 ? 0 : (id * 4) + 4; - - pwm->clk = clk_get(dev, "pwm-tin"); - if (IS_ERR(pwm->clk)) { - dev_err(dev, "failed to get pwm tin clk\n"); - ret = PTR_ERR(pwm->clk); - goto err_alloc; - } - - pwm->clk_div = clk_get(dev, "pwm-tdiv"); - if (IS_ERR(pwm->clk_div)) { - dev_err(dev, "failed to get pwm tdiv clk\n"); - ret = PTR_ERR(pwm->clk_div); - goto err_clk_tin; - } - - clk_enable(pwm->clk); - clk_enable(pwm->clk_div); - - local_irq_save(flags); - - tcon = __raw_readl(S3C2410_TCON); - tcon |= pwm_tcon_invert(pwm); - __raw_writel(tcon, S3C2410_TCON); - - local_irq_restore(flags); - - - ret = pwm_register(pwm); - if (ret) { - dev_err(dev, "failed to register pwm\n"); - goto err_clk_tdiv; - } - - pwm_dbg(pwm, "config bits %02x\n", - (__raw_readl(S3C2410_TCON) >> pwm->tcon_base) & 0x0f); - - dev_info(dev, "tin at %lu, tdiv at %lu, tin=%sclk, base %d\n", - clk_get_rate(pwm->clk), - clk_get_rate(pwm->clk_div), - pwm_is_tdiv(pwm) ? "div" : "ext", pwm->tcon_base); - - platform_set_drvdata(pdev, pwm); - return 0; - - err_clk_tdiv: - clk_disable(pwm->clk_div); - clk_disable(pwm->clk); - clk_put(pwm->clk_div); - - err_clk_tin: - clk_put(pwm->clk); - - err_alloc: - kfree(pwm); - return ret; -} - -static int __devexit s3c_pwm_remove(struct platform_device *pdev) -{ - struct pwm_device *pwm = platform_get_drvdata(pdev); - - clk_disable(pwm->clk_div); - clk_disable(pwm->clk); - clk_put(pwm->clk_div); - clk_put(pwm->clk); - kfree(pwm); - - return 0; -} - -#ifdef CONFIG_PM -static int s3c_pwm_suspend(struct platform_device *pdev, pm_message_t state) -{ - struct pwm_device *pwm = platform_get_drvdata(pdev); - - /* No one preserve these values during suspend so reset them - * Otherwise driver leaves PWM unconfigured if same values - * passed to pwm_config - */ - pwm->period_ns = 0; - pwm->duty_ns = 0; - - return 0; -} - -static int s3c_pwm_resume(struct platform_device *pdev) -{ - struct pwm_device *pwm = platform_get_drvdata(pdev); - unsigned long tcon; - - /* Restore invertion */ - tcon = __raw_readl(S3C2410_TCON); - tcon |= pwm_tcon_invert(pwm); - __raw_writel(tcon, S3C2410_TCON); - - return 0; -} - -#else -#define s3c_pwm_suspend NULL -#define s3c_pwm_resume NULL -#endif - -static struct platform_driver s3c_pwm_driver = { - .driver = { - .name = "s3c24xx-pwm", - .owner = THIS_MODULE, - }, - .probe = s3c_pwm_probe, - .remove = __devexit_p(s3c_pwm_remove), - .suspend = s3c_pwm_suspend, - .resume = s3c_pwm_resume, -}; - -static int __init pwm_init(void) -{ - int ret; - - clk_scaler[0] = clk_get(NULL, "pwm-scaler0"); - clk_scaler[1] = clk_get(NULL, "pwm-scaler1"); - - if (IS_ERR(clk_scaler[0]) || IS_ERR(clk_scaler[1])) { - printk(KERN_ERR "%s: failed to get scaler clocks\n", __func__); - return -EINVAL; - } - - ret = platform_driver_register(&s3c_pwm_driver); - if (ret) - printk(KERN_ERR "%s: failed to add pwm driver\n", __func__); - - return ret; -} - -arch_initcall(pwm_init); diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index a9a97158110..0d9aa92636c 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -36,6 +36,15 @@ config PWM_PXA To compile this driver as a module, choose M here: the module will be called pwm-pxa. +config PWM_SAMSUNG + tristate "Samsung pwm support" + depends on PLAT_SAMSUNG + help + Generic PWM framework driver for Samsung. + + To compile this driver as a module, choose M here: the module + will be called pwm-samsung. + config PWM_TEGRA tristate "NVIDIA Tegra PWM support" depends on ARCH_TEGRA diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 3dd1b8357a0..a1d87b17331 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -2,4 +2,5 @@ obj-$(CONFIG_PWM) += core.o obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o obj-$(CONFIG_PWM_IMX) += pwm-imx.o obj-$(CONFIG_PWM_PXA) += pwm-pxa.o +obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o diff --git a/drivers/pwm/pwm-samsung.c b/drivers/pwm/pwm-samsung.c new file mode 100644 index 00000000000..c40c37e968e --- /dev/null +++ b/drivers/pwm/pwm-samsung.c @@ -0,0 +1,366 @@ +/* arch/arm/plat-s3c/pwm.c + * + * Copyright (c) 2007 Ben Dooks + * Copyright (c) 2008 Simtec Electronics + * Ben Dooks , + * + * S3C series PWM device core + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +struct s3c_chip { + struct platform_device *pdev; + + struct clk *clk_div; + struct clk *clk; + const char *label; + + unsigned int period_ns; + unsigned int duty_ns; + + unsigned char tcon_base; + unsigned char pwm_id; + struct pwm_chip chip; +}; + +#define to_s3c_chip(chip) container_of(chip, struct s3c_chip, chip) + +#define pwm_dbg(_pwm, msg...) dev_dbg(&(_pwm)->pdev->dev, msg) + +static struct clk *clk_scaler[2]; + +static inline int pwm_is_tdiv(struct s3c_chip *chip) +{ + return clk_get_parent(chip->clk) == chip->clk_div; +} + +#define pwm_tcon_start(pwm) (1 << (pwm->tcon_base + 0)) +#define pwm_tcon_invert(pwm) (1 << (pwm->tcon_base + 2)) +#define pwm_tcon_autoreload(pwm) (1 << (pwm->tcon_base + 3)) +#define pwm_tcon_manulupdate(pwm) (1 << (pwm->tcon_base + 1)) + +static int s3c_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct s3c_chip *s3c = to_s3c_chip(chip); + unsigned long flags; + unsigned long tcon; + + local_irq_save(flags); + + tcon = __raw_readl(S3C2410_TCON); + tcon |= pwm_tcon_start(s3c); + __raw_writel(tcon, S3C2410_TCON); + + local_irq_restore(flags); + + return 0; +} + +static void s3c_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct s3c_chip *s3c = to_s3c_chip(chip); + unsigned long flags; + unsigned long tcon; + + local_irq_save(flags); + + tcon = __raw_readl(S3C2410_TCON); + tcon &= ~pwm_tcon_start(s3c); + __raw_writel(tcon, S3C2410_TCON); + + local_irq_restore(flags); +} + +static unsigned long pwm_calc_tin(struct s3c_chip *s3c, unsigned long freq) +{ + unsigned long tin_parent_rate; + unsigned int div; + + tin_parent_rate = clk_get_rate(clk_get_parent(s3c->clk_div)); + pwm_dbg(s3c, "tin parent at %lu\n", tin_parent_rate); + + for (div = 2; div <= 16; div *= 2) { + if ((tin_parent_rate / (div << 16)) < freq) + return tin_parent_rate / div; + } + + return tin_parent_rate / 16; +} + +#define NS_IN_HZ (1000000000UL) + +static int s3c_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct s3c_chip *s3c = to_s3c_chip(chip); + unsigned long tin_rate; + unsigned long tin_ns; + unsigned long period; + unsigned long flags; + unsigned long tcon; + unsigned long tcnt; + long tcmp; + + /* We currently avoid using 64bit arithmetic by using the + * fact that anything faster than 1Hz is easily representable + * by 32bits. */ + + if (period_ns > NS_IN_HZ || duty_ns > NS_IN_HZ) + return -ERANGE; + + if (duty_ns > period_ns) + return -EINVAL; + + if (period_ns == s3c->period_ns && + duty_ns == s3c->duty_ns) + return 0; + + /* The TCMP and TCNT can be read without a lock, they're not + * shared between the timers. */ + + tcmp = __raw_readl(S3C2410_TCMPB(s3c->pwm_id)); + tcnt = __raw_readl(S3C2410_TCNTB(s3c->pwm_id)); + + period = NS_IN_HZ / period_ns; + + pwm_dbg(s3c, "duty_ns=%d, period_ns=%d (%lu)\n", + duty_ns, period_ns, period); + + /* Check to see if we are changing the clock rate of the PWM */ + + if (s3c->period_ns != period_ns) { + if (pwm_is_tdiv(s3c)) { + tin_rate = pwm_calc_tin(s3c, period); + clk_set_rate(s3c->clk_div, tin_rate); + } else + tin_rate = clk_get_rate(s3c->clk); + + s3c->period_ns = period_ns; + + pwm_dbg(s3c, "tin_rate=%lu\n", tin_rate); + + tin_ns = NS_IN_HZ / tin_rate; + tcnt = period_ns / tin_ns; + } else + tin_ns = NS_IN_HZ / clk_get_rate(s3c->clk); + + /* Note, counters count down */ + + tcmp = duty_ns / tin_ns; + tcmp = tcnt - tcmp; + /* the pwm hw only checks the compare register after a decrement, + so the pin never toggles if tcmp = tcnt */ + if (tcmp == tcnt) + tcmp--; + + pwm_dbg(s3c, "tin_ns=%lu, tcmp=%ld/%lu\n", tin_ns, tcmp, tcnt); + + if (tcmp < 0) + tcmp = 0; + + /* Update the PWM register block. */ + + local_irq_save(flags); + + __raw_writel(tcmp, S3C2410_TCMPB(s3c->pwm_id)); + __raw_writel(tcnt, S3C2410_TCNTB(s3c->pwm_id)); + + tcon = __raw_readl(S3C2410_TCON); + tcon |= pwm_tcon_manulupdate(s3c); + tcon |= pwm_tcon_autoreload(s3c); + __raw_writel(tcon, S3C2410_TCON); + + tcon &= ~pwm_tcon_manulupdate(s3c); + __raw_writel(tcon, S3C2410_TCON); + + local_irq_restore(flags); + + return 0; +} + +static struct pwm_ops s3c_pwm_ops = { + .enable = s3c_pwm_enable, + .disable = s3c_pwm_disable, + .config = s3c_pwm_config, + .owner = THIS_MODULE, +}; + +static int s3c_pwm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct s3c_chip *s3c; + unsigned long flags; + unsigned long tcon; + unsigned int id = pdev->id; + int ret; + + if (id == 4) { + dev_err(dev, "TIMER4 is currently not supported\n"); + return -ENXIO; + } + + s3c = kzalloc(sizeof(*s3c), GFP_KERNEL); + if (s3c == NULL) { + dev_err(dev, "failed to allocate pwm_device\n"); + return -ENOMEM; + } + + /* calculate base of control bits in TCON */ + s3c->tcon_base = id == 0 ? 0 : (id * 4) + 4; + s3c->chip.ops = &s3c_pwm_ops; + s3c->chip.base = -1; + s3c->chip.npwm = 1; + + s3c->clk = clk_get(dev, "pwm-tin"); + if (IS_ERR(s3c->clk)) { + dev_err(dev, "failed to get pwm tin clk\n"); + ret = PTR_ERR(s3c->clk); + goto err_alloc; + } + + s3c->clk_div = clk_get(dev, "pwm-tdiv"); + if (IS_ERR(s3c->clk_div)) { + dev_err(dev, "failed to get pwm tdiv clk\n"); + ret = PTR_ERR(s3c->clk_div); + goto err_clk_tin; + } + + clk_enable(s3c->clk); + clk_enable(s3c->clk_div); + + local_irq_save(flags); + + tcon = __raw_readl(S3C2410_TCON); + tcon |= pwm_tcon_invert(s3c); + __raw_writel(tcon, S3C2410_TCON); + + local_irq_restore(flags); + + ret = pwmchip_add(&s3c->chip); + if (ret < 0) { + dev_err(dev, "failed to register pwm\n"); + goto err_clk_tdiv; + } + + pwm_dbg(s3c, "config bits %02x\n", + (__raw_readl(S3C2410_TCON) >> s3c->tcon_base) & 0x0f); + + dev_info(dev, "tin at %lu, tdiv at %lu, tin=%sclk, base %d\n", + clk_get_rate(s3c->clk), + clk_get_rate(s3c->clk_div), + pwm_is_tdiv(s3c) ? "div" : "ext", s3c->tcon_base); + + platform_set_drvdata(pdev, s3c); + return 0; + + err_clk_tdiv: + clk_disable(s3c->clk_div); + clk_disable(s3c->clk); + clk_put(s3c->clk_div); + + err_clk_tin: + clk_put(s3c->clk); + + err_alloc: + kfree(s3c); + return ret; +} + +static int __devexit s3c_pwm_remove(struct platform_device *pdev) +{ + struct s3c_chip *s3c = platform_get_drvdata(pdev); + int err; + + err = pwmchip_remove(&s3c->chip); + if (err < 0) + return err; + + clk_disable(s3c->clk_div); + clk_disable(s3c->clk); + clk_put(s3c->clk_div); + clk_put(s3c->clk); + kfree(s3c); + + return 0; +} + +#ifdef CONFIG_PM +static int s3c_pwm_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct s3c_chip *s3c = platform_get_drvdata(pdev); + + /* No one preserve these values during suspend so reset them + * Otherwise driver leaves PWM unconfigured if same values + * passed to pwm_config + */ + s3c->period_ns = 0; + s3c->duty_ns = 0; + + return 0; +} + +static int s3c_pwm_resume(struct platform_device *pdev) +{ + struct s3c_chip *s3c = platform_get_drvdata(pdev); + unsigned long tcon; + + /* Restore invertion */ + tcon = __raw_readl(S3C2410_TCON); + tcon |= pwm_tcon_invert(s3c); + __raw_writel(tcon, S3C2410_TCON); + + return 0; +} + +#else +#define s3c_pwm_suspend NULL +#define s3c_pwm_resume NULL +#endif + +static struct platform_driver s3c_pwm_driver = { + .driver = { + .name = "s3c24xx-pwm", + .owner = THIS_MODULE, + }, + .probe = s3c_pwm_probe, + .remove = __devexit_p(s3c_pwm_remove), + .suspend = s3c_pwm_suspend, + .resume = s3c_pwm_resume, +}; + +static int __init pwm_init(void) +{ + int ret; + + clk_scaler[0] = clk_get(NULL, "pwm-scaler0"); + clk_scaler[1] = clk_get(NULL, "pwm-scaler1"); + + if (IS_ERR(clk_scaler[0]) || IS_ERR(clk_scaler[1])) { + printk(KERN_ERR "%s: failed to get scaler clocks\n", __func__); + return -EINVAL; + } + + ret = platform_driver_register(&s3c_pwm_driver); + if (ret) + printk(KERN_ERR "%s: failed to add pwm driver\n", __func__); + + return ret; +} + +arch_initcall(pwm_init); -- cgit v1.2.3-70-g09d2 From a245ccebb4aad9fae60597d5cbad158c0de4483e Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 15 Mar 2012 10:04:37 +0100 Subject: ARM vt8500: Move vt8500 pwm driver to pwm framework Move the driver to drivers/pwm/ and convert it to use the framework. Signed-off-by: Sascha Hauer Cc: Alexey Charkov Signed-off-by: Thierry Reding --- arch/arm/Kconfig | 1 - arch/arm/mach-vt8500/Makefile | 2 - arch/arm/mach-vt8500/pwm.c | 265 ------------------------------------------ drivers/pwm/Kconfig | 9 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-vt8500.c | 208 +++++++++++++++++++++++++++++++++ 6 files changed, 218 insertions(+), 268 deletions(-) delete mode 100644 arch/arm/mach-vt8500/pwm.c create mode 100644 drivers/pwm/pwm-vt8500.c (limited to 'arch') diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 84449dd8f03..8da8f82f6fe 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -962,7 +962,6 @@ config ARCH_VT8500 select ARCH_HAS_CPUFREQ select GENERIC_CLOCKEVENTS select ARCH_REQUIRE_GPIOLIB - select HAVE_PWM help Support for VIA/WonderMedia VT8500/WM85xx System-on-Chip. diff --git a/arch/arm/mach-vt8500/Makefile b/arch/arm/mach-vt8500/Makefile index 81aedb7c893..8df9e4abab6 100644 --- a/arch/arm/mach-vt8500/Makefile +++ b/arch/arm/mach-vt8500/Makefile @@ -5,5 +5,3 @@ obj-$(CONFIG_VTWM_VERSION_WM8505) += devices-wm8505.o obj-$(CONFIG_MACH_BV07) += bv07.o obj-$(CONFIG_MACH_WM8505_7IN_NETBOOK) += wm8505_7in.o - -obj-$(CONFIG_HAVE_PWM) += pwm.o diff --git a/arch/arm/mach-vt8500/pwm.c b/arch/arm/mach-vt8500/pwm.c deleted file mode 100644 index 8ad825e9359..00000000000 --- a/arch/arm/mach-vt8500/pwm.c +++ /dev/null @@ -1,265 +0,0 @@ -/* - * arch/arm/mach-vt8500/pwm.c - * - * Copyright (C) 2010 Alexey Charkov - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#define VT8500_NR_PWMS 4 - -static DEFINE_MUTEX(pwm_lock); -static LIST_HEAD(pwm_list); - -struct pwm_device { - struct list_head node; - struct platform_device *pdev; - - const char *label; - - void __iomem *regbase; - - unsigned int use_count; - unsigned int pwm_id; -}; - -#define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t) -static inline void pwm_busy_wait(void __iomem *reg, u8 bitmask) -{ - int loops = msecs_to_loops(10); - while ((readb(reg) & bitmask) && --loops) - cpu_relax(); - - if (unlikely(!loops)) - pr_warning("Waiting for status bits 0x%x to clear timed out\n", - bitmask); -} - -int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) -{ - unsigned long long c; - unsigned long period_cycles, prescale, pv, dc; - - if (pwm == NULL || period_ns == 0 || duty_ns > period_ns) - return -EINVAL; - - c = 25000000/2; /* wild guess --- need to implement clocks */ - c = c * period_ns; - do_div(c, 1000000000); - period_cycles = c; - - if (period_cycles < 1) - period_cycles = 1; - prescale = (period_cycles - 1) / 4096; - pv = period_cycles / (prescale + 1) - 1; - if (pv > 4095) - pv = 4095; - - if (prescale > 1023) - return -EINVAL; - - c = (unsigned long long)pv * duty_ns; - do_div(c, period_ns); - dc = c; - - pwm_busy_wait(pwm->regbase + 0x40 + pwm->pwm_id, (1 << 1)); - writel(prescale, pwm->regbase + 0x4 + (pwm->pwm_id << 4)); - - pwm_busy_wait(pwm->regbase + 0x40 + pwm->pwm_id, (1 << 2)); - writel(pv, pwm->regbase + 0x8 + (pwm->pwm_id << 4)); - - pwm_busy_wait(pwm->regbase + 0x40 + pwm->pwm_id, (1 << 3)); - writel(dc, pwm->regbase + 0xc + (pwm->pwm_id << 4)); - - return 0; -} -EXPORT_SYMBOL(pwm_config); - -int pwm_enable(struct pwm_device *pwm) -{ - pwm_busy_wait(pwm->regbase + 0x40 + pwm->pwm_id, (1 << 0)); - writel(5, pwm->regbase + (pwm->pwm_id << 4)); - return 0; -} -EXPORT_SYMBOL(pwm_enable); - -void pwm_disable(struct pwm_device *pwm) -{ - pwm_busy_wait(pwm->regbase + 0x40 + pwm->pwm_id, (1 << 0)); - writel(0, pwm->regbase + (pwm->pwm_id << 4)); -} -EXPORT_SYMBOL(pwm_disable); - -struct pwm_device *pwm_request(int pwm_id, const char *label) -{ - struct pwm_device *pwm; - int found = 0; - - mutex_lock(&pwm_lock); - - list_for_each_entry(pwm, &pwm_list, node) { - if (pwm->pwm_id == pwm_id) { - found = 1; - break; - } - } - - if (found) { - if (pwm->use_count == 0) { - pwm->use_count++; - pwm->label = label; - } else { - pwm = ERR_PTR(-EBUSY); - } - } else { - pwm = ERR_PTR(-ENOENT); - } - - mutex_unlock(&pwm_lock); - return pwm; -} -EXPORT_SYMBOL(pwm_request); - -void pwm_free(struct pwm_device *pwm) -{ - mutex_lock(&pwm_lock); - - if (pwm->use_count) { - pwm->use_count--; - pwm->label = NULL; - } else { - pr_warning("PWM device already freed\n"); - } - - mutex_unlock(&pwm_lock); -} -EXPORT_SYMBOL(pwm_free); - -static inline void __add_pwm(struct pwm_device *pwm) -{ - mutex_lock(&pwm_lock); - list_add_tail(&pwm->node, &pwm_list); - mutex_unlock(&pwm_lock); -} - -static int __devinit pwm_probe(struct platform_device *pdev) -{ - struct pwm_device *pwms; - struct resource *r; - int ret = 0; - int i; - - pwms = kzalloc(sizeof(struct pwm_device) * VT8500_NR_PWMS, GFP_KERNEL); - if (pwms == NULL) { - dev_err(&pdev->dev, "failed to allocate memory\n"); - return -ENOMEM; - } - - for (i = 0; i < VT8500_NR_PWMS; i++) { - pwms[i].use_count = 0; - pwms[i].pwm_id = i; - pwms[i].pdev = pdev; - } - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (r == NULL) { - dev_err(&pdev->dev, "no memory resource defined\n"); - ret = -ENODEV; - goto err_free; - } - - r = request_mem_region(r->start, resource_size(r), pdev->name); - if (r == NULL) { - dev_err(&pdev->dev, "failed to request memory resource\n"); - ret = -EBUSY; - goto err_free; - } - - pwms[0].regbase = ioremap(r->start, resource_size(r)); - if (pwms[0].regbase == NULL) { - dev_err(&pdev->dev, "failed to ioremap() registers\n"); - ret = -ENODEV; - goto err_free_mem; - } - - for (i = 1; i < VT8500_NR_PWMS; i++) - pwms[i].regbase = pwms[0].regbase; - - for (i = 0; i < VT8500_NR_PWMS; i++) - __add_pwm(&pwms[i]); - - platform_set_drvdata(pdev, pwms); - return 0; - -err_free_mem: - release_mem_region(r->start, resource_size(r)); -err_free: - kfree(pwms); - return ret; -} - -static int __devexit pwm_remove(struct platform_device *pdev) -{ - struct pwm_device *pwms; - struct resource *r; - int i; - - pwms = platform_get_drvdata(pdev); - if (pwms == NULL) - return -ENODEV; - - mutex_lock(&pwm_lock); - - for (i = 0; i < VT8500_NR_PWMS; i++) - list_del(&pwms[i].node); - mutex_unlock(&pwm_lock); - - iounmap(pwms[0].regbase); - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - release_mem_region(r->start, resource_size(r)); - - kfree(pwms); - return 0; -} - -static struct platform_driver pwm_driver = { - .driver = { - .name = "vt8500-pwm", - .owner = THIS_MODULE, - }, - .probe = pwm_probe, - .remove = __devexit_p(pwm_remove), -}; - -static int __init pwm_init(void) -{ - return platform_driver_register(&pwm_driver); -} -arch_initcall(pwm_init); - -static void __exit pwm_exit(void) -{ - platform_driver_unregister(&pwm_driver); -} -module_exit(pwm_exit); - -MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 0d9aa92636c..a93feffbc36 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -55,4 +55,13 @@ config PWM_TEGRA To compile this driver as a module, choose M here: the module will be called pwm-tegra. +config PWM_VT8500 + tristate "vt8500 pwm support" + depends on ARCH_VT8500 + help + Generic PWM framework driver for vt8500. + + To compile this driver as a module, choose M here: the module + will be called pwm-vt8500. + endif diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index a1d87b17331..b7c0fcf4de1 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_PWM_IMX) += pwm-imx.o obj-$(CONFIG_PWM_PXA) += pwm-pxa.o obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o +obj-$(CONFIG_PWM_VT8500) += pwm-vt8500.o diff --git a/drivers/pwm/pwm-vt8500.c b/drivers/pwm/pwm-vt8500.c new file mode 100644 index 00000000000..3db0746f720 --- /dev/null +++ b/drivers/pwm/pwm-vt8500.c @@ -0,0 +1,208 @@ +/* + * arch/arm/mach-vt8500/pwm.c + * + * Copyright (C) 2010 Alexey Charkov + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define VT8500_NR_PWMS 4 + +struct vt8500_chip { + struct pwm_chip chip; + void __iomem *base; +}; + +#define to_vt8500_chip(chip) container_of(chip, struct vt8500_chip, chip) + +#define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t) +static inline void pwm_busy_wait(void __iomem *reg, u8 bitmask) +{ + int loops = msecs_to_loops(10); + while ((readb(reg) & bitmask) && --loops) + cpu_relax(); + + if (unlikely(!loops)) + pr_warning("Waiting for status bits 0x%x to clear timed out\n", + bitmask); +} + +static int vt8500_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct vt8500_chip *vt8500 = to_vt8500_chip(chip); + unsigned long long c; + unsigned long period_cycles, prescale, pv, dc; + + c = 25000000/2; /* wild guess --- need to implement clocks */ + c = c * period_ns; + do_div(c, 1000000000); + period_cycles = c; + + if (period_cycles < 1) + period_cycles = 1; + prescale = (period_cycles - 1) / 4096; + pv = period_cycles / (prescale + 1) - 1; + if (pv > 4095) + pv = 4095; + + if (prescale > 1023) + return -EINVAL; + + c = (unsigned long long)pv * duty_ns; + do_div(c, period_ns); + dc = c; + + pwm_busy_wait(vt8500->base + 0x40 + pwm->hwpwm, (1 << 1)); + writel(prescale, vt8500->base + 0x4 + (pwm->hwpwm << 4)); + + pwm_busy_wait(vt8500->base + 0x40 + pwm->hwpwm, (1 << 2)); + writel(pv, vt8500->base + 0x8 + (pwm->hwpwm << 4)); + + pwm_busy_wait(vt8500->base + 0x40 + pwm->hwpwm, (1 << 3)); + writel(dc, vt8500->base + 0xc + (pwm->hwpwm << 4)); + + return 0; +} + +static int vt8500_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct vt8500_chip *vt8500 = to_vt8500_chip(chip); + + pwm_busy_wait(vt8500->base + 0x40 + pwm->hwpwm, (1 << 0)); + writel(5, vt8500->base + (pwm->hwpwm << 4)); + return 0; +} + +static void vt8500_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct vt8500_chip *vt8500 = to_vt8500_chip(chip); + + pwm_busy_wait(vt8500->base + 0x40 + pwm->hwpwm, (1 << 0)); + writel(0, vt8500->base + (pwm->hwpwm << 4)); +} + +static struct pwm_ops vt8500_pwm_ops = { + .enable = vt8500_pwm_enable, + .disable = vt8500_pwm_disable, + .config = vt8500_pwm_config, + .owner = THIS_MODULE, +}; + +static int __devinit pwm_probe(struct platform_device *pdev) +{ + struct vt8500_chip *chip; + struct resource *r; + int ret; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (chip == NULL) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + chip->chip.dev = &pdev->dev; + chip->chip.ops = &vt8500_pwm_ops; + chip->chip.base = -1; + chip->chip.npwm = VT8500_NR_PWMS; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (r == NULL) { + dev_err(&pdev->dev, "no memory resource defined\n"); + ret = -ENODEV; + goto err_free; + } + + r = request_mem_region(r->start, resource_size(r), pdev->name); + if (r == NULL) { + dev_err(&pdev->dev, "failed to request memory resource\n"); + ret = -EBUSY; + goto err_free; + } + + chip->base = ioremap(r->start, resource_size(r)); + if (chip->base == NULL) { + dev_err(&pdev->dev, "failed to ioremap() registers\n"); + ret = -ENODEV; + goto err_free_mem; + } + + ret = pwmchip_add(&chip->chip); + if (ret < 0) + goto err_unmap; + + platform_set_drvdata(pdev, chip); + return ret; + +err_unmap: + iounmap(chip->base); +err_free_mem: + release_mem_region(r->start, resource_size(r)); +err_free: + kfree(chip); + return ret; +} + +static int __devexit pwm_remove(struct platform_device *pdev) +{ + struct vt8500_chip *chip; + struct resource *r; + int err; + + chip = platform_get_drvdata(pdev); + if (chip == NULL) + return -ENODEV; + + err = pwmchip_remove(&chip->chip); + if (err < 0) + return err; + + iounmap(chip->base); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(r->start, resource_size(r)); + + kfree(chip); + return 0; +} + +static struct platform_driver pwm_driver = { + .driver = { + .name = "vt8500-pwm", + .owner = THIS_MODULE, + }, + .probe = pwm_probe, + .remove = __devexit_p(pwm_remove), +}; + +static int __init pwm_init(void) +{ + return platform_driver_register(&pwm_driver); +} +arch_initcall(pwm_init); + +static void __exit pwm_exit(void) +{ + platform_driver_unregister(&pwm_driver); +} +module_exit(pwm_exit); + +MODULE_LICENSE("GPL"); -- cgit v1.2.3-70-g09d2