From 7524aaeff2fef8a41ee3d4c91fde72b013d8bab5 Mon Sep 17 00:00:00 2001
From: Eric Lee <eric.lee@embedian.com>
Date: Tue, 16 Jun 2015 01:02:24 +0800
Subject: [PATCH] Add U-Boot LVDS Backlight PWM Support

---
 README                                   |   4 ++
 arch/arm/include/asm/arch-mx6/imx-regs.h |  17 +++++
 board/embedian/smarcfimx6/smarcfimx6.c   |  25 +++++--
 drivers/Makefile                         |   1 +
 drivers/pwm/Makefile                     |  13 ++++
 drivers/pwm/pwm-imx-util.c               |  73 +++++++++++++++++++++
 drivers/pwm/pwm-imx-util.h               |  16 +++++
 drivers/pwm/pwm-imx.c                    | 109 +++++++++++++++++++++++++++++++
 include/configs/smarcfimx6.h             |   4 ++
 9 files changed, 255 insertions(+), 7 deletions(-)
 create mode 100644 drivers/pwm/Makefile
 create mode 100644 drivers/pwm/pwm-imx-util.c
 create mode 100644 drivers/pwm/pwm-imx-util.h
 create mode 100644 drivers/pwm/pwm-imx.c

diff --git a/README b/README
index 25b22a3..857b5b7 100644
--- a/README
+++ b/README
@@ -1314,6 +1314,10 @@ The following options need to be configured:
 			CONFIG_SH_ETHER_CACHE_WRITEBACK
 			If this option is set, the driver enables cache flush.
 
+- PWM Support:
+		CONFIG_PWM_IMX
+		Support for PWM modul on the imx6.
+
 - TPM Support:
 		CONFIG_TPM
 		Support TPM devices.
diff --git a/arch/arm/include/asm/arch-mx6/imx-regs.h b/arch/arm/include/asm/arch-mx6/imx-regs.h
index fd85803..57e10af 100644
--- a/arch/arm/include/asm/arch-mx6/imx-regs.h
+++ b/arch/arm/include/asm/arch-mx6/imx-regs.h
@@ -679,6 +679,23 @@ struct wdog_regs {
 	u16	wmcr;	/* Miscellaneous Control */
 };
 
+#define PWMCR_PRESCALER(x)	(((x - 1) & 0xFFF) << 4)
+#define PWMCR_DOZEEN		(1 << 24)
+#define PWMCR_WAITEN		(1 << 23)
+#define PWMCR_DBGEN		(1 << 22)
+#define PWMCR_CLKSRC_IPG_HIGH	(2 << 16)
+#define PWMCR_CLKSRC_IPG	(1 << 16)
+#define PWMCR_EN		(1 << 0)
+
+struct pwm_regs {
+	u32	cr;
+	u32	sr;
+	u32	ir;
+	u32	sar;
+	u32	pr;
+	u32	cnr;
+};
+
 struct dbg_monitor_regs {
 	u32	ctrl[4];		/* Control */
 	u32	master_en[4];		/* Master enable */
diff --git a/board/embedian/smarcfimx6/smarcfimx6.c b/board/embedian/smarcfimx6/smarcfimx6.c
index 1f4b626..0a158af 100644
--- a/board/embedian/smarcfimx6/smarcfimx6.c
+++ b/board/embedian/smarcfimx6/smarcfimx6.c
@@ -32,6 +32,7 @@
 #include <ipu_pixfmt.h>
 #include <asm/io.h>
 #include <asm/arch/sys_proto.h>
+#include <pwm.h>
 #ifdef CONFIG_SYS_I2C_MXC
 #include <i2c.h>
 #include <asm/imx-common/mxc_i2c.h>
@@ -835,11 +836,9 @@ static iomux_v3_cfg_t const backlight_pads[] = {
         MX6_PAD_GPIO_0__GPIO1_IO00 | MUX_PAD_CTRL(NO_PAD_CTRL),
 #define BACKLIGHT_EN IMX_GPIO_NR(1, 00)
         /* PWM Backlight Control: S141 */
-        MX6_PAD_GPIO_1__GPIO1_IO01 | MUX_PAD_CTRL(NO_PAD_CTRL),
-#define BACKLIGHT_PWM IMX_GPIO_NR(1, 01)
+
         /* Backlight Enable for LVDS: S127 */
-        /*MX6_PAD_GPIO_0__GPIO1_IO00 | MUX_PAD_CTRL(NO_PAD_CTRL),
-#define LVDS_BACKLIGHT_EN IMX_GPIO_NR(1, 00)*/
+        MX6_PAD_GPIO_1__PWM2_OUT | MUX_PAD_CTRL(NO_PAD_CTRL),
         /* LCD VDD Enable(for parallel LCD): S133 */
         MX6_PAD_GPIO_2__GPIO1_IO02 | MUX_PAD_CTRL(NO_PAD_CTRL),
 #define LCD_VDD_EN IMX_GPIO_NR(1, 02)
@@ -1075,13 +1074,25 @@ static void setup_display(void)
         writel(reg, &iomux->gpr[3]);
         /* backlights off until needed */
 
-        /*imx_iomux_v3_setup_multiple_pads(backlight_pads,
+        imx_iomux_v3_setup_multiple_pads(backlight_pads,
                                          ARRAY_SIZE(backlight_pads));
-        gpio_direction_input(BACKLIGHT_EN);*/
+        /*gpio_direction_input(BACKLIGHT_EN);*/
 	/* turn on backlight */
         gpio_direction_output(BACKLIGHT_EN, 1);
-        gpio_direction_output(BACKLIGHT_PWM, 1);
         gpio_direction_output(LCD_VDD_EN, 1);
+        /* enable backlight PWM 2 */
+        if (pwm_init(1, 0, 0))
+                goto error;
+        /* duty cycle 500ns, period: 3000ns */
+        if (pwm_config(1, 1000, 3000))
+                goto error;
+        if (pwm_enable(1))
+                goto error;
+        return;
+
+error:
+        puts("error init pwm for backlight\n");
+        return;
 }
 #endif /* CONFIG_VIDEO_IPUV3 */
 
diff --git a/drivers/Makefile b/drivers/Makefile
index e103896..99d8b81 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -15,3 +15,4 @@ obj-y += video/
 obj-y += watchdog/
 obj-y += fastboot/
 obj-$(CONFIG_QE) += qe/
+obj-y += pwm/
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
new file mode 100644
index 0000000..dd3a09a
--- /dev/null
+++ b/drivers/pwm/Makefile
@@ -0,0 +1,13 @@
+#
+# (C) Copyright 2006
+# Wolfgang Denk, DENX Software Engineering, wd at denx.de.
+#
+# (C) Copyright 2001
+# Erik Theisen, Wave 7 Optics, etheisen at mindspring.com.
+#
+# SPDX-License-Identifier:      GPL-2.0+
+#
+
+#ccflags-y += -DDEBUG
+
+obj-$(CONFIG_PWM_IMX) += pwm-imx.o
diff --git a/drivers/pwm/pwm-imx-util.c b/drivers/pwm/pwm-imx-util.c
new file mode 100644
index 0000000..f1d0b35
--- /dev/null
+++ b/drivers/pwm/pwm-imx-util.c
@@ -0,0 +1,73 @@
+/*
+ * (C) Copyright 2014
+ * Heiko Schocher, DENX Software Engineering, hs@denx.de.
+ *
+ * Basic support for the pwm modul on imx6.
+ *
+ * Based on linux:drivers/pwm/pwm-imx.c
+ * from
+ * Sascha Hauer <s.hauer@pengutronix.de>
+ *
+ * SPDX-License-Identifier:	GPL-2.0
+ */
+
+#include <common.h>
+#include <div64.h>
+#include <asm/arch/imx-regs.h>
+
+/* pwm_id from 0..3 */
+struct pwm_regs *pwm_id_to_reg(int pwm_id)
+{
+	switch (pwm_id) {
+	case 0:
+		return (struct pwm_regs *)PWM1_BASE_ADDR;
+		break;
+	case 1:
+		return (struct pwm_regs *)PWM2_BASE_ADDR;
+		break;
+	case 2:
+		return (struct pwm_regs *)PWM3_BASE_ADDR;
+		break;
+	case 3:
+		return (struct pwm_regs *)PWM4_BASE_ADDR;
+		break;
+	default:
+		printf("unknown pwm_id: %d\n", pwm_id);
+		break;
+	}
+	return NULL;
+}
+
+int pwm_imx_get_parms(int period_ns, int duty_ns, unsigned long *period_c,
+		      unsigned long *duty_c, unsigned long *prescale)
+{
+	unsigned long long c;
+
+	/*
+	 * we have not yet a clock framework for imx6, so add the clock
+	 * value here as a define. Replace it when we have the clock
+	 * framework.
+	 */
+	c = CONFIG_IMX6_PWM_PER_CLK;
+	c = c * period_ns;
+	do_div(c, 1000000000);
+	*period_c = c;
+
+	*prescale = *period_c / 0x10000 + 1;
+
+	*period_c /= *prescale;
+	c = (unsigned long long)(*period_c * duty_ns);
+	do_div(c, period_ns);
+	*duty_c = c;
+
+	/*
+	 * according to imx pwm RM, the real period value should be
+	 * PERIOD value in PWMPR plus 2.
+	 */
+	if (*period_c > 2)
+		*period_c -= 2;
+	else
+		*period_c = 0;
+
+	return 0;
+}
diff --git a/drivers/pwm/pwm-imx-util.h b/drivers/pwm/pwm-imx-util.h
new file mode 100644
index 0000000..45465c4
--- /dev/null
+++ b/drivers/pwm/pwm-imx-util.h
@@ -0,0 +1,16 @@
+/*
+ * (C) Copyright 2014
+ * Heiko Schocher, DENX Software Engineering, hs@denx.de.
+ *
+ * Basic support for the pwm modul on imx6.
+ *
+ * SPDX-License-Identifier:	GPL-2.0
+ */
+
+#ifndef _pwm_imx_util_h_
+#define _pwm_imx_util_h_
+
+struct pwm_regs *pwm_id_to_reg(int pwm_id);
+int pwm_imx_get_parms(int period_ns, int duty_ns, unsigned long *period_c,
+		      unsigned long *duty_c, unsigned long *prescale);
+#endif
diff --git a/drivers/pwm/pwm-imx.c b/drivers/pwm/pwm-imx.c
new file mode 100644
index 0000000..cb8f89f
--- /dev/null
+++ b/drivers/pwm/pwm-imx.c
@@ -0,0 +1,109 @@
+/*
+ * (C) Copyright 2014
+ * Heiko Schocher, DENX Software Engineering, hs at denx.de.
+ *
+ * Basic support for the pwm modul on imx6.
+ *
+ * Based on linux:drivers/pwm/pwm-imx.c
+ * from
+ * Sascha Hauer <s.hauer at pengutronix.de>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include <div64.h>
+#include <pwm.h>
+#include <asm/arch/imx-regs.h>
+#include <asm/io.h>
+
+/* pwm_id from 0..3 */
+static struct pwm_regs *pwm_id_to_reg(int pwm_id)
+{
+	switch (pwm_id) {
+	case 0:
+		return (struct pwm_regs *)PWM1_BASE_ADDR;
+		break;
+	case 1:
+		return (struct pwm_regs *)PWM2_BASE_ADDR;
+		break;
+	case 2:
+		return (struct pwm_regs *)PWM3_BASE_ADDR;
+		break;
+	case 3:
+		return (struct pwm_regs *)PWM4_BASE_ADDR;
+		break;
+	default:
+		printf("unknown pwm_id: %d\n", pwm_id);
+		break;
+	}
+	return NULL;
+}
+
+int pwm_init(int pwm_id, int div, int invert)
+{
+	struct pwm_regs *pwm = (struct pwm_regs *)pwm_id_to_reg(pwm_id);
+
+	writel(0, &pwm->ir);
+	return 0;
+}
+
+int pwm_config(int pwm_id, int duty_ns, int period_ns)
+{
+	struct pwm_regs *pwm = (struct pwm_regs *)pwm_id_to_reg(pwm_id);
+	unsigned long long c;
+	unsigned long period_cycles, duty_cycles, prescale;
+	u32 cr;
+
+	/*
+	 * we have not yet a clock framework for imx6, so add the clock
+	 * value here as a define. Replace it when we have the clock
+	 * framework.
+	 */
+	c = CONFIG_IMX6_PWM_PER_CLK;
+	c = c * period_ns;
+	do_div(c, 1000000000);
+	period_cycles = c;
+
+	prescale = period_cycles / 0x10000 + 1;
+
+	period_cycles /= prescale;
+	c = (unsigned long long)period_cycles * duty_ns;
+	do_div(c, period_ns);
+	duty_cycles = c;
+
+	/*
+	 * according to imx pwm RM, the real period value should be
+	 * PERIOD value in PWMPR plus 2.
+	 */
+	if (period_cycles > 2)
+		period_cycles -= 2;
+	else
+		period_cycles = 0;
+
+	cr = PWMCR_PRESCALER(prescale) |
+		PWMCR_DOZEEN | PWMCR_WAITEN |
+		PWMCR_DBGEN | PWMCR_CLKSRC_IPG_HIGH;
+
+	writel(cr, &pwm->cr);
+	/* set duty cycles */
+	writel(duty_cycles, &pwm->sar);
+	/* set period cycles */
+	writel(period_cycles, &pwm->pr);
+	return 0;
+}
+
+int pwm_enable(int pwm_id)
+{
+	struct pwm_regs *pwm = (struct pwm_regs *)pwm_id_to_reg(pwm_id);
+
+	setbits_le32(&pwm->cr, PWMCR_EN);
+	return 0;
+}
+
+void pwm_disable(int pwm_id)
+{
+	struct pwm_regs *pwm = (struct pwm_regs *)pwm_id_to_reg(pwm_id);
+
+	clrbits_le32(&pwm->cr, PWMCR_EN);
+} 
diff --git a/include/configs/smarcfimx6.h b/include/configs/smarcfimx6.h
index 2774ca2..6940377 100644
--- a/include/configs/smarcfimx6.h
+++ b/include/configs/smarcfimx6.h
@@ -40,6 +40,10 @@
 
 #include "smarcfimx6_common.h"
 
+/* PWM Configs */
+#define CONFIG_PWM_IMX
+#define CONFIG_IMX6_PWM_PER_CLK	66000000
+
 /* USB Configs */
 #define CONFIG_CMD_USB
 #define CONFIG_USB_EHCI
-- 
1.9.1