summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Agner <stefan.agner@toradex.com>2014-07-23 15:27:24 +0200
committerStefan Agner <stefan.agner@toradex.com>2014-07-24 17:18:42 +0200
commit6a535bf81ef09e11dc8ddde5cebc11d6cf4296bd (patch)
treeb620d9f5c1d7ee359f49c7c68512c7f67818bc01
parenta4306c3182ca66e328688b5c90057708c51b51e5 (diff)
ARM: ftm-pwm: add FlexTimer PWM driver
This adds a new FlexTimer PWM driver in favor of the old PWM driver. The new driver allows to make use of the individual PWM channels independently. However, the PWM period need to be the same for all PWM channels on the same FlexTimer instance. If the period differs, the driver would overwrites the period of a previously configured PWM output. For Colibri VF, we register FTM0/1 only since those instances are used for PWM support. Also we use the same PWM period of 1ms for all outputs.
-rw-r--r--arch/arm/configs/colibri_vf_defconfig2
-rw-r--r--arch/arm/mach-mvf/board-colibri_vf.c28
-rw-r--r--arch/arm/mach-mvf/devices-mvf.h2
-rw-r--r--arch/arm/mach-mxs/devices/platform-mxs-pwm.c2
-rwxr-xr-xarch/arm/plat-mxc/Kconfig6
-rwxr-xr-xarch/arm/plat-mxc/Makefile1
-rwxr-xr-xarch/arm/plat-mxc/devices/platform-mxc_pwm.c26
-rwxr-xr-xarch/arm/plat-mxc/ftm-pwm.c460
-rwxr-xr-xarch/arm/plat-mxc/include/mach/devices-common.h3
9 files changed, 510 insertions, 20 deletions
diff --git a/arch/arm/configs/colibri_vf_defconfig b/arch/arm/configs/colibri_vf_defconfig
index 008b5854cd8e..4832addb28f7 100644
--- a/arch/arm/configs/colibri_vf_defconfig
+++ b/arch/arm/configs/colibri_vf_defconfig
@@ -27,7 +27,7 @@ CONFIG_ARCH_MXC=y
CONFIG_ARCH_MVF=y
CONFIG_MACH_COLIBRI_VF50=y
CONFIG_MACH_COLIBRI_VF61=y
-CONFIG_MXC_PWM=y
+CONFIG_MVF_FTM_PWM=y
CONFIG_CLK_DEBUG=y
CONFIG_DMA_ZONE_SIZE=16
CONFIG_NO_HZ=y
diff --git a/arch/arm/mach-mvf/board-colibri_vf.c b/arch/arm/mach-mvf/board-colibri_vf.c
index 8e5a84119dbc..4e463f556839 100644
--- a/arch/arm/mach-mvf/board-colibri_vf.c
+++ b/arch/arm/mach-mvf/board-colibri_vf.c
@@ -578,7 +578,7 @@ static int colibri_vf50_backlight_notify(struct device *dev, int brightness)
}
static struct platform_pwm_backlight_data colibri_vf50_backlight_data = {
- .pwm_id = 1, /* PWM<A> (FTM0CH0) */
+ .pwm_id = 0, /* PWM<A> (FTM0CH0) */
.max_brightness = 255,
.dft_brightness = 127,
.pwm_period_ns = 1000000, /* 1 kHz */
@@ -632,28 +632,28 @@ static struct led_pwm tegra_leds_pwm[] = {
#if 0
{
.name = "PWM<A>",
- .pwm_id = 1,
+ .pwm_id = 0, /* FTM0CH0 */
.max_brightness = 255,
- .pwm_period_ns = 19600,
+ .pwm_period_ns = 1000000,
},
#endif
{
.name = "PWM<B>",
- .pwm_id = 2,
+ .pwm_id = 8, /* FTM1CH0 */
.max_brightness = 255,
- .pwm_period_ns = 19600,
+ .pwm_period_ns = 1000000,
},
{
.name = "PWM<C>",
- .pwm_id = 3,
+ .pwm_id = 1, /* FTM0CH1 */
.max_brightness = 255,
- .pwm_period_ns = 19600,
+ .pwm_period_ns = 1000000,
},
{
.name = "PWM<D>",
- .pwm_id = 4,
+ .pwm_id = 9, /* FTM1CH1 */
.max_brightness = 255,
- .pwm_period_ns = 19600,
+ .pwm_period_ns = 1000000,
},
};
@@ -748,8 +748,14 @@ static void __init mvf_board_init(void)
spi_device_init();
mvfa5_add_dcu(0, &mvf_dcu_pdata);
+
+ /* Enable FTM0 and FTM1 */
mvf_add_mxc_pwm(0);
+ mvf_add_mxc_pwm(1);
+
+ /* Backlight on FTM0CH0 */
mvf_add_mxc_pwm_backlight(0, &colibri_vf50_backlight_data);
+ mvf_add_pwm_leds(&tegra_leds_pwm_data);
mvf_add_wdt(0);
@@ -757,10 +763,6 @@ static void __init mvf_board_init(void)
mvf_add_nand(&mvf_data);
- mvf_add_mxc_pwm(1);
- mvf_add_mxc_pwm(2);
- mvf_add_mxc_pwm(3);
- mvf_add_pwm_leds(&tegra_leds_pwm_data);
#ifdef CONFIG_CAN_FLEXCAN
mvf_add_flexcan0(&can0_pdata);
diff --git a/arch/arm/mach-mvf/devices-mvf.h b/arch/arm/mach-mvf/devices-mvf.h
index c5c3db9d13a1..9870c4ff04af 100644
--- a/arch/arm/mach-mvf/devices-mvf.h
+++ b/arch/arm/mach-mvf/devices-mvf.h
@@ -170,7 +170,7 @@ extern const struct imx_viv_gpu_data mvf_gc355_data __initconst;
extern const struct imx_mxc_pwm_data mvf_mxc_pwm_data[] __initconst;
#define mvf_add_mxc_pwm(id) \
- imx_add_mxc_pwm(&mvf_mxc_pwm_data[id])
+ imx_add_mvf_ftm_pwm(&mvf_mxc_pwm_data[id])
#define mvf_add_mxc_pwm_backlight(id, pdata) \
platform_device_register_resndata(NULL, "pwm-backlight",\
diff --git a/arch/arm/mach-mxs/devices/platform-mxs-pwm.c b/arch/arm/mach-mxs/devices/platform-mxs-pwm.c
index 680f5a902936..b1f5e1810f65 100644
--- a/arch/arm/mach-mxs/devices/platform-mxs-pwm.c
+++ b/arch/arm/mach-mxs/devices/platform-mxs-pwm.c
@@ -18,5 +18,7 @@ struct platform_device *__init mxs_add_mxs_pwm(resource_size_t iobase, int id)
res.start = iobase + 0x10 + 0x20 * id;
res.end = res.start + 0x1f;
+ printk(KERN_INFO "adding pwm start %x to end %x\n", res.start, res.end);
+
return mxs_add_platform_device("mxs-pwm", id, &res, 1, NULL, 0);
}
diff --git a/arch/arm/plat-mxc/Kconfig b/arch/arm/plat-mxc/Kconfig
index d3cf435d6d28..0bb90b387489 100755
--- a/arch/arm/plat-mxc/Kconfig
+++ b/arch/arm/plat-mxc/Kconfig
@@ -102,6 +102,12 @@ config MXC_PWM
help
Enable support for the i.MX PWM controller(s).
+config MVF_FTM_PWM
+ tristate "Enable FlexTimer PWM driver"
+ select HAVE_PWM
+ help
+ Enable support for the FlexTimer PWM controller(s).
+
config MXC_PWM_CPWM
bool "Center-Aligned PWM mode"
depends on MXC_PWM && ARCH_MVF
diff --git a/arch/arm/plat-mxc/Makefile b/arch/arm/plat-mxc/Makefile
index 6bb77052bbf4..89a7c90987fe 100755
--- a/arch/arm/plat-mxc/Makefile
+++ b/arch/arm/plat-mxc/Makefile
@@ -26,6 +26,7 @@ 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_MVF_FTM_PWM) += ftm-pwm.o
obj-$(CONFIG_MXC_ULPI) += ulpi.o
obj-$(CONFIG_MXC_USE_EPIT) += epit.o
obj-$(CONFIG_ARCH_MXC_AUDMUX_V1) += audmux-v1.o
diff --git a/arch/arm/plat-mxc/devices/platform-mxc_pwm.c b/arch/arm/plat-mxc/devices/platform-mxc_pwm.c
index c157cf9c782c..32a7516019b9 100755
--- a/arch/arm/plat-mxc/devices/platform-mxc_pwm.c
+++ b/arch/arm/plat-mxc/devices/platform-mxc_pwm.c
@@ -72,31 +72,49 @@ const struct imx_mxc_pwm_data imx6q_mxc_pwm_data[] __initconst = {
#ifdef CONFIG_SOC_MVFA5
const struct imx_mxc_pwm_data mvf_mxc_pwm_data[] __initdata = {
[0] = {
- 1,
+ 0,
MVF_FTM0_BASE_ADDR,
SZ_4K,
MVF_INT_FLEXTIMER0,
},
[1] = {
- 2,
+ 1,
MVF_FTM1_BASE_ADDR,
SZ_4K,
MVF_INT_FLEXTIMER1,
},
[2] = {
- 3,
+ 2,
MVF_FTM2_BASE_ADDR,
SZ_4K,
MVF_INT_FLEXTIMER2,
},
[3] = {
- 4,
+ 3,
MVF_FTM3_BASE_ADDR,
SZ_4K,
MVF_INT_FLEXTIMER3,
},
};
+struct platform_device *__init imx_add_mvf_ftm_pwm(
+ const struct imx_mxc_pwm_data *data)
+{
+ struct resource res[] = {
+ {
+ .start = data->iobase,
+ .end = data->iobase + data->iosize - 1,
+ .flags = IORESOURCE_MEM,
+ }, {
+ .start = data->irq,
+ .end = data->irq,
+ .flags = IORESOURCE_IRQ,
+ },
+ };
+
+ return imx_add_platform_device("mvf_ftm_pwm", data->id,
+ res, ARRAY_SIZE(res), NULL, 0);
+}
#endif
struct platform_device *__init imx_add_mxc_pwm(
diff --git a/arch/arm/plat-mxc/ftm-pwm.c b/arch/arm/plat-mxc/ftm-pwm.c
new file mode 100755
index 000000000000..211018c14276
--- /dev/null
+++ b/arch/arm/plat-mxc/ftm-pwm.c
@@ -0,0 +1,460 @@
+/*
+ * FlexTimer Module PWM driver
+ *
+ * (c) 2014, Toradex AG
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Derived from pxa PWM driver by eric miao <eric.miao@marvell.com>
+ * Copyright 2009-2012 Freescale Semiconductor, Inc.
+ */
+
+#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 <linux/fsl_devices.h>
+#include <mach/hardware.h>
+
+/*
+ * Vybrid(CONFIG_ARCH_MVF) FlexTimer PWM registers defination
+ */
+#define MVF_PWM_FTM_SC 0x00 /* status and controls */
+#define MVF_PWM_FTM_CNT 0x04 /* counter */
+#define MVF_PWM_FTM_MOD 0x08 /* modulo */
+
+#define MVF_PWM_FTM_CxSC(ch) (((ch) * 0x8) + 0x0C) /* channel(x) status and control */
+#define MVF_PWM_FTM_CxV(ch) (((ch) * 0x8) + 0x10) /* channel(x) value */
+
+#define MVF_PWM_FTM_CNTIN 0x4C /* counter initial value */
+#define MVF_PWM_FTM_STATUS 0x50 /* capture and compare status */
+#define MVF_PWM_FTM_MODE 0x54 /* mode select */
+#define MVF_PWM_FTM_SYNC 0x58 /* synchronization */
+#define MVF_PWM_FTM_OUTINIT 0x5C /* initial state for channels output */
+#define MVF_PWM_FTM_OUTMASK 0x60 /* output mask */
+#define MVF_PWM_FTM_COMBINE 0x64 /* function for linked channels */
+#define MVF_PWM_FTM_DEADTIME 0x68 /* deadtime insertion control */
+#define MVF_PWM_FTM_EXTTRIG 0x6C /* external trigger */
+#define MVF_PWM_FTM_POL 0x70 /* channels polarity */
+#define MVF_PWM_FTM_FMS 0x74 /* fault mode status */
+#define MVF_PWM_FTM_FILTER 0x78 /* input capture filter control */
+#define MVF_PWM_FTM_FLTCTRL 0x7C /* fault control */
+#define MVF_PWM_FTM_QDCTRL 0x80 /* quadrature decoder ctrl and status */
+#define MVF_PWM_FTM_CONF 0x84 /* configuration */
+#define MVF_PWM_FTM_FLTPOL 0x88 /* fault input polarity */
+#define MVF_PWM_FTM_SYNCONF 0x8C /* synchronization configuration */
+#define MVF_PWM_FTM_INVCTRL 0x90 /* inverting control */
+#define MVF_PWM_FTM_SWOCTRL 0x94 /* software output control */
+#define MVF_PWM_FTM_PWMLOAD 0x98 /* PWM load */
+
+#define PWM_TYPE_EPWM 0x01 /* Edge-aligned pwm */
+#define PWM_TYPE_CPWM 0x02 /* Center-aligned pwm */
+
+#define PWM_FTMSC_CPWMS (0x01 << 5)
+#define PWM_FTMSC_CLK_MASK 0x3
+#define PWM_FTMSC_CLK_OFFSET 3
+#define PWM_FTMSC_CLKSYS (0x1 << 3)
+#define PWM_FTMSC_CLKFIX (0x2 << 3)
+#define PWM_FTMSC_CLKEXT (0x3 << 3)
+#define PWM_FTMSC_PS_MASK 0x7
+#define PWM_FTMSC_PS_OFFSET 0
+#define PWM_FTMSC_PS1 0x0
+#define PWM_FTMSC_PS2 0x1
+#define PWM_FTMSC_PS4 0x2
+#define PWM_FTMSC_PS8 0x3
+#define PWM_FTMSC_PS16 0x4
+#define PWM_FTMSC_PS32 0x5
+#define PWM_FTMSC_PS64 0x6
+#define PWM_FTMSC_PS128 0x7
+
+#define PWM_FTMCnSC_MSB (0x1 << 5)
+#define PWM_FTMCnSC_MSA (0x1 << 4)
+#define PWM_FTMCnSC_ELSB (0x1 << 3)
+#define PWM_FTMCnSC_ELSA (0x1 << 2)
+
+#define FTM_PWMMODE (PWM_FTMCnSC_MSB)
+#define FTM_PWM_HIGH_TRUE (PWM_FTMCnSC_ELSB)
+#define FTM_PWM_LOW_TRUE (PWM_FTMCnSC_ELSA)
+
+#define PWM_FTMMODE_FTMEN 0x01
+#define PWM_FTMMODE_INIT 0x02
+#define PWM_FTMMODE_PWMSYNC (0x01 << 3)
+
+#define PWM_FTM_OUTMASK(x) (0x01 << (x))
+#define PWM_FTM_OUTINIT(x) (0x01 << (x))
+
+struct pwm_device {
+ struct list_head node;
+ struct ftm_device *ftm;
+
+ const char *label;
+ unsigned int pwm_id;
+ unsigned char ftm_ch;
+ bool used;
+};
+
+struct ftm_device {
+ struct platform_device *pdev;
+
+ struct clk *clk;
+
+ int clk_enabled;
+ void __iomem *mmio_base;
+
+ unsigned int use_count;
+ int pwmo_invert;
+ unsigned int ftm_id;
+ unsigned int cpwm; /* CPWM mode */
+ unsigned int clk_ps; /* clock prescaler:1/2/4/8/16/32/64/128 */
+ void (*enable_pwm_pad)(void);
+ void (*disable_pwm_pad)(void);
+};
+
+int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
+{
+ struct ftm_device *ftm;
+ unsigned long long c;
+ unsigned long period_cycles, duty_cycles;
+ u32 ps, reg;
+
+ printk(KERN_DEBUG "pwm_config %d, duty_ns %d period_ns %d\n",
+ pwm->pwm_id, duty_ns, period_ns);
+ if (pwm == NULL || period_ns == 0 || duty_ns > period_ns)
+ return -EINVAL;
+
+ ftm = pwm->ftm;
+
+ /* FTM clock source prescaler */
+ ps = (0x1 << ftm->clk_ps) * 1000;
+ /* IPS bus clock source */
+ c = clk_get_rate(ftm->clk) / 1000000UL;
+
+ c = c * period_ns;
+ do_div(c, ps);
+ period_cycles = (unsigned long)c;
+
+ c = clk_get_rate(ftm->clk) / 1000000UL;
+ c = c * duty_ns;
+ do_div(c, ps);
+ duty_cycles = (unsigned long)c;
+
+ if (period_cycles > 0xFFFF) {
+ dev_warn(&ftm->pdev->dev,
+ "required PWM period cycles(%lu) overflow"
+ "16-bits counter!\n", period_cycles);
+ period_cycles = 0xFFFF;
+ }
+ if (duty_cycles >= 0xFFFF) {
+ dev_warn(&ftm->pdev->dev,
+ "required PWM duty cycles(%lu) overflow"
+ "16-bits counter!\n", duty_cycles);
+ duty_cycles = 0xFFFF - 1;
+ }
+ if (duty_cycles > period_cycles)
+ duty_cycles = period_cycles;
+
+ /* configure channel to pwm mode */
+ /* enable FTMEN */
+ if (ftm->cpwm) {
+ u32 reg;
+ reg = __raw_readl(ftm->mmio_base + MVF_PWM_FTM_SC);
+ reg |= 0x01 << 5;
+ __raw_writel(reg, ftm->mmio_base + MVF_PWM_FTM_SC);
+ }
+
+ __raw_writel(FTM_PWMMODE | FTM_PWM_HIGH_TRUE,
+ ftm->mmio_base + MVF_PWM_FTM_CxSC(pwm->ftm_ch));
+
+ reg = __raw_readl(ftm->mmio_base + MVF_PWM_FTM_OUTMASK);
+ reg &= ~PWM_FTM_OUTMASK(pwm->ftm_ch);
+ __raw_writel(reg, ftm->mmio_base + MVF_PWM_FTM_OUTMASK);
+
+ reg = __raw_readl(ftm->mmio_base + MVF_PWM_FTM_OUTINIT);
+ reg |= PWM_FTM_OUTINIT(pwm->ftm_ch);
+ __raw_writel(reg, ftm->mmio_base + MVF_PWM_FTM_OUTINIT);
+ __raw_writel(0x0, ftm->mmio_base + MVF_PWM_FTM_CNTIN);
+
+ if (ftm->cpwm) {
+ /*
+ * Center-aligned PWM:
+ * period = 2*(MOD - CNTIN)
+ * duty = 2*(CnV - CNTIN)
+ */
+ period_cycles /= 2;
+ duty_cycles /= 2;
+ } else {
+ /* Edge-aligend PWM
+ * period = MOD - CNTIN + 1
+ * duty = CnV - CNTIN
+ */
+ period_cycles -= 1;
+ }
+
+ __raw_writel(period_cycles, ftm->mmio_base + MVF_PWM_FTM_MOD);
+ __raw_writel(duty_cycles, ftm->mmio_base + MVF_PWM_FTM_CxV(pwm->ftm_ch));
+
+ return 0;
+}
+EXPORT_SYMBOL(pwm_config);
+
+int pwm_enable(struct pwm_device *pwm)
+{
+ struct ftm_device *ftm = pwm->ftm;
+ unsigned long reg;
+ int rc = 0;
+
+ if (!ftm->clk_enabled) {
+ rc = clk_enable(ftm->clk);
+ if (!rc)
+ ftm->clk_enabled++;
+
+ reg = __raw_readl(ftm->mmio_base + MVF_PWM_FTM_SC);
+ reg &= ~((PWM_FTMSC_CLK_MASK << PWM_FTMSC_CLK_OFFSET) |
+ (PWM_FTMSC_PS_MASK << PWM_FTMSC_PS_OFFSET));
+ /*
+ * select IPS bus clock source
+ * prescale 128
+ */
+ reg |= (PWM_FTMSC_CLKSYS | ftm->clk_ps);
+ __raw_writel(reg, ftm->mmio_base + MVF_PWM_FTM_SC);
+ }
+
+ if (ftm->enable_pwm_pad)
+ ftm->enable_pwm_pad();
+
+ return rc;
+}
+EXPORT_SYMBOL(pwm_enable);
+
+void pwm_disable(struct pwm_device *pwm)
+{
+ struct ftm_device *ftm = pwm->ftm;
+ u32 reg;
+
+ reg = __raw_readl(ftm->mmio_base + MVF_PWM_FTM_OUTMASK);
+ reg |= PWM_FTM_OUTMASK(pwm->ftm_ch);
+ __raw_writel(reg, ftm->mmio_base + MVF_PWM_FTM_OUTMASK);
+
+ if (ftm->disable_pwm_pad)
+ ftm->disable_pwm_pad();
+
+ if (ftm->clk_enabled) {
+ ftm->clk_enabled--;
+
+ if (!ftm->clk_enabled)
+ clk_disable(ftm->clk);
+ }
+}
+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->used) {
+ pwm->used = true;
+ pwm->ftm->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->used) {
+ pwm->used = false;
+ pwm->ftm->use_count--;
+ pwm->label = NULL;
+ } else
+ pr_warning("PWM device already freed\n");
+
+ mutex_unlock(&pwm_lock);
+}
+EXPORT_SYMBOL(pwm_free);
+
+static int mvf_ftm_pwm_remove_channels(struct ftm_device *ftm)
+{
+ struct pwm_device *pwm;
+
+ list_for_each_entry(pwm, &pwm_list, node) {
+ mutex_lock(&pwm_lock);
+ list_del(&pwm->node);
+ mutex_unlock(&pwm_lock);
+ }
+
+ return 0;
+}
+
+static int mvf_ftm_pwm_add_channels(struct ftm_device *ftm, int channels)
+{
+ static int pwm_id = 0;
+ struct pwm_device *pwm;
+ int i;
+
+ for (i = 0; i < channels; i++) {
+ pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL);
+ if (pwm == NULL)
+ goto no_memory;
+
+ pwm->pwm_id = pwm_id++;
+ pwm->ftm = ftm;
+ pwm->ftm_ch = i;
+
+ mutex_lock(&pwm_lock);
+ list_add_tail(&pwm->node, &pwm_list);
+ mutex_unlock(&pwm_lock);
+ }
+
+ return 0;
+no_memory:
+ mvf_ftm_pwm_remove_channels(ftm);
+ return -ENOMEM;
+}
+
+static int __devinit mvf_ftm_pwm_probe(struct platform_device *pdev)
+{
+ struct ftm_device *ftm;
+ struct resource *r;
+ struct mxc_pwm_platform_data *plat_data = pdev->dev.platform_data;
+ int ret = 0;
+
+ ftm = kzalloc(sizeof(struct ftm_device), GFP_KERNEL);
+ if (ftm == NULL) {
+ dev_err(&pdev->dev, "failed to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ ftm->clk = clk_get(&pdev->dev, "pwm");
+
+ if (IS_ERR(ftm->clk)) {
+ ret = PTR_ERR(ftm->clk);
+ goto err_free;
+ }
+
+ ftm->clk_enabled = 0;
+
+ ftm->use_count = 0;
+ ftm->ftm_id = pdev->id;
+ ftm->pdev = pdev;
+ /* default select IPS bus clock, and divided by 128 for MVF platform */
+ ftm->clk_ps = PWM_FTMSC_PS128;
+#ifdef CONFIG_MXC_PWM_CPWM
+ ftm->cpwm = 1;
+#endif
+ if (plat_data != NULL) {
+ ftm->pwmo_invert = plat_data->pwmo_invert;
+ ftm->enable_pwm_pad = plat_data->enable_pwm_pad;
+ ftm->disable_pwm_pad = plat_data->disable_pwm_pad;
+ }
+
+ 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, r->end - r->start + 1, pdev->name);
+ if (r == NULL) {
+ dev_err(&pdev->dev, "failed to request memory resource\n");
+ ret = -EBUSY;
+ goto err_free_clk;
+ }
+
+ ftm->mmio_base = ioremap(r->start, r->end - r->start + 1);
+ if (ftm->mmio_base == NULL) {
+ dev_err(&pdev->dev, "failed to ioremap() registers\n");
+ ret = -ENODEV;
+ goto err_free_mem;
+ }
+
+ if (mvf_ftm_pwm_add_channels(ftm, 8))
+ goto err_free_mem;
+
+ dev_info(&pdev->dev, "added 8 PWM channels\n");
+
+ platform_set_drvdata(pdev, ftm);
+
+ return 0;
+err_free_mem:
+ release_mem_region(r->start, r->end - r->start + 1);
+err_free_clk:
+ clk_put(ftm->clk);
+err_free:
+ kfree(ftm);
+ return ret;
+}
+
+static int __devexit mvf_ftm_pwm_remove(struct platform_device *pdev)
+{
+ struct ftm_device *ftm;
+ struct resource *r;
+
+ ftm = platform_get_drvdata(pdev);
+ if (ftm == NULL)
+ return -ENODEV;
+
+ mvf_ftm_pwm_remove_channels(ftm);
+
+ iounmap(ftm->mmio_base);
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ release_mem_region(r->start, r->end - r->start + 1);
+
+ clk_put(ftm->clk);
+
+ kfree(ftm);
+ return 0;
+}
+
+static struct platform_driver mvf_ftm_pwm_driver = {
+ .driver = {
+ .name = "mvf_ftm_pwm",
+ },
+ .probe = mvf_ftm_pwm_probe,
+ .remove = __devexit_p(mvf_ftm_pwm_remove),
+};
+
+static int __init mvf_ftm_pwm_init(void)
+{
+ return platform_driver_register(&mvf_ftm_pwm_driver);
+}
+arch_initcall(mvf_ftm_pwm_init);
+
+static void __exit mvf_ftm_pwm_exit(void)
+{
+ platform_driver_unregister(&mvf_ftm_pwm_driver);
+}
+module_exit(mvf_ftm_pwm_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Stefan Agner <stefan.agner@toradex.com>");
+MODULE_DESCRIPTION("FlexTimer PWM driver");
diff --git a/arch/arm/plat-mxc/include/mach/devices-common.h b/arch/arm/plat-mxc/include/mach/devices-common.h
index 0e2a2a1d13e9..877a78beb2a0 100755
--- a/arch/arm/plat-mxc/include/mach/devices-common.h
+++ b/arch/arm/plat-mxc/include/mach/devices-common.h
@@ -326,7 +326,8 @@ struct imx_mxc_pwm_data {
};
struct platform_device *__init imx_add_mxc_pwm(
const struct imx_mxc_pwm_data *data);
-
+struct platform_device *__init imx_add_mvf_ftm_pwm(
+ const struct imx_mxc_pwm_data *data);
/* mxc_rtc */
struct imx_mxc_rtc_data {
resource_size_t iobase;