Commit 9ec97169 authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'for-3.6' of git://gitorious.org/linux-pwm/linux-pwm

Pull PWM subsystem from Thierry Reding:
 "The new PWM subsystem aims at collecting all implementations of the
  legacy PWM API and to eventually replace it completely.

  The subsystem has been in development for over half a year now and
  many drivers have already been converted.  It has been in linux-next
  for a couple of weeks and there have been no major issues so I think
  it is ready for inclusion in your tree."

Arnd Bergmann <arnd@arndb.de>:
 "Very much Ack on the new subsystem.  It uses the interface
  declarations as the previously separate pwm drivers, so nothing
  changes for now in the drivers using it, although it enables us to
  change those more easily in the future if we want to.

  This work is also one of the missing pieces that are required to
  eventually build ARM kernels for multiple platforms, which is
  currently prohibited (amongs other things) by the fact that you cannot
  have more than one driver exporting the pwm functions."
Tested-and-acked-by: default avatarAlexandre Courbot <acourbot@nvidia.com>
Acked-by: default avatarMark Brown <broonie@opensource.wolfsonmicro.com>
Acked-by: Philip, Avinash <avinashphilip@ti.com> # TI's AM33xx platforms
Acked-By: Alexandre Pereira da Silva <aletes.xgr@gmail.com> # LPC32XX
Acked-by: default avatarArnd Bergmann <arnd@arndb.de>
Acked-by: default avatarSachin Kamat <sachin.kamat@linaro.org>

Fix up trivial conflicts with other cleanups and DT updates.

* 'for-3.6' of git://gitorious.org/linux-pwm/linux-pwm: (36 commits)
  pwm: pwm-tiehrpwm: PWM driver support for EHRPWM
  pwm: pwm-tiecap: PWM driver support for ECAP APWM
  pwm: fix used-uninitialized warning in pwm_get()
  pwm: add lpc32xx PWM support
  pwm_backlight: pass correct brightness to callback
  pwm: Use pr_* functions in pwm-samsung.c file
  pwm: Convert pwm-samsung to use devm_* APIs
  pwm: Convert pwm-tegra to use devm_clk_get()
  pwm: pwm-mxs: Return proper error if pwmchip_remove() fails
  pwm: pwm-bfin: Return proper error if pwmchip_remove() fails
  pwm: pxa: Propagate pwmchip_remove() error
  pwm: Convert pwm-pxa to use devm_* APIs
  pwm: Convert pwm-vt8500 to use devm_* APIs
  pwm: Convert pwm-imx to use devm_* APIs
  pwm: Conflict with legacy PWM API
  pwm: pwm-mxs: add pinctrl support
  pwm: pwm-mxs: use devm_* managed functions
  pwm: pwm-mxs: use global reset function stmp_reset_block
  pwm: pwm-mxs: encode soc name in compatible string
  pwm: Take over maintainership of the PWM subsystem
  ...
parents a410963b 19891b20
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>;
};
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>;
};
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>;
};
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>;
};
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>;
};
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.
...@@ -5526,6 +5526,18 @@ S: Maintained ...@@ -5526,6 +5526,18 @@ S: Maintained
F: Documentation/video4linux/README.pvrusb2 F: Documentation/video4linux/README.pvrusb2
F: drivers/media/video/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 PXA2xx/PXA3xx SUPPORT
M: Eric Miao <eric.y.miao@gmail.com> M: Eric Miao <eric.y.miao@gmail.com>
M: Russell King <linux@arm.linux.org.uk> M: Russell King <linux@arm.linux.org.uk>
......
...@@ -1009,7 +1009,6 @@ config ARCH_VT8500 ...@@ -1009,7 +1009,6 @@ config ARCH_VT8500
select ARCH_HAS_CPUFREQ select ARCH_HAS_CPUFREQ
select GENERIC_CLOCKEVENTS select GENERIC_CLOCKEVENTS
select ARCH_REQUIRE_GPIOLIB select ARCH_REQUIRE_GPIOLIB
select HAVE_PWM
help help
Support for VIA/WonderMedia VT8500/WM85xx System-on-Chip. Support for VIA/WonderMedia VT8500/WM85xx System-on-Chip.
......
...@@ -123,6 +123,12 @@ serial@70006400 { ...@@ -123,6 +123,12 @@ serial@70006400 {
status = "disabled"; status = "disabled";
}; };
pwm {
compatible = "nvidia,tegra20-pwm";
reg = <0x7000a000 0x100>;
#pwm-cells = <2>;
};
i2c@7000c000 { i2c@7000c000 {
compatible = "nvidia,tegra20-i2c"; compatible = "nvidia,tegra20-i2c";
reg = <0x7000c000 0x100>; reg = <0x7000c000 0x100>;
......
...@@ -117,6 +117,12 @@ serial@70006400 { ...@@ -117,6 +117,12 @@ serial@70006400 {
status = "disabled"; status = "disabled";
}; };
pwm {
compatible = "nvidia,tegra30-pwm", "nvidia,tegra20-pwm";
reg = <0x7000a000 0x100>;
#pwm-cells = <2>;
};
i2c@7000c000 { i2c@7000c000 {
compatible = "nvidia,tegra30-i2c", "nvidia,tegra20-i2c"; compatible = "nvidia,tegra30-i2c", "nvidia,tegra20-i2c";
reg = <0x7000c000 0x100>; reg = <0x7000c000 0x100>;
......
...@@ -64,7 +64,8 @@ struct of_dev_auxdata tegra20_auxdata_lookup[] __initdata = { ...@@ -64,7 +64,8 @@ struct of_dev_auxdata tegra20_auxdata_lookup[] __initdata = {
&tegra_ehci2_pdata), &tegra_ehci2_pdata),
OF_DEV_AUXDATA("nvidia,tegra20-ehci", TEGRA_USB3_BASE, "tegra-ehci.2", OF_DEV_AUXDATA("nvidia,tegra20-ehci", TEGRA_USB3_BASE, "tegra-ehci.2",
&tegra_ehci3_pdata), &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),
{} {}
}; };
......
...@@ -33,6 +33,8 @@ ...@@ -33,6 +33,8 @@
#include <asm/mach/arch.h> #include <asm/mach/arch.h>
#include <asm/hardware/gic.h> #include <asm/hardware/gic.h>
#include <mach/iomap.h>
#include "board.h" #include "board.h"
#include "clock.h" #include "clock.h"
...@@ -48,6 +50,7 @@ struct of_dev_auxdata tegra30_auxdata_lookup[] __initdata = { ...@@ -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,tegra20-i2c", 0x7000D000, "tegra-i2c.4", NULL),
OF_DEV_AUXDATA("nvidia,tegra30-ahub", 0x70080000, "tegra30-ahub", 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-apbdma", 0x6000a000, "tegra-apbdma", NULL),
OF_DEV_AUXDATA("nvidia,tegra30-pwm", TEGRA_PWFM_BASE, "tegra-pwm", NULL),
{} {}
}; };
......
...@@ -5,5 +5,3 @@ obj-$(CONFIG_VTWM_VERSION_WM8505) += devices-wm8505.o ...@@ -5,5 +5,3 @@ obj-$(CONFIG_VTWM_VERSION_WM8505) += devices-wm8505.o
obj-$(CONFIG_MACH_BV07) += bv07.o obj-$(CONFIG_MACH_BV07) += bv07.o
obj-$(CONFIG_MACH_WM8505_7IN_NETBOOK) += wm8505_7in.o obj-$(CONFIG_MACH_WM8505_7IN_NETBOOK) += wm8505_7in.o
obj-$(CONFIG_HAVE_PWM) += pwm.o
...@@ -47,12 +47,6 @@ config MXC_TZIC ...@@ -47,12 +47,6 @@ config MXC_TZIC
config MXC_AVIC config MXC_AVIC
bool 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 config MXC_DEBUG_BOARD
bool "Enable MXC debug board(for 3-stack)" bool "Enable MXC debug board(for 3-stack)"
help help
......
...@@ -11,7 +11,6 @@ obj-$(CONFIG_MXC_AVIC) += avic.o ...@@ -11,7 +11,6 @@ obj-$(CONFIG_MXC_AVIC) += avic.o
obj-$(CONFIG_IMX_HAVE_IOMUX_V1) += iomux-v1.o obj-$(CONFIG_IMX_HAVE_IOMUX_V1) += iomux-v1.o
obj-$(CONFIG_ARCH_MXC_IOMUX_V3) += iomux-v3.o obj-$(CONFIG_ARCH_MXC_IOMUX_V3) += iomux-v3.o
obj-$(CONFIG_IRAM_ALLOC) += iram_alloc.o obj-$(CONFIG_IRAM_ALLOC) += iram_alloc.o
obj-$(CONFIG_MXC_PWM) += pwm.o
obj-$(CONFIG_MXC_ULPI) += ulpi.o obj-$(CONFIG_MXC_ULPI) += ulpi.o
obj-$(CONFIG_MXC_USE_EPIT) += epit.o obj-$(CONFIG_MXC_USE_EPIT) += epit.o
obj-$(CONFIG_MXC_DEBUG_BOARD) += 3ds_debugboard.o obj-$(CONFIG_MXC_DEBUG_BOARD) += 3ds_debugboard.o
......
...@@ -8,5 +8,4 @@ obj-$(CONFIG_PXA3xx) += mfp.o ...@@ -8,5 +8,4 @@ obj-$(CONFIG_PXA3xx) += mfp.o
obj-$(CONFIG_PXA95x) += mfp.o obj-$(CONFIG_PXA95x) += mfp.o
obj-$(CONFIG_ARCH_MMP) += mfp.o obj-$(CONFIG_ARCH_MMP) += mfp.o
obj-$(CONFIG_HAVE_PWM) += pwm.o
obj-$(CONFIG_PXA_SSP) += ssp.o obj-$(CONFIG_PXA_SSP) += ssp.o
...@@ -59,7 +59,3 @@ obj-$(CONFIG_SAMSUNG_WAKEMASK) += wakeup-mask.o ...@@ -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_PM) += s5p-pm.o s5p-irq-pm.o
obj-$(CONFIG_S5P_SLEEP) += s5p-sleep.o obj-$(CONFIG_S5P_SLEEP) += s5p-sleep.o
# PWM support
obj-$(CONFIG_HAVE_PWM) += pwm.o
...@@ -1002,16 +1002,6 @@ config BFIN_GPTIMERS ...@@ -1002,16 +1002,6 @@ config BFIN_GPTIMERS
To compile this driver as a module, choose M here: the module To compile this driver as a module, choose M here: the module
will be called gptimers. 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 choice
prompt "Uncached DMA region" prompt "Uncached DMA region"
default DMA_UNCACHED_1M default DMA_UNCACHED_1M
......
...@@ -21,7 +21,6 @@ obj-$(CONFIG_FUNCTION_TRACER) += ftrace-entry.o ...@@ -21,7 +21,6 @@ obj-$(CONFIG_FUNCTION_TRACER) += ftrace-entry.o
obj-$(CONFIG_FUNCTION_GRAPH_TRACER) += ftrace.o obj-$(CONFIG_FUNCTION_GRAPH_TRACER) += ftrace.o
CFLAGS_REMOVE_ftrace.o = -pg CFLAGS_REMOVE_ftrace.o = -pg
obj-$(CONFIG_HAVE_PWM) += pwm.o
obj-$(CONFIG_IPIPE) += ipipe.o obj-$(CONFIG_IPIPE) += ipipe.o
obj-$(CONFIG_BFIN_GPTIMERS) += gptimers.o obj-$(CONFIG_BFIN_GPTIMERS) += gptimers.o
obj-$(CONFIG_CPLB_INFO) += cplbinfo.o obj-$(CONFIG_CPLB_INFO) += cplbinfo.o
......
/*
* 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);
...@@ -148,4 +148,6 @@ source "drivers/iio/Kconfig" ...@@ -148,4 +148,6 @@ source "drivers/iio/Kconfig"
source "drivers/vme/Kconfig" source "drivers/vme/Kconfig"
source "drivers/pwm/Kconfig"
endmenu endmenu
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
# GPIO must come after pinctrl as gpios may need to mux pins etc # GPIO must come after pinctrl as gpios may need to mux pins etc
obj-y += pinctrl/ obj-y += pinctrl/
obj-y += gpio/ obj-y += gpio/
obj-y += pwm/
obj-$(CONFIG_PCI) += pci/ obj-$(CONFIG_PCI) += pci/
obj-$(CONFIG_PARISC) += parisc/ obj-$(CONFIG_PARISC) += parisc/
obj-$(CONFIG_RAPIDIO) += rapidio/ obj-$(CONFIG_RAPIDIO) += rapidio/
......
...@@ -276,6 +276,7 @@ config TWL6030_PWM ...@@ -276,6 +276,7 @@ config TWL6030_PWM
tristate "TWL6030 PWM (Pulse Width Modulator) Support" tristate "TWL6030 PWM (Pulse Width Modulator) Support"
depends on TWL4030_CORE depends on TWL4030_CORE
select HAVE_PWM select HAVE_PWM
depends on !PWM
default n default n
help help
Say yes here if you want support for TWL6030 PWM. Say yes here if you want support for TWL6030 PWM.
......
...@@ -64,6 +64,7 @@ config AB8500_PWM ...@@ -64,6 +64,7 @@ config AB8500_PWM
bool "AB8500 PWM support" bool "AB8500 PWM support"
depends on AB8500_CORE && ARCH_U8500 depends on AB8500_CORE && ARCH_U8500
select HAVE_PWM select HAVE_PWM
depends on !PWM
help help
This driver exports functions to enable/disble/config/free Pulse This driver exports functions to enable/disble/config/free Pulse
Width Modulation in the Analog Baseband Chip AB8500. Width Modulation in the Analog Baseband Chip AB8500.
......
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
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
This diff is collapsed.
/*
* 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");
...@@ -39,33 +39,28 @@ ...@@ -39,33 +39,28 @@
#define MX3_PWMCR_CLKSRC_IPG (1 << 16) #define MX3_PWMCR_CLKSRC_IPG (1 << 16)
#define MX3_PWMCR_EN (1 << 0) #define MX3_PWMCR_EN (1 << 0)
struct imx_chip {
struct pwm_device {
struct list_head node;
struct platform_device *pdev;
const char *label;
struct clk *clk; struct clk *clk;
int clk_enabled; int clk_enabled;
void __iomem *mmio_base; void __iomem *mmio_base;
unsigned int use_count; struct pwm_chip chip;
unsigned int pwm_id;
}; };
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) struct imx_chip *imx = to_imx_chip(chip);
return -EINVAL;
if (!(cpu_is_mx1() || cpu_is_mx21())) { if (!(cpu_is_mx1() || cpu_is_mx21())) {
unsigned long long c; unsigned long long c;
unsigned long period_cycles, duty_cycles, prescale; unsigned long period_cycles, duty_cycles, prescale;
u32 cr; u32 cr;
c = clk_get_rate(pwm->clk); c = clk_get_rate(imx->clk);
c = c * period_ns; c = c * period_ns;
do_div(c, 1000000000); do_div(c, 1000000000);
period_cycles = c; period_cycles = c;
...@@ -86,8 +81,8 @@ int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) ...@@ -86,8 +81,8 @@ int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
else else
period_cycles = 0; period_cycles = 0;
writel(duty_cycles, pwm->mmio_base + MX3_PWMSAR); writel(duty_cycles, imx->mmio_base + MX3_PWMSAR);
writel(period_cycles, pwm->mmio_base + MX3_PWMPR); writel(period_cycles, imx->mmio_base + MX3_PWMPR);
cr = MX3_PWMCR_PRESCALER(prescale) | cr = MX3_PWMCR_PRESCALER(prescale) |
MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN | MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN |
...@@ -98,7 +93,7 @@ int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) ...@@ -98,7 +93,7 @@ int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
else else
cr |= MX3_PWMCR_CLKSRC_IPG_HIGH; 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()) { } else if (cpu_is_mx1() || cpu_is_mx21()) {
/* The PWM subsystem allows for exact frequencies. However, /* The PWM subsystem allows for exact frequencies. However,
* I cannot connect a scope on my device to the PWM line and * 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) ...@@ -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 * both the prescaler (/1 .. /128) and then by CLKSEL
* (/2 .. /16). * (/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; u32 p = max * duty_ns / period_ns;
writel(max - p, pwm->mmio_base + MX1_PWMS); writel(max - p, imx->mmio_base + MX1_PWMS);
} else { } else {
BUG(); BUG();
} }
return 0; 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; int rc = 0;
if (!pwm->clk_enabled) { if (!imx->clk_enabled) {
rc = clk_prepare_enable(pwm->clk); rc = clk_prepare_enable(imx->clk);
if (!rc) if (!rc)
pwm->clk_enabled = 1; imx->clk_enabled = 1;
} }
return rc; 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) { writel(0, imx->mmio_base + MX3_PWMCR);
clk_disable_unprepare(pwm->clk);
pwm->clk_enabled = 0;
}
}
EXPORT_SYMBOL(pwm_disable);
static DEFINE_MUTEX(pwm_lock); if (imx->clk_enabled) {
static LIST_HEAD(pwm_list); clk_disable_unprepare(imx->clk);
imx->clk_enabled = 0;
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) static struct pwm_ops imx_pwm_ops = {
{ .enable = imx_pwm_enable,
mutex_lock(&pwm_lock); .disable = imx_pwm_disable,
.config = imx_pwm_config,
if (pwm->use_count) { .owner = THIS_MODULE,
pwm->use_count--; };
pwm->label = NULL;
} else
pr_warning("PWM device already freed\n");
mutex_unlock(&pwm_lock);
}
EXPORT_SYMBOL(pwm_free);
static int __devinit mxc_pwm_probe(struct platform_device *pdev) static int __devinit imx_pwm_probe(struct platform_device *pdev)
{ {
struct pwm_device *pwm; struct imx_chip *imx;
struct resource *r; struct resource *r;
int ret = 0; int ret = 0;
pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL); imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL);
if (pwm == NULL) { if (imx == NULL) {
dev_err(&pdev->dev, "failed to allocate memory\n"); dev_err(&pdev->dev, "failed to allocate memory\n");
return -ENOMEM; return -ENOMEM;
} }
pwm->clk = clk_get(&pdev->dev, "pwm"); imx->clk = devm_clk_get(&pdev->dev, "pwm");
if (IS_ERR(pwm->clk)) { if (IS_ERR(imx->clk))
ret = PTR_ERR(pwm->clk); return PTR_ERR(imx->clk);
goto err_free;
}
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; imx->clk_enabled = 0;
pwm->pwm_id = pdev->id;
pwm->pdev = pdev;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0); r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (r == NULL) { if (r == NULL) {
dev_err(&pdev->dev, "no memory resource defined\n"); dev_err(&pdev->dev, "no memory resource defined\n");
ret = -ENODEV; return -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)); imx->mmio_base = devm_request_and_ioremap(&pdev->dev, r);
if (pwm->mmio_base == NULL) { if (imx->mmio_base == NULL)
dev_err(&pdev->dev, "failed to ioremap() registers\n"); return -EADDRNOTAVAIL;
ret = -ENODEV;
goto err_free_mem;
}
mutex_lock(&pwm_lock); ret = pwmchip_add(&imx->chip);
list_add_tail(&pwm->node, &pwm_list); if (ret < 0)
mutex_unlock(&pwm_lock); return ret;
platform_set_drvdata(pdev, pwm); platform_set_drvdata(pdev, imx);
return 0; 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 imx_chip *imx;
struct resource *r;
pwm = platform_get_drvdata(pdev); imx = platform_get_drvdata(pdev);
if (pwm == NULL) if (imx == NULL)
return -ENODEV; return -ENODEV;
mutex_lock(&pwm_lock); return pwmchip_remove(&imx->chip);
list_del(&pwm->node);
mutex_unlock(&pwm_lock);
iounmap(pwm->mmio_base);
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
release_mem_region(r->start, resource_size(r));
clk_put(pwm->clk);
kfree(pwm);
return 0;
} }
static struct platform_driver mxc_pwm_driver = { static struct platform_driver imx_pwm_driver = {
.driver = { .driver = {
.name = "mxc_pwm", .name = "mxc_pwm",
}, },
.probe = mxc_pwm_probe, .probe = imx_pwm_probe,
.remove = __devexit_p(mxc_pwm_remove), .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_LICENSE("GPL v2");
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
/*
* 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");
/*
* 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");
/* /*
* linux/arch/arm/mach-pxa/pwm.c * drivers/pwm/pwm-pxa.c
* *
* simple driver for PWM (Pulse Width Modulator) controller * simple driver for PWM (Pulse Width Modulator) controller
* *
...@@ -43,33 +43,39 @@ MODULE_DEVICE_TABLE(platform, pwm_id_table); ...@@ -43,33 +43,39 @@ MODULE_DEVICE_TABLE(platform, pwm_id_table);
#define PWMCR_SD (1 << 6) #define PWMCR_SD (1 << 6)
#define PWMDCR_FD (1 << 10) #define PWMDCR_FD (1 << 10)
struct pwm_device { struct pxa_pwm_chip {
struct list_head node; struct pwm_chip chip;
struct pwm_device *secondary; struct device *dev;
struct platform_device *pdev;
const char *label;
struct clk *clk; struct clk *clk;
int clk_enabled; int clk_enabled;
void __iomem *mmio_base; void __iomem *mmio_base;
unsigned int use_count;
unsigned int pwm_id;
}; };
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 * period_ns = 10^9 * (PRESCALE + 1) * (PV + 1) / PWM_CLK_RATE
* duty_ns = 10^9 * (PRESCALE + 1) * DC / 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) 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 long c;
unsigned long period_cycles, prescale, pv, dc; unsigned long period_cycles, prescale, pv, dc;
unsigned long offset;
int rc;
if (pwm == NULL || period_ns == 0 || duty_ns > period_ns) if (period_ns == 0 || duty_ns > period_ns)
return -EINVAL; return -EINVAL;
c = clk_get_rate(pwm->clk); offset = pwm->hwpwm ? 0x10 : 0;
c = clk_get_rate(pc->clk);
c = c * period_ns; c = c * period_ns;
do_div(c, 1000000000); do_div(c, 1000000000);
period_cycles = c; period_cycles = c;
...@@ -90,193 +96,101 @@ int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) ...@@ -90,193 +96,101 @@ int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
/* NOTE: the clock to PWM has to be enabled first /* NOTE: the clock to PWM has to be enabled first
* before writing to the registers * before writing to the registers
*/ */
clk_enable(pwm->clk); rc = clk_prepare_enable(pc->clk);
__raw_writel(prescale, pwm->mmio_base + PWMCR); if (rc < 0)
__raw_writel(dc, pwm->mmio_base + PWMDCR); return rc;
__raw_writel(pv, pwm->mmio_base + PWMPCR);
clk_disable(pwm->clk); 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; return 0;
} }
EXPORT_SYMBOL(pwm_config);
int pwm_enable(struct pwm_device *pwm) 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; int rc = 0;
if (!pwm->clk_enabled) { if (!pc->clk_enabled) {
rc = clk_enable(pwm->clk); rc = clk_prepare_enable(pc->clk);
if (!rc) if (!rc)
pwm->clk_enabled = 1; pc->clk_enabled++;
} }
return rc; 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) static void pxa_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{ {
struct pwm_device *pwm; struct pxa_pwm_chip *pc = to_pxa_pwm_chip(chip);
int found = 0;
mutex_lock(&pwm_lock); if (pc->clk_enabled) {
clk_disable_unprepare(pc->clk);
list_for_each_entry(pwm, &pwm_list, node) { pc->clk_enabled--;
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) static struct pwm_ops pxa_pwm_ops = {
{ .config = pxa_pwm_config,
mutex_lock(&pwm_lock); .enable = pxa_pwm_enable,
list_add_tail(&pwm->node, &pwm_list); .disable = pxa_pwm_disable,
mutex_unlock(&pwm_lock); .owner = THIS_MODULE,
} };
static int __devinit pwm_probe(struct platform_device *pdev) static int __devinit pwm_probe(struct platform_device *pdev)
{ {
const struct platform_device_id *id = platform_get_device_id(pdev); const struct platform_device_id *id = platform_get_device_id(pdev);
struct pwm_device *pwm, *secondary = NULL; struct pxa_pwm_chip *pwm;
struct resource *r; struct resource *r;
int ret = 0; int ret = 0;
pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL); pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL);
if (pwm == NULL) { if (pwm == NULL) {
dev_err(&pdev->dev, "failed to allocate memory\n"); dev_err(&pdev->dev, "failed to allocate memory\n");
return -ENOMEM; return -ENOMEM;
} }
pwm->clk = clk_get(&pdev->dev, NULL); pwm->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(pwm->clk)) { if (IS_ERR(pwm->clk))
ret = PTR_ERR(pwm->clk); return PTR_ERR(pwm->clk);
goto err_free;
}
pwm->clk_enabled = 0; pwm->clk_enabled = 0;
pwm->use_count = 0; pwm->chip.dev = &pdev->dev;
pwm->pwm_id = PWM_ID_BASE(id->driver_data) + pdev->id; pwm->chip.ops = &pxa_pwm_ops;
pwm->pdev = pdev; pwm->chip.base = -1;
pwm->chip.npwm = (id->driver_data & HAS_SECONDARY_PWM) ? 2 : 1;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0); r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (r == NULL) { if (r == NULL) {
dev_err(&pdev->dev, "no memory resource defined\n"); dev_err(&pdev->dev, "no memory resource defined\n");
ret = -ENODEV; return -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) { pwm->mmio_base = devm_request_and_ioremap(&pdev->dev, r);
secondary = kzalloc(sizeof(struct pwm_device), GFP_KERNEL); if (pwm->mmio_base == NULL)
if (secondary == NULL) { return -EADDRNOTAVAIL;
ret = -ENOMEM;
goto err_free_mem;
}
*secondary = *pwm; ret = pwmchip_add(&pwm->chip);
pwm->secondary = secondary; if (ret < 0) {
dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
/* registers for the second PWM has offset of 0x10 */ return ret;
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); platform_set_drvdata(pdev, pwm);
return 0; 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) static int __devexit pwm_remove(struct platform_device *pdev)
{ {
struct pwm_device *pwm; struct pxa_pwm_chip *chip;
struct resource *r;
pwm = platform_get_drvdata(pdev); chip = platform_get_drvdata(pdev);
if (pwm == NULL) if (chip == NULL)
return -ENODEV; return -ENODEV;
mutex_lock(&pwm_lock); return pwmchip_remove(&chip->chip);
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 = { static struct platform_driver pwm_driver = {
......
/*
* 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");
/*
* 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");
This diff is collapsed.
/* /*
* arch/arm/mach-vt8500/pwm.c * drivers/pwm/pwm-vt8500.c
* *
* Copyright (C) 2010 Alexey Charkov <alchark@gmail.com> * Copyright (C) 2010 Alexey Charkov <alchark@gmail.com>
* *
...@@ -26,21 +26,13 @@ ...@@ -26,21 +26,13 @@
#define VT8500_NR_PWMS 4 #define VT8500_NR_PWMS 4
static DEFINE_MUTEX(pwm_lock); struct vt8500_chip {
static LIST_HEAD(pwm_list); struct pwm_chip chip;
void __iomem *base;
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 to_vt8500_chip(chip) container_of(chip, struct vt8500_chip, chip)
#define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t) #define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t)
static inline void pwm_busy_wait(void __iomem *reg, u8 bitmask) static inline void pwm_busy_wait(void __iomem *reg, u8 bitmask)
{ {
...@@ -53,14 +45,13 @@ static inline void pwm_busy_wait(void __iomem *reg, u8 bitmask) ...@@ -53,14 +45,13 @@ static inline void pwm_busy_wait(void __iomem *reg, u8 bitmask)
bitmask); bitmask);
} }
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) 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 long c;
unsigned long period_cycles, prescale, pv, dc; 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 = 25000000/2; /* wild guess --- need to implement clocks */
c = c * period_ns; c = c * period_ns;
do_div(c, 1000000000); do_div(c, 1000000000);
...@@ -80,165 +71,86 @@ int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) ...@@ -80,165 +71,86 @@ int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
do_div(c, period_ns); do_div(c, period_ns);
dc = c; dc = c;
pwm_busy_wait(pwm->regbase + 0x40 + pwm->pwm_id, (1 << 1)); pwm_busy_wait(vt8500->base + 0x40 + pwm->hwpwm, (1 << 1));
writel(prescale, pwm->regbase + 0x4 + (pwm->pwm_id << 4)); writel(prescale, vt8500->base + 0x4 + (pwm->hwpwm << 4));
pwm_busy_wait(pwm->regbase + 0x40 + pwm->pwm_id, (1 << 2)); pwm_busy_wait(vt8500->base + 0x40 + pwm->hwpwm, (1 << 2));
writel(pv, pwm->regbase + 0x8 + (pwm->pwm_id << 4)); writel(pv, vt8500->base + 0x8 + (pwm->hwpwm << 4));
pwm_busy_wait(pwm->regbase + 0x40 + pwm->pwm_id, (1 << 3)); pwm_busy_wait(vt8500->base + 0x40 + pwm->hwpwm, (1 << 3));
writel(dc, pwm->regbase + 0xc + (pwm->pwm_id << 4)); writel(dc, vt8500->base + 0xc + (pwm->hwpwm << 4));
return 0; return 0;
} }
EXPORT_SYMBOL(pwm_config);
int pwm_enable(struct pwm_device *pwm) static int vt8500_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{ {
pwm_busy_wait(pwm->regbase + 0x40 + pwm->pwm_id, (1 << 0)); struct vt8500_chip *vt8500 = to_vt8500_chip(chip);
writel(5, pwm->regbase + (pwm->pwm_id << 4));
return 0;
}
EXPORT_SYMBOL(pwm_enable);
void pwm_disable(struct pwm_device *pwm) pwm_busy_wait(vt8500->base + 0x40 + pwm->hwpwm, (1 << 0));
{ writel(5, vt8500->base + (pwm->hwpwm << 4));
pwm_busy_wait(pwm->regbase + 0x40 + pwm->pwm_id, (1 << 0)); return 0;
writel(0, pwm->regbase + (pwm->pwm_id << 4));
} }
EXPORT_SYMBOL(pwm_disable);
struct pwm_device *pwm_request(int pwm_id, const char *label) static void vt8500_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{ {
struct pwm_device *pwm; struct vt8500_chip *vt8500 = to_vt8500_chip(chip);
int found = 0;
mutex_lock(&pwm_lock); pwm_busy_wait(vt8500->base + 0x40 + pwm->hwpwm, (1 << 0));
writel(0, vt8500->base + (pwm->hwpwm << 4));
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) static struct pwm_ops vt8500_pwm_ops = {
{ .enable = vt8500_pwm_enable,
mutex_lock(&pwm_lock); .disable = vt8500_pwm_disable,
.config = vt8500_pwm_config,
if (pwm->use_count) { .owner = THIS_MODULE,
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) static int __devinit pwm_probe(struct platform_device *pdev)
{ {
struct pwm_device *pwms; struct vt8500_chip *chip;
struct resource *r; struct resource *r;
int ret = 0; int ret;
int i;
pwms = kzalloc(sizeof(struct pwm_device) * VT8500_NR_PWMS, GFP_KERNEL); chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
if (pwms == NULL) { if (chip == NULL) {
dev_err(&pdev->dev, "failed to allocate memory\n"); dev_err(&pdev->dev, "failed to allocate memory\n");
return -ENOMEM; return -ENOMEM;
} }
for (i = 0; i < VT8500_NR_PWMS; i++) { chip->chip.dev = &pdev->dev;
pwms[i].use_count = 0; chip->chip.ops = &vt8500_pwm_ops;
pwms[i].pwm_id = i; chip->chip.base = -1;
pwms[i].pdev = pdev; chip->chip.npwm = VT8500_NR_PWMS;
}
r = platform_get_resource(pdev, IORESOURCE_MEM, 0); r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (r == NULL) { if (r == NULL) {
dev_err(&pdev->dev, "no memory resource defined\n"); dev_err(&pdev->dev, "no memory resource defined\n");
ret = -ENODEV; return -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++) chip->base = devm_request_and_ioremap(&pdev->dev, r);
pwms[i].regbase = pwms[0].regbase; if (chip->base == NULL)
return -EADDRNOTAVAIL;
for (i = 0; i < VT8500_NR_PWMS; i++)
__add_pwm(&pwms[i]);
platform_set_drvdata(pdev, pwms); ret = pwmchip_add(&chip->chip);
return 0; if (ret < 0)
return ret;
err_free_mem: platform_set_drvdata(pdev, chip);
release_mem_region(r->start, resource_size(r));
err_free:
kfree(pwms);
return ret; return ret;
} }
static int __devexit pwm_remove(struct platform_device *pdev) static int __devexit pwm_remove(struct platform_device *pdev)
{ {
struct pwm_device *pwms; struct vt8500_chip *chip;
struct resource *r;
int i;
pwms = platform_get_drvdata(pdev); chip = platform_get_drvdata(pdev);
if (pwms == NULL) if (chip == NULL)
return -ENODEV; return -ENODEV;
mutex_lock(&pwm_lock); return pwmchip_remove(&chip->chip);
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 = { static struct platform_driver pwm_driver = {
......
...@@ -245,7 +245,7 @@ config BACKLIGHT_CARILLO_RANCH ...@@ -245,7 +245,7 @@ config BACKLIGHT_CARILLO_RANCH
config BACKLIGHT_PWM config BACKLIGHT_PWM
tristate "Generic PWM based Backlight Driver" tristate "Generic PWM based Backlight Driver"
depends on HAVE_PWM depends on PWM
help help
If you have a LCD backlight adjustable by PWM, say Y to enable If you have a LCD backlight adjustable by PWM, say Y to enable
this driver. this driver.
......
...@@ -26,11 +26,13 @@ struct pwm_bl_data { ...@@ -26,11 +26,13 @@ struct pwm_bl_data {
struct device *dev; struct device *dev;
unsigned int period; unsigned int period;
unsigned int lth_brightness; unsigned int lth_brightness;
unsigned int *levels;
int (*notify)(struct device *, int (*notify)(struct device *,
int brightness); int brightness);
void (*notify_after)(struct device *, void (*notify_after)(struct device *,
int brightness); int brightness);
int (*check_fb)(struct device *, struct fb_info *); int (*check_fb)(struct device *, struct fb_info *);
void (*exit)(struct device *);
}; };
static int pwm_backlight_update_status(struct backlight_device *bl) static int pwm_backlight_update_status(struct backlight_device *bl)
...@@ -52,9 +54,18 @@ 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_config(pb->pwm, 0, pb->period);
pwm_disable(pb->pwm); pwm_disable(pb->pwm);
} else { } else {
brightness = pb->lth_brightness + int duty_cycle;
(brightness * (pb->period - pb->lth_brightness) / max);
pwm_config(pb->pwm, brightness, pb->period); 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); pwm_enable(pb->pwm);
} }
...@@ -83,17 +94,98 @@ static const struct backlight_ops pwm_backlight_ops = { ...@@ -83,17 +94,98 @@ static const struct backlight_ops pwm_backlight_ops = {
.check_fb = pwm_backlight_check_fb, .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) 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 *data = pdev->dev.platform_data;
struct platform_pwm_backlight_data defdata;
struct backlight_properties props;
struct backlight_device *bl; struct backlight_device *bl;
struct pwm_bl_data *pb; struct pwm_bl_data *pb;
unsigned int max;
int ret; int ret;
if (!data) { if (!data) {
dev_err(&pdev->dev, "failed to find platform data\n"); ret = pwm_backlight_parse_dt(&pdev->dev, &defdata);
return -EINVAL; if (ret < 0) {
dev_err(&pdev->dev, "failed to find platform data\n");
return ret;
}
data = &defdata;
} }
if (data->init) { if (data->init) {
...@@ -109,21 +201,42 @@ static int pwm_backlight_probe(struct platform_device *pdev) ...@@ -109,21 +201,42 @@ static int pwm_backlight_probe(struct platform_device *pdev)
goto err_alloc; 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 = data->notify;
pb->notify_after = data->notify_after; pb->notify_after = data->notify_after;
pb->check_fb = data->check_fb; pb->check_fb = data->check_fb;
pb->lth_brightness = data->lth_brightness * pb->exit = data->exit;
(data->pwm_period_ns / data->max_brightness);
pb->dev = &pdev->dev; pb->dev = &pdev->dev;
pb->pwm = pwm_request(data->pwm_id, "backlight"); pb->pwm = pwm_get(&pdev->dev, NULL);
if (IS_ERR(pb->pwm)) { if (IS_ERR(pb->pwm)) {
dev_err(&pdev->dev, "unable to request PWM for backlight\n"); dev_err(&pdev->dev, "unable to request PWM, trying legacy API\n");
ret = PTR_ERR(pb->pwm);
goto err_alloc; pb->pwm = pwm_request(data->pwm_id, "pwm-backlight");
} else if (IS_ERR(pb->pwm)) {
dev_dbg(&pdev->dev, "got pwm for backlight\n"); 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)); memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW; props.type = BACKLIGHT_RAW;
...@@ -143,7 +256,7 @@ static int pwm_backlight_probe(struct platform_device *pdev) ...@@ -143,7 +256,7 @@ static int pwm_backlight_probe(struct platform_device *pdev)
return 0; return 0;
err_bl: err_bl:
pwm_free(pb->pwm); pwm_put(pb->pwm);
err_alloc: err_alloc:
if (data->exit) if (data->exit)
data->exit(&pdev->dev); data->exit(&pdev->dev);
...@@ -152,16 +265,15 @@ static int pwm_backlight_probe(struct platform_device *pdev) ...@@ -152,16 +265,15 @@ static int pwm_backlight_probe(struct platform_device *pdev)
static int pwm_backlight_remove(struct platform_device *pdev) 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 backlight_device *bl = platform_get_drvdata(pdev);
struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev); struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev);
backlight_device_unregister(bl); backlight_device_unregister(bl);
pwm_config(pb->pwm, 0, pb->period); pwm_config(pb->pwm, 0, pb->period);
pwm_disable(pb->pwm); pwm_disable(pb->pwm);
pwm_free(pb->pwm); pwm_put(pb->pwm);
if (data->exit) if (pb->exit)
data->exit(&pdev->dev); pb->exit(&pdev->dev);
return 0; return 0;
} }
...@@ -195,11 +307,12 @@ static SIMPLE_DEV_PM_OPS(pwm_backlight_pm_ops, pwm_backlight_suspend, ...@@ -195,11 +307,12 @@ static SIMPLE_DEV_PM_OPS(pwm_backlight_pm_ops, pwm_backlight_suspend,
static struct platform_driver pwm_backlight_driver = { static struct platform_driver pwm_backlight_driver = {
.driver = { .driver = {
.name = "pwm-backlight", .name = "pwm-backlight",
.owner = THIS_MODULE, .owner = THIS_MODULE,
#ifdef CONFIG_PM #ifdef CONFIG_PM
.pm = &pwm_backlight_pm_ops, .pm = &pwm_backlight_pm_ops,
#endif #endif
.of_match_table = of_match_ptr(pwm_backlight_of_match),
}, },
.probe = pwm_backlight_probe, .probe = pwm_backlight_probe,
.remove = pwm_backlight_remove, .remove = pwm_backlight_remove,
......
...@@ -386,6 +386,13 @@ static inline int of_property_read_u64(const struct device_node *np, ...@@ -386,6 +386,13 @@ static inline int of_property_read_u64(const struct device_node *np,
return -ENOSYS; 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, static inline struct device_node *of_parse_phandle(struct device_node *np,
const char *phandle_name, const char *phandle_name,
int index) int index)
...@@ -393,6 +400,15 @@ static inline struct device_node *of_parse_phandle(struct device_node *np, ...@@ -393,6 +400,15 @@ static inline struct device_node *of_parse_phandle(struct device_node *np,
return NULL; 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) static inline int of_alias_get_id(struct device_node *np, const char *stem)
{ {
return -ENOSYS; return -ENOSYS;
......
#ifndef __LINUX_PWM_H #ifndef __LINUX_PWM_H
#define __LINUX_PWM_H #define __LINUX_PWM_H
#include <linux/of.h>
struct pwm_device; struct pwm_device;
struct seq_file;
/* /*
* pwm_request - request a PWM device * pwm_request - request a PWM device
...@@ -28,4 +31,118 @@ int pwm_enable(struct pwm_device *pwm); ...@@ -28,4 +31,118 @@ int pwm_enable(struct pwm_device *pwm);
*/ */
void pwm_disable(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 */ #endif /* __LINUX_PWM_H */
...@@ -12,6 +12,7 @@ struct platform_pwm_backlight_data { ...@@ -12,6 +12,7 @@ struct platform_pwm_backlight_data {
unsigned int dft_brightness; unsigned int dft_brightness;
unsigned int lth_brightness; unsigned int lth_brightness;
unsigned int pwm_period_ns; unsigned int pwm_period_ns;
unsigned int *levels;
int (*init)(struct device *dev); int (*init)(struct device *dev);
int (*notify)(struct device *dev, int brightness); int (*notify)(struct device *dev, int brightness);
void (*notify_after)(struct device *dev, int brightness); void (*notify_after)(struct device *dev, int brightness);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment