diff options
44 files changed, 3308 insertions, 1007 deletions
diff --git a/Documentation/devicetree/bindings/pwm/lpc32xx-pwm.txt b/Documentation/devicetree/bindings/pwm/lpc32xx-pwm.txt new file mode 100644 index 000000000000..cfe1db3bb6e9 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/lpc32xx-pwm.txt @@ -0,0 +1,12 @@ +LPC32XX PWM controller + +Required properties: +- compatible: should be "nxp,lpc3220-pwm" +- reg: physical base address and length of the controller's registers + +Examples: + +pwm@0x4005C000 { + compatible = "nxp,lpc3220-pwm"; + reg = <0x4005C000 0x8>; +}; diff --git a/Documentation/devicetree/bindings/pwm/mxs-pwm.txt b/Documentation/devicetree/bindings/pwm/mxs-pwm.txt new file mode 100644 index 000000000000..b16f4a57d111 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/mxs-pwm.txt @@ -0,0 +1,17 @@ +Freescale MXS PWM controller + +Required properties: +- compatible: should be "fsl,imx23-pwm" +- reg: physical base address and length of the controller's registers +- #pwm-cells: should be 2. The first cell specifies the per-chip index + of the PWM to use and the second cell is the duty cycle in nanoseconds. +- fsl,pwm-number: the number of PWM devices + +Example: + +pwm: pwm@80064000 { + compatible = "fsl,imx28-pwm", "fsl,imx23-pwm"; + reg = <0x80064000 2000>; + #pwm-cells = <2>; + fsl,pwm-number = <8>; +}; diff --git a/Documentation/devicetree/bindings/pwm/nvidia,tegra20-pwm.txt b/Documentation/devicetree/bindings/pwm/nvidia,tegra20-pwm.txt new file mode 100644 index 000000000000..bbbeedb4ec05 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/nvidia,tegra20-pwm.txt @@ -0,0 +1,18 @@ +Tegra SoC PWFM controller + +Required properties: +- compatible: should be one of: + - "nvidia,tegra20-pwm" + - "nvidia,tegra30-pwm" +- reg: physical base address and length of the controller's registers +- #pwm-cells: On Tegra the number of cells used to specify a PWM is 2. The + first cell specifies the per-chip index of the PWM to use and the second + cell is the duty cycle in nanoseconds. + +Example: + + pwm: pwm@7000a000 { + compatible = "nvidia,tegra20-pwm"; + reg = <0x7000a000 0x100>; + #pwm-cells = <2>; + }; diff --git a/Documentation/devicetree/bindings/pwm/pwm.txt b/Documentation/devicetree/bindings/pwm/pwm.txt new file mode 100644 index 000000000000..73ec962bfe8c --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/pwm.txt @@ -0,0 +1,57 @@ +Specifying PWM information for devices +====================================== + +1) PWM user nodes +----------------- + +PWM users should specify a list of PWM devices that they want to use +with a property containing a 'pwm-list': + + pwm-list ::= <single-pwm> [pwm-list] + single-pwm ::= <pwm-phandle> <pwm-specifier> + pwm-phandle : phandle to PWM controller node + pwm-specifier : array of #pwm-cells specifying the given PWM + (controller specific) + +PWM properties should be named "pwms". The exact meaning of each pwms +property must be documented in the device tree binding for each device. +An optional property "pwm-names" may contain a list of strings to label +each of the PWM devices listed in the "pwms" property. If no "pwm-names" +property is given, the name of the user node will be used as fallback. + +Drivers for devices that use more than a single PWM device can use the +"pwm-names" property to map the name of the PWM device requested by the +pwm_get() call to an index into the list given by the "pwms" property. + +The following example could be used to describe a PWM-based backlight +device: + + pwm: pwm { + #pwm-cells = <2>; + }; + + [...] + + bl: backlight { + pwms = <&pwm 0 5000000>; + pwm-names = "backlight"; + }; + +pwm-specifier typically encodes the chip-relative PWM number and the PWM +period in nanoseconds. Note that in the example above, specifying the +"pwm-names" is redundant because the name "backlight" would be used as +fallback anyway. + +2) PWM controller nodes +----------------------- + +PWM controller nodes must specify the number of cells used for the +specifier using the '#pwm-cells' property. + +An example PWM controller might look like this: + + pwm: pwm@7000a000 { + compatible = "nvidia,tegra20-pwm"; + reg = <0x7000a000 0x100>; + #pwm-cells = <2>; + }; diff --git a/Documentation/devicetree/bindings/video/backlight/pwm-backlight.txt b/Documentation/devicetree/bindings/video/backlight/pwm-backlight.txt new file mode 100644 index 000000000000..1e4fc727f3b1 --- /dev/null +++ b/Documentation/devicetree/bindings/video/backlight/pwm-backlight.txt @@ -0,0 +1,28 @@ +pwm-backlight bindings + +Required properties: + - compatible: "pwm-backlight" + - pwms: OF device-tree PWM specification (see PWM binding[0]) + - brightness-levels: Array of distinct brightness levels. Typically these + are in the range from 0 to 255, but any range starting at 0 will do. + The actual brightness level (PWM duty cycle) will be interpolated + from these values. 0 means a 0% duty cycle (darkest/off), while the + last value in the array represents a 100% duty cycle (brightest). + - default-brightness-level: the default brightness level (index into the + array defined by the "brightness-levels" property) + +Optional properties: + - pwm-names: a list of names for the PWM devices specified in the + "pwms" property (see PWM binding[0]) + +[0]: Documentation/devicetree/bindings/pwm/pwm.txt + +Example: + + backlight { + compatible = "pwm-backlight"; + pwms = <&pwm 0 5000000>; + + brightness-levels = <0 4 8 16 32 64 128 255>; + default-brightness-level = <6>; + }; diff --git a/Documentation/pwm.txt b/Documentation/pwm.txt new file mode 100644 index 000000000000..554290ebab94 --- /dev/null +++ b/Documentation/pwm.txt @@ -0,0 +1,76 @@ +Pulse Width Modulation (PWM) interface + +This provides an overview about the Linux PWM interface + +PWMs are commonly used for controlling LEDs, fans or vibrators in +cell phones. PWMs with a fixed purpose have no need implementing +the Linux PWM API (although they could). However, PWMs are often +found as discrete devices on SoCs which have no fixed purpose. It's +up to the board designer to connect them to LEDs or fans. To provide +this kind of flexibility the generic PWM API exists. + +Identifying PWMs +---------------- + +Users of the legacy PWM API use unique IDs to refer to PWM devices. + +Instead of referring to a PWM device via its unique ID, board setup code +should instead register a static mapping that can be used to match PWM +consumers to providers, as given in the following example: + + static struct pwm_lookup board_pwm_lookup[] = { + PWM_LOOKUP("tegra-pwm", 0, "pwm-backlight", NULL), + }; + + static void __init board_init(void) + { + ... + pwm_add_table(board_pwm_lookup, ARRAY_SIZE(board_pwm_lookup)); + ... + } + +Using PWMs +---------- + +Legacy users can request a PWM device using pwm_request() and free it +after usage with pwm_free(). + +New users should use the pwm_get() function and pass to it the consumer +device or a consumer name. pwm_put() is used to free the PWM device. + +After being requested a PWM has to be configured using: + +int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns); + +To start/stop toggling the PWM output use pwm_enable()/pwm_disable(). + +Implementing a PWM driver +------------------------- + +Currently there are two ways to implement pwm drivers. Traditionally +there only has been the barebone API meaning that each driver has +to implement the pwm_*() functions itself. This means that it's impossible +to have multiple PWM drivers in the system. For this reason it's mandatory +for new drivers to use the generic PWM framework. + +A new PWM controller/chip can be added using pwmchip_add() and removed +again with pwmchip_remove(). pwmchip_add() takes a filled in struct +pwm_chip as argument which provides a description of the PWM chip, the +number of PWM devices provider by the chip and the chip-specific +implementation of the supported PWM operations to the framework. + +Locking +------- + +The PWM core list manipulations are protected by a mutex, so pwm_request() +and pwm_free() may not be called from an atomic context. Currently the +PWM core does not enforce any locking to pwm_enable(), pwm_disable() and +pwm_config(), so the calling context is currently driver specific. This +is an issue derived from the former barebone API and should be fixed soon. + +Helpers +------- + +Currently a PWM can only be configured with period_ns and duty_ns. For several +use cases freq_hz and duty_percent might be better. Instead of calculating +this in your driver please consider adding appropriate helpers to the framework. diff --git a/MAINTAINERS b/MAINTAINERS index bd451649f13a..b141083b2621 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5526,6 +5526,18 @@ S: Maintained F: Documentation/video4linux/README.pvrusb2 F: drivers/media/video/pvrusb2/ +PWM SUBSYSTEM +M: Thierry Reding <thierry.reding@avionic-design.de> +L: linux-kernel@vger.kernel.org +S: Maintained +W: http://gitorious.org/linux-pwm +T: git git://gitorious.org/linux-pwm/linux-pwm.git +F: Documentation/pwm.txt +F: Documentation/devicetree/bindings/pwm/ +F: include/linux/pwm.h +F: include/linux/of_pwm.h +F: drivers/pwm/ + PXA2xx/PXA3xx SUPPORT M: Eric Miao <eric.y.miao@gmail.com> M: Russell King <linux@arm.linux.org.uk> diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index fbdd8533c05d..6b86bb963a28 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1009,7 +1009,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/boot/dts/tegra20.dtsi b/arch/arm/boot/dts/tegra20.dtsi index 9f1921634eb7..405d1673904e 100644 --- a/arch/arm/boot/dts/tegra20.dtsi +++ b/arch/arm/boot/dts/tegra20.dtsi @@ -123,6 +123,12 @@ status = "disabled"; }; + 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 da740191771f..3e4334d14efb 100644 --- a/arch/arm/boot/dts/tegra30.dtsi +++ b/arch/arm/boot/dts/tegra30.dtsi @@ -117,6 +117,12 @@ status = "disabled"; }; + 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 d0de9c1192f7..c0999633a9ab 100644 --- a/arch/arm/mach-tegra/board-dt-tegra20.c +++ b/arch/arm/mach-tegra/board-dt-tegra20.c @@ -64,7 +64,8 @@ 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-apbdma", 0x6000a000, "tegra-apbdma", NULL), + OF_DEV_AUXDATA("nvidia,tegra20-apbdma", TEGRA_APB_DMA_BASE, "tegra-apbdma", NULL), + 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 ee48214bfd89..53bf60f11580 100644 --- a/arch/arm/mach-tegra/board-dt-tegra30.c +++ b/arch/arm/mach-tegra/board-dt-tegra30.c @@ -33,6 +33,8 @@ #include <asm/mach/arch.h> #include <asm/hardware/gic.h> +#include <mach/iomap.h> + #include "board.h" #include "clock.h" @@ -48,6 +50,7 @@ struct of_dev_auxdata tegra30_auxdata_lookup[] __initdata = { 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-apbdma", 0x6000a000, "tegra-apbdma", NULL), + OF_DEV_AUXDATA("nvidia,tegra30-pwm", TEGRA_PWFM_BASE, "tegra-pwm", NULL), {} }; diff --git a/arch/arm/mach-vt8500/Makefile b/arch/arm/mach-vt8500/Makefile index 54e69973f39b..7ce51767c99c 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 8ad825e93592..000000000000 --- a/arch/arm/mach-vt8500/pwm.c +++ /dev/null @@ -1,265 +0,0 @@ -/* - * arch/arm/mach-vt8500/pwm.c - * - * Copyright (C) 2010 Alexey Charkov <alchark@gmail.com> - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include <linux/module.h> -#include <linux/kernel.h> -#include <linux/platform_device.h> -#include <linux/slab.h> -#include <linux/err.h> -#include <linux/io.h> -#include <linux/pwm.h> -#include <linux/delay.h> - -#include <asm/div64.h> - -#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/arch/arm/plat-mxc/Kconfig b/arch/arm/plat-mxc/Kconfig index c722f9ce6918..baf9064c0844 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 63b064b5c1d5..6ac720031150 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-pxa/Makefile b/arch/arm/plat-pxa/Makefile index f302d048392d..af8e484001e5 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 ef32686feef9..000000000000 --- 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 <eric.miao@marvell.com> - */ - -#include <linux/module.h> -#include <linux/kernel.h> -#include <linux/platform_device.h> -#include <linux/slab.h> -#include <linux/err.h> -#include <linux/clk.h> -#include <linux/io.h> -#include <linux/pwm.h> - -#include <asm/div64.h> - -#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/arch/arm/plat-samsung/Makefile b/arch/arm/plat-samsung/Makefile index b78717496677..9e40e8d00740 100644 --- a/arch/arm/plat-samsung/Makefile +++ b/arch/arm/plat-samsung/Makefile @@ -59,7 +59,3 @@ obj-$(CONFIG_SAMSUNG_WAKEMASK) += wakeup-mask.o obj-$(CONFIG_S5P_PM) += s5p-pm.o s5p-irq-pm.o obj-$(CONFIG_S5P_SLEEP) += s5p-sleep.o - -# PWM support - -obj-$(CONFIG_HAVE_PWM) += pwm.o diff --git a/arch/blackfin/Kconfig b/arch/blackfin/Kconfig index 9b765107e15c..ec44fc6c34ca 100644 --- a/arch/blackfin/Kconfig +++ b/arch/blackfin/Kconfig @@ -1002,16 +1002,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 08e6625106be..735f24e07425 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 33f5942733bd..000000000000 --- 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 <linux/module.h> -#include <linux/pwm.h> -#include <linux/slab.h> - -#include <asm/gptimers.h> -#include <asm/portmux.h> - -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/Kconfig b/drivers/Kconfig index bfc918633fd9..805c432c9439 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -148,4 +148,6 @@ source "drivers/iio/Kconfig" source "drivers/vme/Kconfig" +source "drivers/pwm/Kconfig" + endmenu diff --git a/drivers/Makefile b/drivers/Makefile index 2ba29ffef2cb..bd36f09f2246 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -8,6 +8,7 @@ # GPIO must come after pinctrl as gpios may need to mux pins etc obj-y += pinctrl/ obj-y += gpio/ +obj-y += pwm/ obj-$(CONFIG_PCI) += pci/ obj-$(CONFIG_PARISC) += parisc/ obj-$(CONFIG_RAPIDIO) += rapidio/ diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 92144ed1ad46..4fdc04ef37c2 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -276,6 +276,7 @@ config TWL6030_PWM tristate "TWL6030 PWM (Pulse Width Modulator) Support" depends on TWL4030_CORE select HAVE_PWM + depends on !PWM default n help Say yes here if you want support for TWL6030 PWM. diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 154f3ef07631..98a442da892a 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -64,6 +64,7 @@ config AB8500_PWM bool "AB8500 PWM support" depends on AB8500_CORE && ARCH_U8500 select HAVE_PWM + depends on !PWM help This driver exports functions to enable/disble/config/free Pulse Width Modulation in the Analog Baseband Chip AB8500. diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig new file mode 100644 index 000000000000..8fc3808d7a3e --- /dev/null +++ b/drivers/pwm/Kconfig @@ -0,0 +1,108 @@ +menuconfig PWM + bool "PWM Support" + depends on !MACH_JZ4740 && !PUV3_PWM + help + This enables PWM support through the generic PWM framework. + You only need to enable this, if you also want to enable + one or more of the PWM drivers below. + + If unsure, say N. + +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_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_LPC32XX + tristate "LPC32XX PWM support" + depends on ARCH_LPC32XX + help + Generic PWM framework driver for LPC32XX. The LPC32XX SOC has two + PWM controllers. + + To compile this driver as a module, choose M here: the module + will be called pwm-lpc32xx. + +config PWM_MXS + tristate "Freescale MXS PWM support" + depends on ARCH_MXS && OF + select STMP_DEVICE + help + Generic PWM framework driver for Freescale MXS. + + To compile this driver as a module, choose M here: the module + will be called pwm-mxs. + +config PWM_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_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 + help + Generic PWM framework driver for the PWFM controller found on NVIDIA + Tegra SoCs. + + To compile this driver as a module, choose M here: the module + will be called pwm-tegra. + +config PWM_TIECAP + tristate "ECAP PWM support" + depends on SOC_AM33XX + help + PWM driver support for the ECAP APWM controller found on AM33XX + TI SOC + + To compile this driver as a module, choose M here: the module + will be called pwm-tiecap. + +config PWM_TIEHRPWM + tristate "EHRPWM PWM support" + depends on SOC_AM33XX + help + PWM driver support for the EHRPWM controller found on AM33XX + TI SOC + + To compile this driver as a module, choose M here: the module + will be called pwm-tiehrpwm. + +config PWM_VT8500 + tristate "vt8500 pwm support" + depends on ARCH_VT8500 + help + Generic PWM framework driver for vt8500. + + To compile this driver as a module, choose M here: the module + will be called pwm-vt8500. + +endif diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile new file mode 100644 index 000000000000..e4b2c898964d --- /dev/null +++ b/drivers/pwm/Makefile @@ -0,0 +1,11 @@ +obj-$(CONFIG_PWM) += core.o +obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o +obj-$(CONFIG_PWM_IMX) += pwm-imx.o +obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.o +obj-$(CONFIG_PWM_MXS) += pwm-mxs.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_TIECAP) += pwm-tiecap.o +obj-$(CONFIG_PWM_TIEHRPWM) += pwm-tiehrpwm.o +obj-$(CONFIG_PWM_VT8500) += pwm-vt8500.o diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c new file mode 100644 index 000000000000..ecb76909e946 --- /dev/null +++ b/drivers/pwm/core.c @@ -0,0 +1,713 @@ +/* + * Generic pwmlib implementation + * + * Copyright (C) 2011 Sascha Hauer <s.hauer@pengutronix.de> + * Copyright (C) 2011-2012 Avionic Design GmbH + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/pwm.h> +#include <linux/radix-tree.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> + +#define MAX_PWMS 1024 + +static DEFINE_MUTEX(pwm_lookup_lock); +static LIST_HEAD(pwm_lookup_list); +static DEFINE_MUTEX(pwm_lock); +static LIST_HEAD(pwm_chips); +static DECLARE_BITMAP(allocated_pwms, MAX_PWMS); +static RADIX_TREE(pwm_tree, GFP_KERNEL); + +static struct pwm_device *pwm_to_device(unsigned int pwm) +{ + return radix_tree_lookup(&pwm_tree, pwm); +} + +static int alloc_pwms(int pwm, unsigned int count) +{ + unsigned int from = 0; + unsigned int start; + + if (pwm >= MAX_PWMS) + return -EINVAL; + + if (pwm >= 0) + from = pwm; + + start = bitmap_find_next_zero_area(allocated_pwms, MAX_PWMS, from, + count, 0); + + if (pwm >= 0 && start != pwm) + return -EEXIST; + + if (start + count > MAX_PWMS) + return -ENOSPC; + + return start; +} + +static void free_pwms(struct pwm_chip *chip) +{ + unsigned int i; + + for (i = 0; i < chip->npwm; i++) { + struct pwm_device *pwm = &chip->pwms[i]; + radix_tree_delete(&pwm_tree, pwm->pwm); + } + + bitmap_clear(allocated_pwms, chip->base, chip->npwm); + + kfree(chip->pwms); + chip->pwms = NULL; +} + +static struct pwm_chip *pwmchip_find_by_name(const char *name) +{ + struct pwm_chip *chip; + + if (!name) + return NULL; + + mutex_lock(&pwm_lock); + + list_for_each_entry(chip, &pwm_chips, list) { + const char *chip_name = dev_name(chip->dev); + + if (chip_name && strcmp(chip_name, name) == 0) { + mutex_unlock(&pwm_lock); + return chip; + } + } + + mutex_unlock(&pwm_lock); + + return NULL; +} + +static int pwm_device_request(struct pwm_device *pwm, const char *label) +{ + int err; + + if (test_bit(PWMF_REQUESTED, &pwm->flags)) + return -EBUSY; + + if (!try_module_get(pwm->chip->ops->owner)) + return -ENODEV; + + if (pwm->chip->ops->request) { + err = pwm->chip->ops->request(pwm->chip, pwm); + if (err) { + module_put(pwm->chip->ops->owner); + return err; + } + } + + set_bit(PWMF_REQUESTED, &pwm->flags); + pwm->label = label; + + return 0; +} + +static struct pwm_device *of_pwm_simple_xlate(struct pwm_chip *pc, + const struct of_phandle_args *args) +{ + struct pwm_device *pwm; + + if (pc->of_pwm_n_cells < 2) + return ERR_PTR(-EINVAL); + + if (args->args[0] >= pc->npwm) + return ERR_PTR(-EINVAL); + + pwm = pwm_request_from_chip(pc, args->args[0], NULL); + if (IS_ERR(pwm)) + return pwm; + + pwm_set_period(pwm, args->args[1]); + + return pwm; +} + +void of_pwmchip_add(struct pwm_chip *chip) +{ + if (!chip->dev || !chip->dev->of_node) + return; + + if (!chip->of_xlate) { + chip->of_xlate = of_pwm_simple_xlate; + chip->of_pwm_n_cells = 2; + } + + of_node_get(chip->dev->of_node); +} + +void of_pwmchip_remove(struct pwm_chip *chip) +{ + if (chip->dev && chip->dev->of_node) + of_node_put(chip->dev->of_node); +} + +/** + * pwm_set_chip_data() - set private chip data for a PWM + * @pwm: PWM device + * @data: pointer to chip-specific data + */ +int pwm_set_chip_data(struct pwm_device *pwm, void *data) +{ + if (!pwm) + return -EINVAL; + + pwm->chip_data = data; + + return 0; +} + +/** + * pwm_get_chip_data() - get private chip data for a PWM + * @pwm: PWM device + */ +void *pwm_get_chip_data(struct pwm_device *pwm) +{ + return pwm ? pwm->chip_data : NULL; +} + +/** + * pwmchip_add() - register a new PWM chip + * @chip: the PWM chip to add + * + * Register a new PWM chip. If chip->base < 0 then a dynamically assigned base + * will be used. + */ +int pwmchip_add(struct pwm_chip *chip) +{ + struct pwm_device *pwm; + unsigned int i; + int ret; + + if (!chip || !chip->dev || !chip->ops || !chip->ops->config || + !chip->ops->enable || !chip->ops->disable) + return -EINVAL; + + mutex_lock(&pwm_lock); + + ret = alloc_pwms(chip->base, chip->npwm); + if (ret < 0) + goto out; + + chip->pwms = kzalloc(chip->npwm * sizeof(*pwm), GFP_KERNEL); + if (!chip->pwms) { + ret = -ENOMEM; + goto out; + } + + chip->base = ret; + + for (i = 0; i < chip->npwm; i++) { + pwm = &chip->pwms[i]; + + pwm->chip = chip; + pwm->pwm = chip->base + i; + pwm->hwpwm = i; + + radix_tree_insert(&pwm_tree, pwm->pwm, pwm); + } + + bitmap_set(allocated_pwms, chip->base, chip->npwm); + + INIT_LIST_HEAD(&chip->list); + list_add(&chip->list, &pwm_chips); + + ret = 0; + + if (IS_ENABLED(CONFIG_OF)) + of_pwmchip_add(chip); + +out: + mutex_unlock(&pwm_lock); + return ret; +} +EXPORT_SYMBOL_GPL(pwmchip_add); + +/** + * pwmchip_remove() - remove a PWM chip + * @chip: the PWM chip to remove + * + * Removes a PWM chip. This function may return busy if the PWM chip provides + * a PWM device that is still requested. + */ +int pwmchip_remove(struct pwm_chip *chip) +{ + unsigned int i; + int ret = 0; + + mutex_lock(&pwm_lock); + + for (i = 0; i < chip->npwm; i++) { + struct pwm_device *pwm = &chip->pwms[i]; + + if (test_bit(PWMF_REQUESTED, &pwm->flags)) { + ret = -EBUSY; + goto out; + } + } + + list_del_init(&chip->list); + + if (IS_ENABLED(CONFIG_OF)) + of_pwmchip_remove(chip); + + free_pwms(chip); + +out: + mutex_unlock(&pwm_lock); + return ret; +} +EXPORT_SYMBOL_GPL(pwmchip_remove); + +/** + * pwm_request() - request a PWM device + * @pwm_id: global PWM device index + * @label: PWM device label + * + * This function is deprecated, use pwm_get() instead. + */ +struct pwm_device *pwm_request(int pwm, const char *label) +{ + struct pwm_device *dev; + int err; + + if (pwm < 0 || pwm >= MAX_PWMS) + return ERR_PTR(-EINVAL); + + mutex_lock(&pwm_lock); + + dev = pwm_to_device(pwm); + if (!dev) { + dev = ERR_PTR(-EPROBE_DEFER); + goto out; + } + + err = pwm_device_request(dev, label); + if (err < 0) + dev = ERR_PTR(err); + +out: + mutex_unlock(&pwm_lock); + + return dev; +} +EXPORT_SYMBOL_GPL(pwm_request); + +/** + * pwm_request_from_chip() - request a PWM device relative to a PWM chip + * @chip: PWM chip + * @index: per-chip index of the PWM to request + * @label: a literal description string of this PWM + * + * Returns the PWM at the given index of the given PWM chip. A negative error + * code is returned if the index is not valid for the specified PWM chip or + * if the PWM device cannot be requested. + */ +struct pwm_device *pwm_request_from_chip(struct pwm_chip *chip, + unsigned int index, + const char *label) +{ + struct pwm_device *pwm; + int err; + + if (!chip || index >= chip->npwm) + return ERR_PTR(-EINVAL); + + mutex_lock(&pwm_lock); + pwm = &chip->pwms[index]; + + err = pwm_device_request(pwm, label); + if (err < 0) + pwm = ERR_PTR(err); + + mutex_unlock(&pwm_lock); + return pwm; +} +EXPORT_SYMBOL_GPL(pwm_request_from_chip); + +/** + * pwm_free() - free a PWM device + * @pwm: PWM device + * + * This function is deprecated, use pwm_put() instead. + */ +void pwm_free(struct pwm_device *pwm) +{ + pwm_put(pwm); +} +EXPORT_SYMBOL_GPL(pwm_free); + +/** + * pwm_config() - change a PWM device configuration + * @pwm: PWM device + * @duty_ns: "on" time (in nanoseconds) + * @period_ns: duration (in nanoseconds) of one cycle + */ +int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) +{ + if (!pwm || period_ns == 0 || duty_ns > period_ns) + return -EINVAL; + + return pwm->chip->ops->config(pwm->chip, pwm, duty_ns, period_ns); +} +EXPORT_SYMBOL_GPL(pwm_config); + +/** + * pwm_enable() - start a PWM output toggling + * @pwm: PWM device + */ +int pwm_enable(struct pwm_device *pwm) +{ + if (pwm && !test_and_set_bit(PWMF_ENABLED, &pwm->flags)) + return pwm->chip->ops->enable(pwm->chip, pwm); + + return pwm ? 0 : -EINVAL; +} +EXPORT_SYMBOL_GPL(pwm_enable); + +/** + * pwm_disable() - stop a PWM output toggling + * @pwm: PWM device + */ +void pwm_disable(struct pwm_device *pwm) +{ + if (pwm && test_and_clear_bit(PWMF_ENABLED, &pwm->flags)) + pwm->chip->ops->disable(pwm->chip, pwm); +} +EXPORT_SYMBOL_GPL(pwm_disable); + +static struct pwm_chip *of_node_to_pwmchip(struct device_node *np) +{ + struct pwm_chip *chip; + + mutex_lock(&pwm_lock); + + list_for_each_entry(chip, &pwm_chips, list) + if (chip->dev && chip->dev->of_node == np) { + mutex_unlock(&pwm_lock); + return chip; + } + + mutex_unlock(&pwm_lock); + + return ERR_PTR(-EPROBE_DEFER); +} + +/** + * of_pwm_request() - request a PWM via the PWM framework + * @np: device node to get the PWM from + * @con_id: consumer name + * + * Returns the PWM device parsed from the phandle and index specified in the + * "pwms" property of a device tree node or a negative error-code on failure. + * Values parsed from the device tree are stored in the returned PWM device + * object. + * + * If con_id is NULL, the first PWM device listed in the "pwms" property will + * be requested. Otherwise the "pwm-names" property is used to do a reverse + * lookup of the PWM index. This also means that the "pwm-names" property + * becomes mandatory for devices that look up the PWM device via the con_id + * parameter. + */ +static struct pwm_device *of_pwm_request(struct device_node *np, + const char *con_id) +{ + struct pwm_device *pwm = NULL; + struct of_phandle_args args; + struct pwm_chip *pc; + int index = 0; + int err; + + if (con_id) { + index = of_property_match_string(np, "pwm-names", con_id); + if (index < 0) + return ERR_PTR(index); + } + + err = of_parse_phandle_with_args(np, "pwms", "#pwm-cells", index, + &args); + if (err) { + pr_debug("%s(): can't parse \"pwms\" property\n", __func__); + return ERR_PTR(err); + } + + pc = of_node_to_pwmchip(args.np); + if (IS_ERR(pc)) { + pr_debug("%s(): PWM chip not found\n", __func__); + pwm = ERR_CAST(pc); + goto put; + } + + if (args.args_count != pc->of_pwm_n_cells) { + pr_debug("%s: wrong #pwm-cells for %s\n", np->full_name, + args.np->full_name); + pwm = ERR_PTR(-EINVAL); + goto put; + } + + pwm = pc->of_xlate(pc, &args); + if (IS_ERR(pwm)) + goto put; + + /* + * If a consumer name was not given, try to look it up from the + * "pwm-names" property if it exists. Otherwise use the name of + * the user device node. + */ + if (!con_id) { + err = of_property_read_string_index(np, "pwm-names", index, + &con_id); + if (err < 0) + con_id = np->name; + } + + pwm->label = con_id; + +put: + of_node_put(args.np); + + return pwm; +} + +/** + * pwm_add_table() - register PWM device consumers + * @table: array of consumers to register + * @num: number of consumers in table + */ +void __init pwm_add_table(struct pwm_lookup *table, size_t num) +{ + mutex_lock(&pwm_lookup_lock); + + while (num--) { + list_add_tail(&table->list, &pwm_lookup_list); + table++; + } + + mutex_unlock(&pwm_lookup_lock); +} + +/** + * pwm_get() - look up and request a PWM device + * @dev: device for PWM consumer + * @con_id: consumer name + * + * Lookup is first attempted using DT. If the device was not instantiated from + * a device tree, a PWM chip and a relative index is looked up via a table + * supplied by board setup code (see pwm_add_table()). + * + * Once a PWM chip has been found the specified PWM device will be requested + * and is ready to be used. + */ +struct pwm_device *pwm_get(struct device *dev, const char *con_id) +{ + struct pwm_device *pwm = ERR_PTR(-EPROBE_DEFER); + const char *dev_id = dev ? dev_name(dev): NULL; + struct pwm_chip *chip = NULL; + unsigned int index = 0; + unsigned int best = 0; + struct pwm_lookup *p; + unsigned int match; + + /* look up via DT first */ + if (IS_ENABLED(CONFIG_OF) && dev && dev->of_node) + return of_pwm_request(dev->of_node, con_id); + + /* + * We look up the provider in the static table typically provided by + * board setup code. We first try to lookup the consumer device by + * name. If the consumer device was passed in as NULL or if no match + * was found, we try to find the consumer by directly looking it up + * by name. + * + * If a match is found, the provider PWM chip is looked up by name + * and a PWM device is requested using the PWM device per-chip index. + * + * The lookup algorithm was shamelessly taken from the clock + * framework: + * + * We do slightly fuzzy matching here: + * An entry with a NULL ID is assumed to be a wildcard. + * If an entry has a device ID, it must match + * If an entry has a connection ID, it must match + * Then we take the most specific entry - with the following order + * of precedence: dev+con > dev only > con only. + */ + mutex_lock(&pwm_lookup_lock); + + list_for_each_entry(p, &pwm_lookup_list, list) { + match = 0; + + if (p->dev_id) { + if (!dev_id || strcmp(p->dev_id, dev_id)) + continue; + + match += 2; + } + + if (p->con_id) { + if (!con_id || strcmp(p->con_id, con_id)) + continue; + + match += 1; + } + + if (match > best) { + chip = pwmchip_find_by_name(p->provider); + index = p->index; + + if (match != 3) + best = match; + else + break; + } + } + + if (chip) + pwm = pwm_request_from_chip(chip, index, con_id ?: dev_id); + + mutex_unlock(&pwm_lookup_lock); + + return pwm; +} +EXPORT_SYMBOL_GPL(pwm_get); + +/** + * pwm_put() - release a PWM device + * @pwm: PWM device + */ +void pwm_put(struct pwm_device *pwm) +{ + if (!pwm) + return; + + mutex_lock(&pwm_lock); + + if (!test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) { + pr_warning("PWM device already freed\n"); + goto out; + } + + if (pwm->chip->ops->free) + pwm->chip->ops->free(pwm->chip, pwm); + + pwm->label = NULL; + + module_put(pwm->chip->ops->owner); +out: + mutex_unlock(&pwm_lock); +} +EXPORT_SYMBOL_GPL(pwm_put); + +#ifdef CONFIG_DEBUG_FS +static void pwm_dbg_show(struct pwm_chip *chip, struct seq_file *s) +{ + unsigned int i; + + for (i = 0; i < chip->npwm; i++) { + struct pwm_device *pwm = &chip->pwms[i]; + + seq_printf(s, " pwm-%-3d (%-20.20s):", i, pwm->label); + + if (test_bit(PWMF_REQUESTED, &pwm->flags)) + seq_printf(s, " requested"); + + if (test_bit(PWMF_ENABLED, &pwm->flags)) + seq_printf(s, " enabled"); + + seq_printf(s, "\n"); + } +} + +static void *pwm_seq_start(struct seq_file *s, loff_t *pos) +{ + mutex_lock(&pwm_lock); + s->private = ""; + + return seq_list_start(&pwm_chips, *pos); +} + +static void *pwm_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + s->private = "\n"; + + return seq_list_next(v, &pwm_chips, pos); +} + +static void pwm_seq_stop(struct seq_file *s, void *v) +{ + mutex_unlock(&pwm_lock); +} + +static int pwm_seq_show(struct seq_file *s, void *v) +{ + struct pwm_chip *chip = list_entry(v, struct pwm_chip, list); + + seq_printf(s, "%s%s/%s, %d PWM device%s\n", (char *)s->private, + chip->dev->bus ? chip->dev->bus->name : "no-bus", + dev_name(chip->dev), chip->npwm, + (chip->npwm != 1) ? "s" : ""); + + if (chip->ops->dbg_show) + chip->ops->dbg_show(chip, s); + else + pwm_dbg_show(chip, s); + + return 0; +} + +static const struct seq_operations pwm_seq_ops = { + .start = pwm_seq_start, + .next = pwm_seq_next, + .stop = pwm_seq_stop, + .show = pwm_seq_show, +}; + +static int pwm_seq_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &pwm_seq_ops); +} + +static const struct file_operations pwm_debugfs_ops = { + .owner = THIS_MODULE, + .open = pwm_seq_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static int __init pwm_debugfs_init(void) +{ + debugfs_create_file("pwm", S_IFREG | S_IRUGO, NULL, NULL, + &pwm_debugfs_ops); + + return 0; +} + +subsys_initcall(pwm_debugfs_init); +#endif /* CONFIG_DEBUG_FS */ diff --git a/drivers/pwm/pwm-bfin.c b/drivers/pwm/pwm-bfin.c new file mode 100644 index 000000000000..d53c4e7941ef --- /dev/null +++ b/drivers/pwm/pwm-bfin.c @@ -0,0 +1,162 @@ +/* + * Blackfin Pulse Width Modulation (PWM) core + * + * Copyright (c) 2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> + +#include <asm/gptimers.h> +#include <asm/portmux.h> + +struct bfin_pwm_chip { + struct pwm_chip chip; +}; + +struct bfin_pwm { + unsigned short pin; +}; + +static const unsigned short pwm_to_gptimer_per[] = { + P_TMR0, P_TMR1, P_TMR2, P_TMR3, P_TMR4, P_TMR5, + P_TMR6, P_TMR7, P_TMR8, P_TMR9, P_TMR10, P_TMR11, +}; + +static int bfin_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct bfin_pwm *priv; + int ret; + + if (pwm->hwpwm >= ARRAY_SIZE(pwm_to_gptimer_per)) + return -EINVAL; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->pin = pwm_to_gptimer_per[pwm->hwpwm]; + + ret = peripheral_request(priv->pin, NULL); + if (ret) { + kfree(priv); + return ret; + } + + pwm_set_chip_data(pwm, priv); + + return 0; +} + +static void bfin_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct bfin_pwm *priv = pwm_get_chip_data(pwm); + + if (priv) { + peripheral_free(priv->pin); + kfree(priv); + } +} + +static int bfin_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct bfin_pwm *priv = pwm_get_chip_data(pwm); + unsigned long period, duty; + unsigned long long val; + + 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); + + return pwmchip_remove(&pwm->chip); +} + +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"); diff --git a/arch/arm/plat-mxc/pwm.c b/drivers/pwm/pwm-imx.c index c0cab2270dd1..2a0b35333972 100644 --- a/arch/arm/plat-mxc/pwm.c +++ b/drivers/pwm/pwm-imx.c @@ -39,33 +39,28 @@ #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 imx_chip { struct clk *clk; int clk_enabled; void __iomem *mmio_base; - unsigned int use_count; - unsigned int pwm_id; + struct pwm_chip chip; }; -int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) +#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) { - if (pwm == NULL || period_ns == 0 || duty_ns > period_ns) - return -EINVAL; + 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(pwm->clk); + c = clk_get_rate(imx->clk); c = c * period_ns; do_div(c, 1000000000); period_cycles = c; @@ -86,8 +81,8 @@ int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) else period_cycles = 0; - writel(duty_cycles, pwm->mmio_base + MX3_PWMSAR); - writel(period_cycles, pwm->mmio_base + MX3_PWMPR); + 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 | @@ -98,7 +93,7 @@ int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) else cr |= MX3_PWMCR_CLKSRC_IPG_HIGH; - writel(cr, pwm->mmio_base + MX3_PWMCR); + 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 @@ -116,191 +111,120 @@ int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) * both the prescaler (/1 .. /128) and then by CLKSEL * (/2 .. /16). */ - u32 max = readl(pwm->mmio_base + MX1_PWMP); + u32 max = readl(imx->mmio_base + MX1_PWMP); u32 p = max * duty_ns / period_ns; - writel(max - p, pwm->mmio_base + MX1_PWMS); + writel(max - p, imx->mmio_base + MX1_PWMS); } else { BUG(); } return 0; } -EXPORT_SYMBOL(pwm_config); -int pwm_enable(struct pwm_device *pwm) +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 (!pwm->clk_enabled) { - rc = clk_prepare_enable(pwm->clk); + if (!imx->clk_enabled) { + rc = clk_prepare_enable(imx->clk); if (!rc) - pwm->clk_enabled = 1; + imx->clk_enabled = 1; } return rc; } -EXPORT_SYMBOL(pwm_enable); -void pwm_disable(struct pwm_device *pwm) +static void imx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) { - writel(0, pwm->mmio_base + MX3_PWMCR); + struct imx_chip *imx = to_imx_chip(chip); - if (pwm->clk_enabled) { - clk_disable_unprepare(pwm->clk); - pwm->clk_enabled = 0; - } -} -EXPORT_SYMBOL(pwm_disable); + writel(0, imx->mmio_base + MX3_PWMCR); -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 (imx->clk_enabled) { + clk_disable_unprepare(imx->clk); + imx->clk_enabled = 0; } - - 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 struct pwm_ops imx_pwm_ops = { + .enable = imx_pwm_enable, + .disable = imx_pwm_disable, + .config = imx_pwm_config, + .owner = THIS_MODULE, +}; -static int __devinit mxc_pwm_probe(struct platform_device *pdev) +static int __devinit imx_pwm_probe(struct platform_device *pdev) { - struct pwm_device *pwm; + struct imx_chip *imx; struct resource *r; int ret = 0; - pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL); - if (pwm == NULL) { + imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL); + if (imx == NULL) { dev_err(&pdev->dev, "failed to allocate memory\n"); return -ENOMEM; } - pwm->clk = clk_get(&pdev->dev, "pwm"); + imx->clk = devm_clk_get(&pdev->dev, "pwm"); - if (IS_ERR(pwm->clk)) { - ret = PTR_ERR(pwm->clk); - goto err_free; - } + if (IS_ERR(imx->clk)) + return PTR_ERR(imx->clk); - pwm->clk_enabled = 0; + imx->chip.ops = &imx_pwm_ops; + imx->chip.dev = &pdev->dev; + imx->chip.base = -1; + imx->chip.npwm = 1; - pwm->use_count = 0; - pwm->pwm_id = pdev->id; - pwm->pdev = pdev; + 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; + return -ENODEV; } - 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; - } + imx->mmio_base = devm_request_and_ioremap(&pdev->dev, r); + if (imx->mmio_base == NULL) + return -EADDRNOTAVAIL; - mutex_lock(&pwm_lock); - list_add_tail(&pwm->node, &pwm_list); - mutex_unlock(&pwm_lock); + ret = pwmchip_add(&imx->chip); + if (ret < 0) + return ret; - platform_set_drvdata(pdev, pwm); + platform_set_drvdata(pdev, imx); 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) +static int __devexit imx_pwm_remove(struct platform_device *pdev) { - struct pwm_device *pwm; - struct resource *r; + struct imx_chip *imx; - pwm = platform_get_drvdata(pdev); - if (pwm == NULL) + imx = platform_get_drvdata(pdev); + if (imx == 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; + return pwmchip_remove(&imx->chip); } -static struct platform_driver mxc_pwm_driver = { +static struct platform_driver imx_pwm_driver = { .driver = { .name = "mxc_pwm", }, - .probe = mxc_pwm_probe, - .remove = __devexit_p(mxc_pwm_remove), + .probe = imx_pwm_probe, + .remove = __devexit_p(imx_pwm_remove), }; -static int __init mxc_pwm_init(void) +static int __init imx_pwm_init(void) { - return platform_driver_register(&mxc_pwm_driver); + return platform_driver_register(&imx_pwm_driver); } -arch_initcall(mxc_pwm_init); +arch_initcall(imx_pwm_init); -static void __exit mxc_pwm_exit(void) +static void __exit imx_pwm_exit(void) { - platform_driver_unregister(&mxc_pwm_driver); + platform_driver_unregister(&imx_pwm_driver); } -module_exit(mxc_pwm_exit); +module_exit(imx_pwm_exit); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); diff --git a/drivers/pwm/pwm-lpc32xx.c b/drivers/pwm/pwm-lpc32xx.c new file mode 100644 index 000000000000..adb87f0c1633 --- /dev/null +++ b/drivers/pwm/pwm-lpc32xx.c @@ -0,0 +1,148 @@ +/* + * Copyright 2012 Alexandre Pereira da Silva <aletes.xgr@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2. + * + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> + +struct lpc32xx_pwm_chip { + struct pwm_chip chip; + struct clk *clk; + void __iomem *base; +}; + +#define PWM_ENABLE (1 << 31) +#define PWM_RELOADV(x) (((x) & 0xFF) << 8) +#define PWM_DUTY(x) ((x) & 0xFF) + +#define to_lpc32xx_pwm_chip(_chip) \ + container_of(_chip, struct lpc32xx_pwm_chip, chip) + +static int lpc32xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct lpc32xx_pwm_chip *lpc32xx = to_lpc32xx_pwm_chip(chip); + unsigned long long c; + int period_cycles, duty_cycles; + + 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; + + c = 256 * duty_ns; + do_div(c, period_ns); + duty_cycles = c; + + writel(PWM_ENABLE | PWM_RELOADV(period_cycles) | PWM_DUTY(duty_cycles), + lpc32xx->base + (pwm->hwpwm << 2)); + + return 0; +} + +static int lpc32xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct lpc32xx_pwm_chip *lpc32xx = to_lpc32xx_pwm_chip(chip); + + return clk_enable(lpc32xx->clk); +} + +static void lpc32xx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct lpc32xx_pwm_chip *lpc32xx = to_lpc32xx_pwm_chip(chip); + + writel(0, lpc32xx->base + (pwm->hwpwm << 2)); + clk_disable(lpc32xx->clk); +} + +static const struct pwm_ops lpc32xx_pwm_ops = { + .config = lpc32xx_pwm_config, + .enable = lpc32xx_pwm_enable, + .disable = lpc32xx_pwm_disable, + .owner = THIS_MODULE, +}; + +static int lpc32xx_pwm_probe(struct platform_device *pdev) +{ + struct lpc32xx_pwm_chip *lpc32xx; + struct resource *res; + int ret; + + lpc32xx = devm_kzalloc(&pdev->dev, sizeof(*lpc32xx), GFP_KERNEL); + if (!lpc32xx) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + + lpc32xx->base = devm_request_and_ioremap(&pdev->dev, res); + if (!lpc32xx->base) + return -EADDRNOTAVAIL; + + lpc32xx->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(lpc32xx->clk)) + return PTR_ERR(lpc32xx->clk); + + lpc32xx->chip.dev = &pdev->dev; + lpc32xx->chip.ops = &lpc32xx_pwm_ops; + lpc32xx->chip.npwm = 2; + + ret = pwmchip_add(&lpc32xx->chip); + if (ret < 0) { + dev_err(&pdev->dev, "failed to add PWM chip, error %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, lpc32xx); + + return 0; +} + +static int __devexit lpc32xx_pwm_remove(struct platform_device *pdev) +{ + struct lpc32xx_pwm_chip *lpc32xx = platform_get_drvdata(pdev); + + clk_disable(lpc32xx->clk); + return pwmchip_remove(&lpc32xx->chip); +} + +static struct of_device_id lpc32xx_pwm_dt_ids[] = { + { .compatible = "nxp,lpc3220-pwm", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, lpc32xx_pwm_dt_ids); + +static struct platform_driver lpc32xx_pwm_driver = { + .driver = { + .name = "lpc32xx-pwm", + .of_match_table = of_match_ptr(lpc32xx_pwm_dt_ids), + }, + .probe = lpc32xx_pwm_probe, + .remove = __devexit_p(lpc32xx_pwm_remove), +}; +module_platform_driver(lpc32xx_pwm_driver); + +MODULE_ALIAS("platform:lpc32xx-pwm"); +MODULE_AUTHOR("Alexandre Pereira da Silva <aletes.xgr@gmail.com>"); +MODULE_DESCRIPTION("LPC32XX PWM Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-mxs.c b/drivers/pwm/pwm-mxs.c new file mode 100644 index 000000000000..e5852646f082 --- /dev/null +++ b/drivers/pwm/pwm-mxs.c @@ -0,0 +1,203 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/pinctrl/consumer.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> +#include <linux/stmp_device.h> + +#define SET 0x4 +#define CLR 0x8 +#define TOG 0xc + +#define PWM_CTRL 0x0 +#define PWM_ACTIVE0 0x10 +#define PWM_PERIOD0 0x20 +#define PERIOD_PERIOD(p) ((p) & 0xffff) +#define PERIOD_PERIOD_MAX 0x10000 +#define PERIOD_ACTIVE_HIGH (3 << 16) +#define PERIOD_INACTIVE_LOW (2 << 18) +#define PERIOD_CDIV(div) (((div) & 0x7) << 20) +#define PERIOD_CDIV_MAX 8 + +struct mxs_pwm_chip { + struct pwm_chip chip; + struct device *dev; + struct clk *clk; + void __iomem *base; +}; + +#define to_mxs_pwm_chip(_chip) container_of(_chip, struct mxs_pwm_chip, chip) + +static int mxs_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct mxs_pwm_chip *mxs = to_mxs_pwm_chip(chip); + int ret, div = 0; + unsigned int period_cycles, duty_cycles; + unsigned long rate; + unsigned long long c; + + rate = clk_get_rate(mxs->clk); + while (1) { + c = rate / (1 << div); + c = c * period_ns; + do_div(c, 1000000000); + if (c < PERIOD_PERIOD_MAX) + break; + div++; + if (div > PERIOD_CDIV_MAX) + return -EINVAL; + } + + period_cycles = c; + c *= duty_ns; + do_div(c, period_ns); + duty_cycles = c; + + /* + * If the PWM channel is disabled, make sure to turn on the clock + * before writing the register. Otherwise, keep it enabled. + */ + if (!test_bit(PWMF_ENABLED, &pwm->flags)) { + ret = clk_prepare_enable(mxs->clk); + if (ret) + return ret; + } + + writel(duty_cycles << 16, + mxs->base + PWM_ACTIVE0 + pwm->hwpwm * 0x20); + writel(PERIOD_PERIOD(period_cycles) | PERIOD_ACTIVE_HIGH | + PERIOD_INACTIVE_LOW | PERIOD_CDIV(div), + mxs->base + PWM_PERIOD0 + pwm->hwpwm * 0x20); + + /* + * If the PWM is not enabled, turn the clock off again to save power. + */ + if (!test_bit(PWMF_ENABLED, &pwm->flags)) + clk_disable_unprepare(mxs->clk); + + return 0; +} + +static int mxs_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct mxs_pwm_chip *mxs = to_mxs_pwm_chip(chip); + int ret; + + ret = clk_prepare_enable(mxs->clk); + if (ret) + return ret; + + writel(1 << pwm->hwpwm, mxs->base + PWM_CTRL + SET); + + return 0; +} + +static void mxs_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct mxs_pwm_chip *mxs = to_mxs_pwm_chip(chip); + + writel(1 << pwm->hwpwm, mxs->base + PWM_CTRL + CLR); + + clk_disable_unprepare(mxs->clk); +} + +static const struct pwm_ops mxs_pwm_ops = { + .config = mxs_pwm_config, + .enable = mxs_pwm_enable, + .disable = mxs_pwm_disable, + .owner = THIS_MODULE, +}; + +static int mxs_pwm_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct mxs_pwm_chip *mxs; + struct resource *res; + struct pinctrl *pinctrl; + int ret; + + mxs = devm_kzalloc(&pdev->dev, sizeof(*mxs), GFP_KERNEL); + if (!mxs) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mxs->base = devm_request_and_ioremap(&pdev->dev, res); + if (!mxs->base) + return -EADDRNOTAVAIL; + + pinctrl = devm_pinctrl_get_select_default(&pdev->dev); + if (IS_ERR(pinctrl)) + return PTR_ERR(pinctrl); + + mxs->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(mxs->clk)) + return PTR_ERR(mxs->clk); + + mxs->chip.dev = &pdev->dev; + mxs->chip.ops = &mxs_pwm_ops; + mxs->chip.base = -1; + ret = of_property_read_u32(np, "fsl,pwm-number", &mxs->chip.npwm); + if (ret < 0) { + dev_err(&pdev->dev, "failed to get pwm number: %d\n", ret); + return ret; + } + + ret = pwmchip_add(&mxs->chip); + if (ret < 0) { + dev_err(&pdev->dev, "failed to add pwm chip %d\n", ret); + return ret; + } + + mxs->dev = &pdev->dev; + platform_set_drvdata(pdev, mxs); + + stmp_reset_block(mxs->base); + + return 0; +} + +static int __devexit mxs_pwm_remove(struct platform_device *pdev) +{ + struct mxs_pwm_chip *mxs = platform_get_drvdata(pdev); + + return pwmchip_remove(&mxs->chip); +} + +static struct of_device_id mxs_pwm_dt_ids[] = { + { .compatible = "fsl,imx23-pwm", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mxs_pwm_dt_ids); + +static struct platform_driver mxs_pwm_driver = { + .driver = { + .name = "mxs-pwm", + .of_match_table = of_match_ptr(mxs_pwm_dt_ids), + }, + .probe = mxs_pwm_probe, + .remove = __devexit_p(mxs_pwm_remove), +}; +module_platform_driver(mxs_pwm_driver); + +MODULE_ALIAS("platform:mxs-pwm"); +MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>"); +MODULE_DESCRIPTION("Freescale MXS PWM Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-pxa.c b/drivers/pwm/pwm-pxa.c new file mode 100644 index 000000000000..bd5867a1c700 --- /dev/null +++ b/drivers/pwm/pwm-pxa.c @@ -0,0 +1,218 @@ +/* + * drivers/pwm/pwm-pxa.c + * + * simple driver for PWM (Pulse Width Modulator) controller + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 2008-02-13 initial version + * eric miao <eric.miao@marvell.com> + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/pwm.h> + +#include <asm/div64.h> + +#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 = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); + if (pwm == NULL) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + pwm->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(pwm->clk)) + return PTR_ERR(pwm->clk); + + 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"); + return -ENODEV; + } + + pwm->mmio_base = devm_request_and_ioremap(&pdev->dev, r); + if (pwm->mmio_base == NULL) + return -EADDRNOTAVAIL; + + ret = pwmchip_add(&pwm->chip); + if (ret < 0) { + dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, pwm); + return 0; +} + +static int __devexit pwm_remove(struct platform_device *pdev) +{ + struct pxa_pwm_chip *chip; + + chip = platform_get_drvdata(pdev); + if (chip == NULL) + return -ENODEV; + + return pwmchip_remove(&chip->chip); +} + +static struct platform_driver pwm_driver = { + .driver = { + .name = "pxa25x-pwm", + .owner = THIS_MODULE, + }, + .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/arch/arm/plat-samsung/pwm.c b/drivers/pwm/pwm-samsung.c index d3583050fb05..d10386528c9c 100644 --- a/arch/arm/plat-samsung/pwm.c +++ b/drivers/pwm/pwm-samsung.c @@ -1,4 +1,4 @@ -/* arch/arm/plat-s3c/pwm.c +/* drivers/pwm/pwm-samsung.c * * Copyright (c) 2007 Ben Dooks * Copyright (c) 2008 Simtec Electronics @@ -11,6 +11,8 @@ * the Free Software Foundation; either version 2 of the License. */ +#define pr_fmt(fmt) "pwm-samsung: " fmt + #include <linux/export.h> #include <linux/kernel.h> #include <linux/platform_device.h> @@ -24,8 +26,7 @@ #include <plat/regs-timer.h> -struct pwm_device { - struct list_head list; +struct s3c_chip { struct platform_device *pdev; struct clk *clk_div; @@ -36,81 +37,36 @@ struct pwm_device { unsigned int duty_ns; unsigned char tcon_base; - unsigned char use_count; 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 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) +static inline int pwm_is_tdiv(struct s3c_chip *chip) { - 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); + return clk_get_parent(chip->clk) == chip->clk_div; } -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) +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(pwm); + tcon |= pwm_tcon_start(s3c); __raw_writel(tcon, S3C2410_TCON); local_irq_restore(flags); @@ -118,31 +74,28 @@ int pwm_enable(struct pwm_device *pwm) return 0; } -EXPORT_SYMBOL(pwm_enable); - -void pwm_disable(struct pwm_device *pwm) +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(pwm); + tcon &= ~pwm_tcon_start(s3c); __raw_writel(tcon, S3C2410_TCON); local_irq_restore(flags); } -EXPORT_SYMBOL(pwm_disable); - -static unsigned long pwm_calc_tin(struct pwm_device *pwm, unsigned long freq) +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(pwm->clk_div)); - pwm_dbg(pwm, "tin parent at %lu\n", tin_parent_rate); + 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) @@ -154,8 +107,10 @@ static unsigned long pwm_calc_tin(struct pwm_device *pwm, unsigned long freq) #define NS_IN_HZ (1000000000UL) -int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) +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; @@ -174,38 +129,38 @@ int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) if (duty_ns > period_ns) return -EINVAL; - if (period_ns == pwm->period_ns && - duty_ns == pwm->duty_ns) + 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(pwm->pwm_id)); - tcnt = __raw_readl(S3C2410_TCNTB(pwm->pwm_id)); + tcmp = __raw_readl(S3C2410_TCMPB(s3c->pwm_id)); + tcnt = __raw_readl(S3C2410_TCNTB(s3c->pwm_id)); period = NS_IN_HZ / period_ns; - pwm_dbg(pwm, "duty_ns=%d, period_ns=%d (%lu)\n", + 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 (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); + 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(pwm->clk); + tin_rate = clk_get_rate(s3c->clk); - pwm->period_ns = period_ns; + s3c->period_ns = period_ns; - pwm_dbg(pwm, "tin_rate=%lu\n", tin_rate); + 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(pwm->clk); + tin_ns = NS_IN_HZ / clk_get_rate(s3c->clk); /* Note, counters count down */ @@ -216,7 +171,7 @@ int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) if (tcmp == tcnt) tcmp--; - pwm_dbg(pwm, "tin_ns=%lu, tcmp=%ld/%lu\n", tin_ns, tcmp, tcnt); + pwm_dbg(s3c, "tin_ns=%lu, tcmp=%ld/%lu\n", tin_ns, tcmp, tcnt); if (tcmp < 0) tcmp = 0; @@ -225,15 +180,15 @@ int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) local_irq_save(flags); - __raw_writel(tcmp, S3C2410_TCMPB(pwm->pwm_id)); - __raw_writel(tcnt, S3C2410_TCNTB(pwm->pwm_id)); + __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(pwm); - tcon |= pwm_tcon_autoreload(pwm); + tcon |= pwm_tcon_manulupdate(s3c); + tcon |= pwm_tcon_autoreload(s3c); __raw_writel(tcon, S3C2410_TCON); - tcon &= ~pwm_tcon_manulupdate(pwm); + tcon &= ~pwm_tcon_manulupdate(s3c); __raw_writel(tcon, S3C2410_TCON); local_irq_restore(flags); @@ -241,24 +196,17 @@ int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) 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 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 pwm_device *pwm; + struct s3c_chip *s3c; unsigned long flags; unsigned long tcon; unsigned int id = pdev->id; @@ -269,83 +217,75 @@ static int s3c_pwm_probe(struct platform_device *pdev) return -ENXIO; } - pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL); - if (pwm == NULL) { + s3c = devm_kzalloc(&pdev->dev, sizeof(*s3c), GFP_KERNEL); + if (s3c == 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; + s3c->tcon_base = id == 0 ? 0 : (id * 4) + 4; + s3c->chip.ops = &s3c_pwm_ops; + s3c->chip.base = -1; + s3c->chip.npwm = 1; - pwm->clk = clk_get(dev, "pwm-tin"); - if (IS_ERR(pwm->clk)) { + s3c->clk = devm_clk_get(dev, "pwm-tin"); + if (IS_ERR(s3c->clk)) { dev_err(dev, "failed to get pwm tin clk\n"); - ret = PTR_ERR(pwm->clk); - goto err_alloc; + return PTR_ERR(s3c->clk); } - pwm->clk_div = clk_get(dev, "pwm-tdiv"); - if (IS_ERR(pwm->clk_div)) { + s3c->clk_div = devm_clk_get(dev, "pwm-tdiv"); + if (IS_ERR(s3c->clk_div)) { dev_err(dev, "failed to get pwm tdiv clk\n"); - ret = PTR_ERR(pwm->clk_div); - goto err_clk_tin; + return PTR_ERR(s3c->clk_div); } - clk_enable(pwm->clk); - clk_enable(pwm->clk_div); + clk_enable(s3c->clk); + clk_enable(s3c->clk_div); local_irq_save(flags); tcon = __raw_readl(S3C2410_TCON); - tcon |= pwm_tcon_invert(pwm); + tcon |= pwm_tcon_invert(s3c); __raw_writel(tcon, S3C2410_TCON); local_irq_restore(flags); - - ret = pwm_register(pwm); - if (ret) { + ret = pwmchip_add(&s3c->chip); + if (ret < 0) { 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); + 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(pwm->clk), - clk_get_rate(pwm->clk_div), - pwm_is_tdiv(pwm) ? "div" : "ext", pwm->tcon_base); + clk_get_rate(s3c->clk), + clk_get_rate(s3c->clk_div), + pwm_is_tdiv(s3c) ? "div" : "ext", s3c->tcon_base); - platform_set_drvdata(pdev, pwm); + platform_set_drvdata(pdev, s3c); 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); + clk_disable(s3c->clk_div); + clk_disable(s3c->clk); return ret; } static int __devexit s3c_pwm_remove(struct platform_device *pdev) { - struct pwm_device *pwm = platform_get_drvdata(pdev); + struct s3c_chip *s3c = platform_get_drvdata(pdev); + int err; + + err = pwmchip_remove(&s3c->chip); + if (err < 0) + return err; - clk_disable(pwm->clk_div); - clk_disable(pwm->clk); - clk_put(pwm->clk_div); - clk_put(pwm->clk); - kfree(pwm); + clk_disable(s3c->clk_div); + clk_disable(s3c->clk); return 0; } @@ -353,26 +293,26 @@ static int __devexit s3c_pwm_remove(struct platform_device *pdev) #ifdef CONFIG_PM static int s3c_pwm_suspend(struct platform_device *pdev, pm_message_t state) { - struct pwm_device *pwm = platform_get_drvdata(pdev); + 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 */ - pwm->period_ns = 0; - pwm->duty_ns = 0; + s3c->period_ns = 0; + s3c->duty_ns = 0; return 0; } static int s3c_pwm_resume(struct platform_device *pdev) { - struct pwm_device *pwm = platform_get_drvdata(pdev); + struct s3c_chip *s3c = platform_get_drvdata(pdev); unsigned long tcon; /* Restore invertion */ tcon = __raw_readl(S3C2410_TCON); - tcon |= pwm_tcon_invert(pwm); + tcon |= pwm_tcon_invert(s3c); __raw_writel(tcon, S3C2410_TCON); return 0; @@ -402,13 +342,13 @@ static int __init pwm_init(void) 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__); + pr_err("failed to get scaler clocks\n"); return -EINVAL; } ret = platform_driver_register(&s3c_pwm_driver); if (ret) - printk(KERN_ERR "%s: failed to add pwm driver\n", __func__); + pr_err("failed to add pwm driver\n"); return ret; } diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c new file mode 100644 index 000000000000..02ce18d5e49a --- /dev/null +++ b/drivers/pwm/pwm-tegra.c @@ -0,0 +1,261 @@ +/* + * drivers/pwm/pwm-tegra.c + * + * Tegra pulse-width-modulation controller driver + * + * Copyright (c) 2010, NVIDIA Corporation. + * Based on arch/arm/plat-mxc/pwm.c by Sascha Hauer <s.hauer@pengutronix.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/pwm.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#define PWM_ENABLE (1 << 31) +#define PWM_DUTY_WIDTH 8 +#define PWM_DUTY_SHIFT 16 +#define PWM_SCALE_WIDTH 13 +#define PWM_SCALE_SHIFT 0 + +#define NUM_PWM 4 + +struct tegra_pwm_chip { + struct pwm_chip chip; + struct device *dev; + + struct clk *clk; + + void __iomem *mmio_base; +}; + +static inline struct tegra_pwm_chip *to_tegra_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct tegra_pwm_chip, chip); +} + +static inline u32 pwm_readl(struct tegra_pwm_chip *chip, unsigned int num) +{ + return readl(chip->mmio_base + (num << 4)); +} + +static inline void pwm_writel(struct tegra_pwm_chip *chip, unsigned int num, + unsigned long val) +{ + writel(val, chip->mmio_base + (num << 4)); +} + +static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip); + unsigned long long c; + unsigned long rate, hz; + u32 val = 0; + int err; + + /* + * Convert from duty_ns / period_ns to a fixed number of duty ticks + * per (1 << PWM_DUTY_WIDTH) cycles and make sure to round to the + * nearest integer during division. + */ + c = duty_ns * ((1 << PWM_DUTY_WIDTH) - 1) + period_ns / 2; + do_div(c, period_ns); + + val = (u32)c << PWM_DUTY_SHIFT; + + /* + * Compute the prescaler value for which (1 << PWM_DUTY_WIDTH) + * cycles at the PWM clock rate will take period_ns nanoseconds. + */ + rate = clk_get_rate(pc->clk) >> PWM_DUTY_WIDTH; + hz = 1000000000ul / period_ns; + + rate = (rate + (hz / 2)) / hz; + + /* + * Since the actual PWM divider is the register's frequency divider + * field minus 1, we need to decrement to get the correct value to + * write to the register. + */ + if (rate > 0) + rate--; + + /* + * Make sure that the rate will fit in the register's frequency + * divider field. + */ + if (rate >> PWM_SCALE_WIDTH) + return -EINVAL; + + val |= rate << PWM_SCALE_SHIFT; + + /* + * If the PWM channel is disabled, make sure to turn on the clock + * before writing the register. Otherwise, keep it enabled. + */ + if (!test_bit(PWMF_ENABLED, &pwm->flags)) { + err = clk_prepare_enable(pc->clk); + if (err < 0) + return err; + } else + val |= PWM_ENABLE; + + pwm_writel(pc, pwm->hwpwm, val); + + /* + * If the PWM is not enabled, turn the clock off again to save power. + */ + if (!test_bit(PWMF_ENABLED, &pwm->flags)) + clk_disable_unprepare(pc->clk); + + return 0; +} + +static int tegra_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip); + int rc = 0; + u32 val; + + rc = clk_prepare_enable(pc->clk); + if (rc < 0) + return rc; + + val = pwm_readl(pc, pwm->hwpwm); + val |= PWM_ENABLE; + pwm_writel(pc, pwm->hwpwm, val); + + return 0; +} + +static void tegra_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip); + u32 val; + + val = pwm_readl(pc, pwm->hwpwm); + val &= ~PWM_ENABLE; + pwm_writel(pc, pwm->hwpwm, val); + + clk_disable_unprepare(pc->clk); +} + +static const struct pwm_ops tegra_pwm_ops = { + .config = tegra_pwm_config, + .enable = tegra_pwm_enable, + .disable = tegra_pwm_disable, + .owner = THIS_MODULE, +}; + +static int tegra_pwm_probe(struct platform_device *pdev) +{ + struct tegra_pwm_chip *pwm; + struct resource *r; + int ret; + + pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL); + if (!pwm) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + pwm->dev = &pdev->dev; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + dev_err(&pdev->dev, "no memory resources defined\n"); + return -ENODEV; + } + + pwm->mmio_base = devm_request_and_ioremap(&pdev->dev, r); + if (!pwm->mmio_base) { + dev_err(&pdev->dev, "failed to ioremap() region\n"); + return -EADDRNOTAVAIL; + } + + platform_set_drvdata(pdev, pwm); + + pwm->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(pwm->clk)) + return PTR_ERR(pwm->clk); + + pwm->chip.dev = &pdev->dev; + pwm->chip.ops = &tegra_pwm_ops; + pwm->chip.base = -1; + pwm->chip.npwm = NUM_PWM; + + ret = pwmchip_add(&pwm->chip); + if (ret < 0) { + dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int __devexit tegra_pwm_remove(struct platform_device *pdev) +{ + struct tegra_pwm_chip *pc = platform_get_drvdata(pdev); + int i; + + if (WARN_ON(!pc)) + return -ENODEV; + + for (i = 0; i < NUM_PWM; i++) { + struct pwm_device *pwm = &pc->chip.pwms[i]; + + if (!test_bit(PWMF_ENABLED, &pwm->flags)) + if (clk_prepare_enable(pc->clk) < 0) + continue; + + pwm_writel(pc, i, 0); + + clk_disable_unprepare(pc->clk); + } + + return pwmchip_remove(&pc->chip); +} + +#ifdef CONFIG_OF +static struct of_device_id tegra_pwm_of_match[] = { + { .compatible = "nvidia,tegra20-pwm" }, + { .compatible = "nvidia,tegra30-pwm" }, + { } +}; + +MODULE_DEVICE_TABLE(of, tegra_pwm_of_match); +#endif + +static struct platform_driver tegra_pwm_driver = { + .driver = { + .name = "tegra-pwm", + .of_match_table = of_match_ptr(tegra_pwm_of_match), + }, + .probe = tegra_pwm_probe, + .remove = __devexit_p(tegra_pwm_remove), +}; + +module_platform_driver(tegra_pwm_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("NVIDIA Corporation"); +MODULE_ALIAS("platform:tegra-pwm"); diff --git a/drivers/pwm/pwm-tiecap.c b/drivers/pwm/pwm-tiecap.c new file mode 100644 index 000000000000..3c2ad284ee3e --- /dev/null +++ b/drivers/pwm/pwm-tiecap.c @@ -0,0 +1,232 @@ +/* + * ECAP PWM driver + * + * Copyright (C) 2012 Texas Instruments, Inc. - http://www.ti.com/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/pm_runtime.h> +#include <linux/pwm.h> + +/* ECAP registers and bits definitions */ +#define CAP1 0x08 +#define CAP2 0x0C +#define CAP3 0x10 +#define CAP4 0x14 +#define ECCTL2 0x2A +#define ECCTL2_APWM_MODE BIT(9) +#define ECCTL2_SYNC_SEL_DISA (BIT(7) | BIT(6)) +#define ECCTL2_TSCTR_FREERUN BIT(4) + +struct ecap_pwm_chip { + struct pwm_chip chip; + unsigned int clk_rate; + void __iomem *mmio_base; +}; + +static inline struct ecap_pwm_chip *to_ecap_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct ecap_pwm_chip, chip); +} + +/* + * period_ns = 10^9 * period_cycles / PWM_CLK_RATE + * duty_ns = 10^9 * duty_cycles / PWM_CLK_RATE + */ +static int ecap_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip); + unsigned long long c; + unsigned long period_cycles, duty_cycles; + unsigned int reg_val; + + if (period_ns < 0 || duty_ns < 0 || period_ns > NSEC_PER_SEC) + return -ERANGE; + + c = pc->clk_rate; + c = c * period_ns; + do_div(c, NSEC_PER_SEC); + period_cycles = (unsigned long)c; + + if (period_cycles < 1) { + period_cycles = 1; + duty_cycles = 1; + } else { + c = pc->clk_rate; + c = c * duty_ns; + do_div(c, NSEC_PER_SEC); + duty_cycles = (unsigned long)c; + } + + pm_runtime_get_sync(pc->chip.dev); + + reg_val = readw(pc->mmio_base + ECCTL2); + + /* Configure APWM mode & disable sync option */ + reg_val |= ECCTL2_APWM_MODE | ECCTL2_SYNC_SEL_DISA; + + writew(reg_val, pc->mmio_base + ECCTL2); + + if (!test_bit(PWMF_ENABLED, &pwm->flags)) { + /* Update active registers if not running */ + writel(duty_cycles, pc->mmio_base + CAP2); + writel(period_cycles, pc->mmio_base + CAP1); + } else { + /* + * Update shadow registers to configure period and + * compare values. This helps current PWM period to + * complete on reconfiguring + */ + writel(duty_cycles, pc->mmio_base + CAP4); + writel(period_cycles, pc->mmio_base + CAP3); + } + + pm_runtime_put_sync(pc->chip.dev); + return 0; +} + +static int ecap_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip); + unsigned int reg_val; + + /* Leave clock enabled on enabling PWM */ + pm_runtime_get_sync(pc->chip.dev); + + /* + * Enable 'Free run Time stamp counter mode' to start counter + * and 'APWM mode' to enable APWM output + */ + reg_val = readw(pc->mmio_base + ECCTL2); + reg_val |= ECCTL2_TSCTR_FREERUN | ECCTL2_APWM_MODE; + writew(reg_val, pc->mmio_base + ECCTL2); + return 0; +} + +static void ecap_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip); + unsigned int reg_val; + + /* + * Disable 'Free run Time stamp counter mode' to stop counter + * and 'APWM mode' to put APWM output to low + */ + reg_val = readw(pc->mmio_base + ECCTL2); + reg_val &= ~(ECCTL2_TSCTR_FREERUN | ECCTL2_APWM_MODE); + writew(reg_val, pc->mmio_base + ECCTL2); + + /* Disable clock on PWM disable */ + pm_runtime_put_sync(pc->chip.dev); +} + +static void ecap_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + if (test_bit(PWMF_ENABLED, &pwm->flags)) { + dev_warn(chip->dev, "Removing PWM device without disabling\n"); + pm_runtime_put_sync(chip->dev); + } +} + +static const struct pwm_ops ecap_pwm_ops = { + .free = ecap_pwm_free, + .config = ecap_pwm_config, + .enable = ecap_pwm_enable, + .disable = ecap_pwm_disable, + .owner = THIS_MODULE, +}; + +static int __devinit ecap_pwm_probe(struct platform_device *pdev) +{ + int ret; + struct resource *r; + struct clk *clk; + struct ecap_pwm_chip *pc; + + pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); + if (!pc) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + clk = devm_clk_get(&pdev->dev, "fck"); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "failed to get clock\n"); + return PTR_ERR(clk); + } + + pc->clk_rate = clk_get_rate(clk); + if (!pc->clk_rate) { + dev_err(&pdev->dev, "failed to get clock rate\n"); + return -EINVAL; + } + + pc->chip.dev = &pdev->dev; + pc->chip.ops = &ecap_pwm_ops; + pc->chip.base = -1; + pc->chip.npwm = 1; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + dev_err(&pdev->dev, "no memory resource defined\n"); + return -ENODEV; + } + + pc->mmio_base = devm_request_and_ioremap(&pdev->dev, r); + if (!pc->mmio_base) { + dev_err(&pdev->dev, "failed to ioremap() registers\n"); + return -EADDRNOTAVAIL; + } + + ret = pwmchip_add(&pc->chip); + if (ret < 0) { + dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); + return ret; + } + + pm_runtime_enable(&pdev->dev); + platform_set_drvdata(pdev, pc); + return 0; +} + +static int __devexit ecap_pwm_remove(struct platform_device *pdev) +{ + struct ecap_pwm_chip *pc = platform_get_drvdata(pdev); + + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + return pwmchip_remove(&pc->chip); +} + +static struct platform_driver ecap_pwm_driver = { + .driver = { + .name = "ecap", + }, + .probe = ecap_pwm_probe, + .remove = __devexit_p(ecap_pwm_remove), +}; + +module_platform_driver(ecap_pwm_driver); + +MODULE_DESCRIPTION("ECAP PWM driver"); +MODULE_AUTHOR("Texas Instruments"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c new file mode 100644 index 000000000000..010d232cb0c8 --- /dev/null +++ b/drivers/pwm/pwm-tiehrpwm.c @@ -0,0 +1,411 @@ +/* + * EHRPWM PWM driver + * + * Copyright (C) 2012 Texas Instruments, Inc. - http://www.ti.com/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/pm_runtime.h> + +/* EHRPWM registers and bits definitions */ + +/* Time base module registers */ +#define TBCTL 0x00 +#define TBPRD 0x0A + +#define TBCTL_RUN_MASK (BIT(15) | BIT(14)) +#define TBCTL_STOP_NEXT 0 +#define TBCTL_STOP_ON_CYCLE BIT(14) +#define TBCTL_FREE_RUN (BIT(15) | BIT(14)) +#define TBCTL_PRDLD_MASK BIT(3) +#define TBCTL_PRDLD_SHDW 0 +#define TBCTL_PRDLD_IMDT BIT(3) +#define TBCTL_CLKDIV_MASK (BIT(12) | BIT(11) | BIT(10) | BIT(9) | \ + BIT(8) | BIT(7)) +#define TBCTL_CTRMODE_MASK (BIT(1) | BIT(0)) +#define TBCTL_CTRMODE_UP 0 +#define TBCTL_CTRMODE_DOWN BIT(0) +#define TBCTL_CTRMODE_UPDOWN BIT(1) +#define TBCTL_CTRMODE_FREEZE (BIT(1) | BIT(0)) + +#define TBCTL_HSPCLKDIV_SHIFT 7 +#define TBCTL_CLKDIV_SHIFT 10 + +#define CLKDIV_MAX 7 +#define HSPCLKDIV_MAX 7 +#define PERIOD_MAX 0xFFFF + +/* compare module registers */ +#define CMPA 0x12 +#define CMPB 0x14 + +/* Action qualifier module registers */ +#define AQCTLA 0x16 +#define AQCTLB 0x18 +#define AQSFRC 0x1A +#define AQCSFRC 0x1C + +#define AQCTL_CBU_MASK (BIT(9) | BIT(8)) +#define AQCTL_CBU_FRCLOW BIT(8) +#define AQCTL_CBU_FRCHIGH BIT(9) +#define AQCTL_CBU_FRCTOGGLE (BIT(9) | BIT(8)) +#define AQCTL_CAU_MASK (BIT(5) | BIT(4)) +#define AQCTL_CAU_FRCLOW BIT(4) +#define AQCTL_CAU_FRCHIGH BIT(5) +#define AQCTL_CAU_FRCTOGGLE (BIT(5) | BIT(4)) +#define AQCTL_PRD_MASK (BIT(3) | BIT(2)) +#define AQCTL_PRD_FRCLOW BIT(2) +#define AQCTL_PRD_FRCHIGH BIT(3) +#define AQCTL_PRD_FRCTOGGLE (BIT(3) | BIT(2)) +#define AQCTL_ZRO_MASK (BIT(1) | BIT(0)) +#define AQCTL_ZRO_FRCLOW BIT(0) +#define AQCTL_ZRO_FRCHIGH BIT(1) +#define AQCTL_ZRO_FRCTOGGLE (BIT(1) | BIT(0)) + +#define AQSFRC_RLDCSF_MASK (BIT(7) | BIT(6)) +#define AQSFRC_RLDCSF_ZRO 0 +#define AQSFRC_RLDCSF_PRD BIT(6) +#define AQSFRC_RLDCSF_ZROPRD BIT(7) +#define AQSFRC_RLDCSF_IMDT (BIT(7) | BIT(6)) + +#define AQCSFRC_CSFB_MASK (BIT(3) | BIT(2)) +#define AQCSFRC_CSFB_FRCDIS 0 +#define AQCSFRC_CSFB_FRCLOW BIT(2) +#define AQCSFRC_CSFB_FRCHIGH BIT(3) +#define AQCSFRC_CSFB_DISSWFRC (BIT(3) | BIT(2)) +#define AQCSFRC_CSFA_MASK (BIT(1) | BIT(0)) +#define AQCSFRC_CSFA_FRCDIS 0 +#define AQCSFRC_CSFA_FRCLOW BIT(0) +#define AQCSFRC_CSFA_FRCHIGH BIT(1) +#define AQCSFRC_CSFA_DISSWFRC (BIT(1) | BIT(0)) + +#define NUM_PWM_CHANNEL 2 /* EHRPWM channels */ + +struct ehrpwm_pwm_chip { + struct pwm_chip chip; + unsigned int clk_rate; + void __iomem *mmio_base; +}; + +static inline struct ehrpwm_pwm_chip *to_ehrpwm_pwm_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct ehrpwm_pwm_chip, chip); +} + +static void ehrpwm_write(void *base, int offset, unsigned int val) +{ + writew(val & 0xFFFF, base + offset); +} + +static void ehrpwm_modify(void *base, int offset, + unsigned short mask, unsigned short val) +{ + unsigned short regval; + + regval = readw(base + offset); + regval &= ~mask; + regval |= val & mask; + writew(regval, base + offset); +} + +/** + * set_prescale_div - Set up the prescaler divider function + * @rqst_prescaler: prescaler value min + * @prescale_div: prescaler value set + * @tb_clk_div: Time Base Control prescaler bits + */ +static int set_prescale_div(unsigned long rqst_prescaler, + unsigned short *prescale_div, unsigned short *tb_clk_div) +{ + unsigned int clkdiv, hspclkdiv; + + for (clkdiv = 0; clkdiv <= CLKDIV_MAX; clkdiv++) { + for (hspclkdiv = 0; hspclkdiv <= HSPCLKDIV_MAX; hspclkdiv++) { + + /* + * calculations for prescaler value : + * prescale_div = HSPCLKDIVIDER * CLKDIVIDER. + * HSPCLKDIVIDER = 2 ** hspclkdiv + * CLKDIVIDER = (1), if clkdiv == 0 *OR* + * (2 * clkdiv), if clkdiv != 0 + * + * Configure prescale_div value such that period + * register value is less than 65535. + */ + + *prescale_div = (1 << clkdiv) * + (hspclkdiv ? (hspclkdiv * 2) : 1); + if (*prescale_div > rqst_prescaler) { + *tb_clk_div = (clkdiv << TBCTL_CLKDIV_SHIFT) | + (hspclkdiv << TBCTL_HSPCLKDIV_SHIFT); + return 0; + } + } + } + return 1; +} + +static void configure_chans(struct ehrpwm_pwm_chip *pc, int chan, + unsigned long duty_cycles) +{ + int cmp_reg, aqctl_reg; + unsigned short aqctl_val, aqctl_mask; + + /* + * Channels can be configured from action qualifier module. + * Channel 0 configured with compare A register and for + * up-counter mode. + * Channel 1 configured with compare B register and for + * up-counter mode. + */ + if (chan == 1) { + aqctl_reg = AQCTLB; + cmp_reg = CMPB; + /* Configure PWM Low from compare B value */ + aqctl_val = AQCTL_CBU_FRCLOW; + aqctl_mask = AQCTL_CBU_MASK; + } else { + cmp_reg = CMPA; + aqctl_reg = AQCTLA; + /* Configure PWM Low from compare A value*/ + aqctl_val = AQCTL_CAU_FRCLOW; + aqctl_mask = AQCTL_CAU_MASK; + } + + /* Configure PWM High from period value and zero value */ + aqctl_val |= AQCTL_PRD_FRCHIGH | AQCTL_ZRO_FRCHIGH; + aqctl_mask |= AQCTL_PRD_MASK | AQCTL_ZRO_MASK; + ehrpwm_modify(pc->mmio_base, aqctl_reg, aqctl_mask, aqctl_val); + + ehrpwm_write(pc->mmio_base, cmp_reg, duty_cycles); +} + +/* + * period_ns = 10^9 * (ps_divval * period_cycles) / PWM_CLK_RATE + * duty_ns = 10^9 * (ps_divval * duty_cycles) / PWM_CLK_RATE + */ +static int ehrpwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip); + unsigned long long c; + unsigned long period_cycles, duty_cycles; + unsigned short ps_divval, tb_divval; + + if (period_ns < 0 || duty_ns < 0 || period_ns > NSEC_PER_SEC) + return -ERANGE; + + c = pc->clk_rate; + c = c * period_ns; + do_div(c, NSEC_PER_SEC); + period_cycles = (unsigned long)c; + + if (period_cycles < 1) { + period_cycles = 1; + duty_cycles = 1; + } else { + c = pc->clk_rate; + c = c * duty_ns; + do_div(c, NSEC_PER_SEC); + duty_cycles = (unsigned long)c; + } + + /* Configure clock prescaler to support Low frequency PWM wave */ + if (set_prescale_div(period_cycles/PERIOD_MAX, &ps_divval, + &tb_divval)) { + dev_err(chip->dev, "Unsupported values\n"); + return -EINVAL; + } + + pm_runtime_get_sync(chip->dev); + + /* Update clock prescaler values */ + ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_CLKDIV_MASK, tb_divval); + + /* Update period & duty cycle with presacler division */ + period_cycles = period_cycles / ps_divval; + duty_cycles = duty_cycles / ps_divval; + + /* Configure shadow loading on Period register */ + ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_PRDLD_MASK, TBCTL_PRDLD_SHDW); + + ehrpwm_write(pc->mmio_base, TBPRD, period_cycles); + + /* Configure ehrpwm counter for up-count mode */ + ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_CTRMODE_MASK, + TBCTL_CTRMODE_UP); + + /* Configure the channel for duty cycle */ + configure_chans(pc, pwm->hwpwm, duty_cycles); + pm_runtime_put_sync(chip->dev); + return 0; +} + +static int ehrpwm_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip); + unsigned short aqcsfrc_val, aqcsfrc_mask; + + /* Leave clock enabled on enabling PWM */ + pm_runtime_get_sync(chip->dev); + + /* Disabling Action Qualifier on PWM output */ + if (pwm->hwpwm) { + aqcsfrc_val = AQCSFRC_CSFB_FRCDIS; + aqcsfrc_mask = AQCSFRC_CSFB_MASK; + } else { + aqcsfrc_val = AQCSFRC_CSFA_FRCDIS; + aqcsfrc_mask = AQCSFRC_CSFA_MASK; + } + + /* Changes to shadow mode */ + ehrpwm_modify(pc->mmio_base, AQSFRC, AQSFRC_RLDCSF_MASK, + AQSFRC_RLDCSF_ZRO); + + ehrpwm_modify(pc->mmio_base, AQCSFRC, aqcsfrc_mask, aqcsfrc_val); + + /* Enable time counter for free_run */ + ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_RUN_MASK, TBCTL_FREE_RUN); + return 0; +} + +static void ehrpwm_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip); + unsigned short aqcsfrc_val, aqcsfrc_mask; + + /* Action Qualifier puts PWM output low forcefully */ + if (pwm->hwpwm) { + aqcsfrc_val = AQCSFRC_CSFB_FRCLOW; + aqcsfrc_mask = AQCSFRC_CSFB_MASK; + } else { + aqcsfrc_val = AQCSFRC_CSFA_FRCLOW; + aqcsfrc_mask = AQCSFRC_CSFA_MASK; + } + + /* + * Changes to immediate action on Action Qualifier. This puts + * Action Qualifier control on PWM output from next TBCLK + */ + ehrpwm_modify(pc->mmio_base, AQSFRC, AQSFRC_RLDCSF_MASK, + AQSFRC_RLDCSF_IMDT); + + ehrpwm_modify(pc->mmio_base, AQCSFRC, aqcsfrc_mask, aqcsfrc_val); + + /* Stop Time base counter */ + ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_RUN_MASK, TBCTL_STOP_NEXT); + + /* Disable clock on PWM disable */ + pm_runtime_put_sync(chip->dev); +} + +static void ehrpwm_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + if (test_bit(PWMF_ENABLED, &pwm->flags)) { + dev_warn(chip->dev, "Removing PWM device without disabling\n"); + pm_runtime_put_sync(chip->dev); + } +} + +static const struct pwm_ops ehrpwm_pwm_ops = { + .free = ehrpwm_pwm_free, + .config = ehrpwm_pwm_config, + .enable = ehrpwm_pwm_enable, + .disable = ehrpwm_pwm_disable, + .owner = THIS_MODULE, +}; + +static int __devinit ehrpwm_pwm_probe(struct platform_device *pdev) +{ + int ret; + struct resource *r; + struct clk *clk; + struct ehrpwm_pwm_chip *pc; + + pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); + if (!pc) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + clk = devm_clk_get(&pdev->dev, "fck"); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "failed to get clock\n"); + return PTR_ERR(clk); + } + + pc->clk_rate = clk_get_rate(clk); + if (!pc->clk_rate) { + dev_err(&pdev->dev, "failed to get clock rate\n"); + return -EINVAL; + } + + pc->chip.dev = &pdev->dev; + pc->chip.ops = &ehrpwm_pwm_ops; + pc->chip.base = -1; + pc->chip.npwm = NUM_PWM_CHANNEL; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + dev_err(&pdev->dev, "no memory resource defined\n"); + return -ENODEV; + } + + pc->mmio_base = devm_request_and_ioremap(&pdev->dev, r); + if (!pc->mmio_base) { + dev_err(&pdev->dev, "failed to ioremap() registers\n"); + return -EADDRNOTAVAIL; + } + + ret = pwmchip_add(&pc->chip); + if (ret < 0) { + dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); + return ret; + } + + pm_runtime_enable(&pdev->dev); + platform_set_drvdata(pdev, pc); + return 0; +} + +static int __devexit ehrpwm_pwm_remove(struct platform_device *pdev) +{ + struct ehrpwm_pwm_chip *pc = platform_get_drvdata(pdev); + + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + return pwmchip_remove(&pc->chip); +} + +static struct platform_driver ehrpwm_pwm_driver = { + .driver = { + .name = "ehrpwm", + }, + .probe = ehrpwm_pwm_probe, + .remove = __devexit_p(ehrpwm_pwm_remove), +}; + +module_platform_driver(ehrpwm_pwm_driver); + +MODULE_DESCRIPTION("EHRPWM PWM driver"); +MODULE_AUTHOR("Texas Instruments"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-vt8500.c b/drivers/pwm/pwm-vt8500.c new file mode 100644 index 000000000000..548021439f0c --- /dev/null +++ b/drivers/pwm/pwm-vt8500.c @@ -0,0 +1,177 @@ +/* + * drivers/pwm/pwm-vt8500.c + * + * Copyright (C) 2010 Alexey Charkov <alchark@gmail.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/pwm.h> +#include <linux/delay.h> + +#include <asm/div64.h> + +#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 = devm_kzalloc(&pdev->dev, 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"); + return -ENODEV; + } + + chip->base = devm_request_and_ioremap(&pdev->dev, r); + if (chip->base == NULL) + return -EADDRNOTAVAIL; + + ret = pwmchip_add(&chip->chip); + if (ret < 0) + return ret; + + platform_set_drvdata(pdev, chip); + return ret; +} + +static int __devexit pwm_remove(struct platform_device *pdev) +{ + struct vt8500_chip *chip; + + chip = platform_get_drvdata(pdev); + if (chip == NULL) + return -ENODEV; + + return pwmchip_remove(&chip->chip); +} + +static struct platform_driver pwm_driver = { + .driver = { + .name = "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/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index 2979292650d6..cf282763a8dc 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig @@ -245,7 +245,7 @@ config BACKLIGHT_CARILLO_RANCH config BACKLIGHT_PWM tristate "Generic PWM based Backlight Driver" - depends on HAVE_PWM + depends on PWM help If you have a LCD backlight adjustable by PWM, say Y to enable this driver. diff --git a/drivers/video/backlight/pwm_bl.c b/drivers/video/backlight/pwm_bl.c index 342b7d7cbb63..995f0164c9b0 100644 --- a/drivers/video/backlight/pwm_bl.c +++ b/drivers/video/backlight/pwm_bl.c @@ -26,11 +26,13 @@ struct pwm_bl_data { struct device *dev; unsigned int period; unsigned int lth_brightness; + unsigned int *levels; int (*notify)(struct device *, int brightness); void (*notify_after)(struct device *, int brightness); int (*check_fb)(struct device *, struct fb_info *); + void (*exit)(struct device *); }; static int pwm_backlight_update_status(struct backlight_device *bl) @@ -52,9 +54,18 @@ static int pwm_backlight_update_status(struct backlight_device *bl) pwm_config(pb->pwm, 0, pb->period); pwm_disable(pb->pwm); } else { - brightness = pb->lth_brightness + - (brightness * (pb->period - pb->lth_brightness) / max); - pwm_config(pb->pwm, brightness, pb->period); + int duty_cycle; + + if (pb->levels) { + duty_cycle = pb->levels[brightness]; + max = pb->levels[max]; + } else { + duty_cycle = brightness; + } + + duty_cycle = pb->lth_brightness + + (duty_cycle * (pb->period - pb->lth_brightness) / max); + pwm_config(pb->pwm, duty_cycle, pb->period); pwm_enable(pb->pwm); } @@ -83,17 +94,98 @@ static const struct backlight_ops pwm_backlight_ops = { .check_fb = pwm_backlight_check_fb, }; +#ifdef CONFIG_OF +static int pwm_backlight_parse_dt(struct device *dev, + struct platform_pwm_backlight_data *data) +{ + struct device_node *node = dev->of_node; + struct property *prop; + int length; + u32 value; + int ret; + + if (!node) + return -ENODEV; + + memset(data, 0, sizeof(*data)); + + /* determine the number of brightness levels */ + prop = of_find_property(node, "brightness-levels", &length); + if (!prop) + return -EINVAL; + + data->max_brightness = length / sizeof(u32); + + /* read brightness levels from DT property */ + if (data->max_brightness > 0) { + size_t size = sizeof(*data->levels) * data->max_brightness; + + data->levels = devm_kzalloc(dev, size, GFP_KERNEL); + if (!data->levels) + return -ENOMEM; + + ret = of_property_read_u32_array(node, "brightness-levels", + data->levels, + data->max_brightness); + if (ret < 0) + return ret; + + ret = of_property_read_u32(node, "default-brightness-level", + &value); + if (ret < 0) + return ret; + + if (value >= data->max_brightness) { + dev_warn(dev, "invalid default brightness level: %u, using %u\n", + value, data->max_brightness - 1); + value = data->max_brightness - 1; + } + + data->dft_brightness = value; + data->max_brightness--; + } + + /* + * TODO: Most users of this driver use a number of GPIOs to control + * backlight power. Support for specifying these needs to be + * added. + */ + + return 0; +} + +static struct of_device_id pwm_backlight_of_match[] = { + { .compatible = "pwm-backlight" }, + { } +}; + +MODULE_DEVICE_TABLE(of, pwm_backlight_of_match); +#else +static int pwm_backlight_parse_dt(struct device *dev, + struct platform_pwm_backlight_data *data) +{ + return -ENODEV; +} +#endif + static int pwm_backlight_probe(struct platform_device *pdev) { - struct backlight_properties props; struct platform_pwm_backlight_data *data = pdev->dev.platform_data; + struct platform_pwm_backlight_data defdata; + struct backlight_properties props; struct backlight_device *bl; struct pwm_bl_data *pb; + unsigned int max; int ret; if (!data) { - dev_err(&pdev->dev, "failed to find platform data\n"); - return -EINVAL; + ret = pwm_backlight_parse_dt(&pdev->dev, &defdata); + if (ret < 0) { + dev_err(&pdev->dev, "failed to find platform data\n"); + return ret; + } + + data = &defdata; } if (data->init) { @@ -109,21 +201,42 @@ static int pwm_backlight_probe(struct platform_device *pdev) goto err_alloc; } - pb->period = data->pwm_period_ns; + if (data->levels) { + max = data->levels[data->max_brightness]; + pb->levels = data->levels; + } else + max = data->max_brightness; + pb->notify = data->notify; pb->notify_after = data->notify_after; pb->check_fb = data->check_fb; - pb->lth_brightness = data->lth_brightness * - (data->pwm_period_ns / data->max_brightness); + pb->exit = data->exit; pb->dev = &pdev->dev; - pb->pwm = pwm_request(data->pwm_id, "backlight"); + pb->pwm = pwm_get(&pdev->dev, NULL); if (IS_ERR(pb->pwm)) { - dev_err(&pdev->dev, "unable to request PWM for backlight\n"); - ret = PTR_ERR(pb->pwm); - goto err_alloc; - } else - dev_dbg(&pdev->dev, "got pwm for backlight\n"); + dev_err(&pdev->dev, "unable to request PWM, trying legacy API\n"); + + pb->pwm = pwm_request(data->pwm_id, "pwm-backlight"); + if (IS_ERR(pb->pwm)) { + dev_err(&pdev->dev, "unable to request legacy PWM\n"); + ret = PTR_ERR(pb->pwm); + goto err_alloc; + } + } + + dev_dbg(&pdev->dev, "got pwm for backlight\n"); + + /* + * The DT case will set the pwm_period_ns field to 0 and store the + * period, parsed from the DT, in the PWM device. For the non-DT case, + * set the period from platform data. + */ + if (data->pwm_period_ns > 0) + pwm_set_period(pb->pwm, data->pwm_period_ns); + + pb->period = pwm_get_period(pb->pwm); + pb->lth_brightness = data->lth_brightness * (pb->period / max); memset(&props, 0, sizeof(struct backlight_properties)); props.type = BACKLIGHT_RAW; @@ -143,7 +256,7 @@ static int pwm_backlight_probe(struct platform_device *pdev) return 0; err_bl: - pwm_free(pb->pwm); + pwm_put(pb->pwm); err_alloc: if (data->exit) data->exit(&pdev->dev); @@ -152,16 +265,15 @@ err_alloc: static int pwm_backlight_remove(struct platform_device *pdev) { - struct platform_pwm_backlight_data *data = pdev->dev.platform_data; struct backlight_device *bl = platform_get_drvdata(pdev); struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev); backlight_device_unregister(bl); pwm_config(pb->pwm, 0, pb->period); pwm_disable(pb->pwm); - pwm_free(pb->pwm); - if (data->exit) - data->exit(&pdev->dev); + pwm_put(pb->pwm); + if (pb->exit) + pb->exit(&pdev->dev); return 0; } @@ -195,11 +307,12 @@ static SIMPLE_DEV_PM_OPS(pwm_backlight_pm_ops, pwm_backlight_suspend, static struct platform_driver pwm_backlight_driver = { .driver = { - .name = "pwm-backlight", - .owner = THIS_MODULE, + .name = "pwm-backlight", + .owner = THIS_MODULE, #ifdef CONFIG_PM - .pm = &pwm_backlight_pm_ops, + .pm = &pwm_backlight_pm_ops, #endif + .of_match_table = of_match_ptr(pwm_backlight_of_match), }, .probe = pwm_backlight_probe, .remove = pwm_backlight_remove, diff --git a/include/linux/of.h b/include/linux/of.h index 0e9cf9eec085..42c2a58328c1 100644 --- a/include/linux/of.h +++ b/include/linux/of.h @@ -386,6 +386,13 @@ static inline int of_property_read_u64(const struct device_node *np, return -ENOSYS; } +static inline int of_property_match_string(struct device_node *np, + const char *propname, + const char *string) +{ + return -ENOSYS; +} + static inline struct device_node *of_parse_phandle(struct device_node *np, const char *phandle_name, int index) @@ -393,6 +400,15 @@ static inline struct device_node *of_parse_phandle(struct device_node *np, return NULL; } +static inline int of_parse_phandle_with_args(struct device_node *np, + const char *list_name, + const char *cells_name, + int index, + struct of_phandle_args *out_args) +{ + return -ENOSYS; +} + static inline int of_alias_get_id(struct device_node *np, const char *stem) { return -ENOSYS; diff --git a/include/linux/pwm.h b/include/linux/pwm.h index 7c775751392c..21d076c5089e 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -1,7 +1,10 @@ #ifndef __LINUX_PWM_H #define __LINUX_PWM_H +#include <linux/of.h> + struct pwm_device; +struct seq_file; /* * pwm_request - request a PWM device @@ -28,4 +31,118 @@ int pwm_enable(struct pwm_device *pwm); */ void pwm_disable(struct pwm_device *pwm); +#ifdef CONFIG_PWM +struct pwm_chip; + +enum { + PWMF_REQUESTED = 1 << 0, + PWMF_ENABLED = 1 << 1, +}; + +struct pwm_device { + const char *label; + unsigned long flags; + unsigned int hwpwm; + unsigned int pwm; + struct pwm_chip *chip; + void *chip_data; + + unsigned int period; /* in nanoseconds */ +}; + +static inline void pwm_set_period(struct pwm_device *pwm, unsigned int period) +{ + if (pwm) + pwm->period = period; +} + +static inline unsigned int pwm_get_period(struct pwm_device *pwm) +{ + return pwm ? pwm->period : 0; +} + +/** + * struct pwm_ops - PWM controller operations + * @request: optional hook for requesting a PWM + * @free: optional hook for freeing a PWM + * @config: configure duty cycles and period length for this PWM + * @enable: enable PWM output toggling + * @disable: disable PWM output toggling + * @dbg_show: optional routine to show contents in debugfs + * @owner: helps prevent removal of modules exporting active PWMs + */ +struct pwm_ops { + int (*request)(struct pwm_chip *chip, + struct pwm_device *pwm); + void (*free)(struct pwm_chip *chip, + struct pwm_device *pwm); + int (*config)(struct pwm_chip *chip, + struct pwm_device *pwm, + int duty_ns, int period_ns); + int (*enable)(struct pwm_chip *chip, + struct pwm_device *pwm); + void (*disable)(struct pwm_chip *chip, + struct pwm_device *pwm); +#ifdef CONFIG_DEBUG_FS + void (*dbg_show)(struct pwm_chip *chip, + struct seq_file *s); +#endif + struct module *owner; +}; + +/** + * struct pwm_chip - abstract a PWM controller + * @dev: device providing the PWMs + * @list: list node for internal use + * @ops: callbacks for this PWM controller + * @base: number of first PWM controlled by this chip + * @npwm: number of PWMs controlled by this chip + * @pwms: array of PWM devices allocated by the framework + */ +struct pwm_chip { + struct device *dev; + struct list_head list; + const struct pwm_ops *ops; + int base; + unsigned int npwm; + + struct pwm_device *pwms; + + struct pwm_device * (*of_xlate)(struct pwm_chip *pc, + const struct of_phandle_args *args); + unsigned int of_pwm_n_cells; +}; + +int pwm_set_chip_data(struct pwm_device *pwm, void *data); +void *pwm_get_chip_data(struct pwm_device *pwm); + +int pwmchip_add(struct pwm_chip *chip); +int pwmchip_remove(struct pwm_chip *chip); +struct pwm_device *pwm_request_from_chip(struct pwm_chip *chip, + unsigned int index, + const char *label); + +struct pwm_device *pwm_get(struct device *dev, const char *consumer); +void pwm_put(struct pwm_device *pwm); + +struct pwm_lookup { + struct list_head list; + const char *provider; + unsigned int index; + const char *dev_id; + const char *con_id; +}; + +#define PWM_LOOKUP(_provider, _index, _dev_id, _con_id) \ + { \ + .provider = _provider, \ + .index = _index, \ + .dev_id = _dev_id, \ + .con_id = _con_id, \ + } + +void pwm_add_table(struct pwm_lookup *table, size_t num); + +#endif + #endif /* __LINUX_PWM_H */ diff --git a/include/linux/pwm_backlight.h b/include/linux/pwm_backlight.h index 63d2df43e61a..56f4a866539a 100644 --- a/include/linux/pwm_backlight.h +++ b/include/linux/pwm_backlight.h @@ -12,6 +12,7 @@ struct platform_pwm_backlight_data { unsigned int dft_brightness; unsigned int lth_brightness; unsigned int pwm_period_ns; + unsigned int *levels; int (*init)(struct device *dev); int (*notify)(struct device *dev, int brightness); void (*notify_after)(struct device *dev, int brightness); |