[msm8x60/pmic]: Add LCD brightness control API for PM8058

Adding LCD display brightness control API by configuring PWM in PMIC.

Change-Id: I4d4103eba315cd3d4986032f7355cc9a08d176ac
CRs-Fixed: 287902
diff --git a/platform/msm8x60/include/platform/pmic.h b/platform/msm8x60/include/platform/pmic.h
index 813f290..258b368 100755
--- a/platform/msm8x60/include/platform/pmic.h
+++ b/platform/msm8x60/include/platform/pmic.h
@@ -71,15 +71,11 @@
 #define LDO_TEST_XO_EN_ALL_MASK (0x1F)
 
 /* PMIC 8058 defines */
-#define LPG_CTL_0         (0x13C)
-#define LPG_CTL_1         (0x13D)
-#define LPG_CTL_2         (0x13E)
-#define LPG_CTL_3         (0x13F)
-#define LPG_CTL_4         (0x140)
-#define LPG_CTL_5         (0x141)
-#define LPG_CTL_6         (0x142)
-#define LPG_BANK_SEL      (0x143)
-#define LPG_BANK_ENABLE   (0x144)
+#define PM8058_LPG_CTL_BASE      (0x13C)
+#define PM8058_LPG_CTL(n)        (PM8058_LPG_CTL_BASE + (n))
+#define PM8058_LPG_BANK_SEL      (0x143)
+#define PM8058_LPG_BANK_ENABLE   (0x144)
+
 #define GPIO24_GPIO_CNTRL (0x167)
 #define GPIO25_GPIO_CNTRL (0x168)
 
diff --git a/platform/msm8x60/include/platform/pmic_pwm.h b/platform/msm8x60/include/platform/pmic_pwm.h
new file mode 100644
index 0000000..810790b
--- /dev/null
+++ b/platform/msm8x60/include/platform/pmic_pwm.h
@@ -0,0 +1,152 @@
+/*
+ * * Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials provided
+ *    with the distribution.
+ *  * Neither the name of Code Aurora Forum, Inc. nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __PMIC_PWM_H
+#define __PMIC_PWM_H
+
+#define USEC_PER_SEC		1000000L
+#define NSEC_PER_SEC		1000000000L
+#define NSEC_PER_USEC		1000
+
+#define PWM_FREQ_HZ		300
+#define PWM_LEVEL		15
+
+#define NUM_CLOCKS		3
+#define NUM_PRE_DIVIDE		3
+
+#define NUM_LPG_CTL_REGS		7
+
+#define PRE_DIVIDE_0		2
+#define PRE_DIVIDE_1		3
+#define PRE_DIVIDE_2		5
+
+#define PRE_DIVIDE_MIN		PRE_DIVIDE_0
+#define PRE_DIVIDE_MAX		PRE_DIVIDE_2
+
+#define PM_PWM_M_MIN		0
+#define PM_PWM_M_MAX		7
+
+#define NSEC_1000HZ		(NSEC_PER_SEC / 1000)
+#define NSEC_32768HZ		(NSEC_PER_SEC / 32768)
+#define NSEC_19P2MHZ		(NSEC_PER_SEC / 19200000)
+
+#define CLK_PERIOD_MIN		NSEC_19P2MHZ
+#define CLK_PERIOD_MAX		NSEC_1000HZ
+
+#define MIN_MPT		((PRE_DIVIDE_MIN * CLK_PERIOD_MIN) << PM_PWM_M_MIN)
+#define MAX_MPT		((PRE_DIVIDE_MAX * CLK_PERIOD_MAX) << PM_PWM_M_MAX)
+
+/* The MAX value is computation limit. Hardware limit is 393 seconds. */
+#define PM_PWM_PERIOD_MAX		(274 * USEC_PER_SEC)
+/* The MIN value is hardware limit. */
+#define PM_PWM_PERIOD_MIN		7 /* micro seconds */
+
+#define PWM_PERIOD_USEC		(USEC_PER_SEC / PWM_FREQ_HZ)
+#define PWM_DUTY_LEVEL		(PWM_PERIOD_USEC / PWM_LEVEL)
+
+/* Control 0 */
+#define PM_PWM_1KHZ_COUNT_MASK		0xF0
+#define PM_PWM_1KHZ_COUNT_SHIFT		4
+
+#define PM_PWM_1KHZ_COUNT_MAX		15
+
+#define PM_PWM_OUTPUT_EN		0x08
+#define PM_PWM_PWM_EN		0x04
+#define PM_PWM_RAMP_GEN_EN		0x02
+#define PM_PWM_RAMP_START		0x01
+
+#define PM_PWM_PWM_START		(PM_PWM_OUTPUT_EN \
+				| PM_PWM_PWM_EN)
+#define PM_PWM_RAMP_GEN_START	(PM_PWM_RAMP_GEN_EN \
+				| PM_PWM_RAMP_START)
+
+/* Control 1 */
+#define PM_PWM_REVERSE_EN		0x80
+#define PM_PWM_BYPASS_LUT		0x40
+#define PM_PWM_HIGH_INDEX_MASK		0x3F
+
+/* Control 2 */
+#define PM_PWM_LOOP_EN		0x80
+#define PM_PWM_RAMP_UP		0x40
+#define PM_PWM_LOW_INDEX_MASK		0x3F
+
+/* Control 3 */
+#define PM_PWM_VALUE_BIT7_0		0xFF
+#define PM_PWM_VALUE_BIT5_0		0x3F
+
+/* Control 4 */
+#define PM_PWM_VALUE_BIT8		0x80
+
+#define PM_PWM_CLK_SEL_MASK		0x60
+#define PM_PWM_CLK_SEL_SHIFT		5
+
+#define PM_PWM_CLK_SEL_NO		0
+#define PM_PWM_CLK_SEL_1KHZ		1
+#define PM_PWM_CLK_SEL_32KHZ		2
+#define PM_PWM_CLK_SEL_19P2MHZ		3
+
+#define PM_PWM_PREDIVIDE_MASK		0x18
+#define PM_PWM_PREDIVIDE_SHIFT		3
+
+#define PM_PWM_PREDIVIDE_2		0
+#define PM_PWM_PREDIVIDE_3		1
+#define PM_PWM_PREDIVIDE_5		2
+#define PM_PWM_PREDIVIDE_6		3
+
+#define PM_PWM_M_MASK		0x07
+#define PM_PWM_M_MIN		0
+#define PM_PWM_M_MAX		7
+
+/* Control 5 */
+#define PM_PWM_PAUSE_COUNT_HI_MASK		0xFC
+#define PM_PWM_PAUSE_COUNT_HI_SHIFT		2
+
+#define PM_PWM_PAUSE_ENABLE_HIGH		0x02
+#define PM_PWM_SIZE_9_BIT		0x01
+
+/* Control 6 */
+#define PM_PWM_PAUSE_COUNT_LO_MASK		0xFC
+#define PM_PWM_PAUSE_COUNT_LO_SHIFT		2
+
+#define PM_PWM_PAUSE_ENABLE_LOW		0x02
+#define PM_PWM_RESERVED		0x01
+
+#define PM_PWM_PAUSE_COUNT_MAX		56 /* < 2^6 = 64*/
+
+struct pm_pwm_config {
+	int pwm_size;	/* round up to 6 or 9 for 6/9-bit PWM SIZE */
+	int clk;
+	int pre_div;
+	int pre_div_exp;
+	int pwm_value;
+	int bypass_lut;
+	uint8_t pwm_ctl[NUM_LPG_CTL_REGS];
+};
+
+#endif
diff --git a/platform/msm8x60/panel.c b/platform/msm8x60/panel.c
index 9139030..0809661 100644
--- a/platform/msm8x60/panel.c
+++ b/platform/msm8x60/panel.c
@@ -1,5 +1,5 @@
 /*
- * * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+ * * Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are
@@ -34,6 +34,7 @@
 #include <platform/gpio_hw.h>
 #include <platform/clock.h>
 #include <platform/pmic.h>
+#include <platform/pmic_pwm.h>
 
 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
 
@@ -228,84 +229,84 @@
     gpio_tlmm_config(27, func, dir, pull, drv, enable); /* lcdc_blu0 */
 }
 
-/* Backlight duty cycle init is used to configure the PMIC8058 for
- * PWM output and drive those pins.
- */
-static void bl_duty_cycle_init(void)
+/* API to set backlight level configuring PWM in PM8058 */
+
+int panel_set_backlight(uint8_t bt_level)
 {
-    /* Disable backlight LPG channels before configuring them and dedicated
-       PMIC GPIOs */
-    pm8058_write_one(0x00, LPG_BANK_ENABLE);
+	int rc = -1;
+	uint32_t duty_us, period_us;
 
-    /* Configure PM8058 GPIO24 as a PWM driver (LPG ch0) for chain 1 of 6 LEDs */
-    pm8058_write_one(0x81, GPIO24_GPIO_CNTRL);  /* Write, Bank0, VIN0, Mode
+	if((bt_level <= 0) || (bt_level > 15))
+	{
+		dprintf(CRITICAL, "Error in brightness level (1-15 allowed)\n");
+		goto bail_out;
+	}
+
+	duty_us = bt_level*PWM_DUTY_LEVEL;
+	period_us = PWM_PERIOD_USEC;
+	rc = pm_pwm_config(0, duty_us, period_us);
+	if(rc)
+	{
+		dprintf(CRITICAL, "Error in pwm_config0\n");
+		goto bail_out;
+	}
+
+	duty_us = PWM_PERIOD_USEC - (bt_level*PWM_DUTY_LEVEL);
+	period_us = PWM_PERIOD_USEC;
+	rc = pm_pwm_config(1, duty_us, period_us);
+	if(rc)
+	{
+		dprintf(CRITICAL, "Error in pwm_config1\n");
+		goto bail_out;
+	}
+
+	rc = pm_pwm_enable(0);
+	if(rc)
+	{
+		dprintf(CRITICAL, "Error in pwm_enable0\n");
+		goto bail_out;
+	}
+
+	rc = pm_pwm_enable(1);
+	if(rc)
+		dprintf(CRITICAL, "Error in pwm_enable1\n");
+
+bail_out:
+	return rc;
+}
+
+void bl_gpio_init(void)
+{
+	/* Configure PM8058 GPIO24 as a PWM driver (LPG ch0) for chain 1 of 6 LEDs */
+	pm8058_write_one(0x81, GPIO24_GPIO_CNTRL);  /* Write, Bank0, VIN0, Mode
                                                    selection enabled */
-    pm8058_write_one(0x98, GPIO24_GPIO_CNTRL);  /* Write, Bank1, OutOn/InOff,
+	pm8058_write_one(0x98, GPIO24_GPIO_CNTRL);  /* Write, Bank1, OutOn/InOff,
                                                    CMOS, Don't Invert Output */
-    pm8058_write_one(0xAA, GPIO24_GPIO_CNTRL);  /* Write, Bank2, GPIO no pull */
-    pm8058_write_one(0xB4, GPIO24_GPIO_CNTRL);  /* Write, Bank3, high drv
+	pm8058_write_one(0xAA, GPIO24_GPIO_CNTRL);  /* Write, Bank2, GPIO no pull */
+	pm8058_write_one(0xB4, GPIO24_GPIO_CNTRL);  /* Write, Bank3, high drv
                                                    strength */
-    pm8058_write_one(0xC6, GPIO24_GPIO_CNTRL);  /* Write, Bank4, Src: LPG_DRV1
+	pm8058_write_one(0xC6, GPIO24_GPIO_CNTRL);  /* Write, Bank4, Src: LPG_DRV1
                                                    (Spec. Fnc 2) */
-    pm8058_write_one(0xD8, GPIO24_GPIO_CNTRL);  /* Write, Bank5, Interrupt
+	pm8058_write_one(0xD8, GPIO24_GPIO_CNTRL);  /* Write, Bank5, Interrupt
                                                    polarity noninversion */
 
-    /* Configure PM8058 GPIO25 as a PWM driver (LPG ch1) for chain 2 of 5 LEDs */
-    pm8058_write_one(0x81, GPIO25_GPIO_CNTRL);  /* Write, Bank0, VIN0, Mode
+	/* Configure PM8058 GPIO25 as a PWM driver (LPG ch1) for chain 2 of 5 LEDs */
+	pm8058_write_one(0x81, GPIO25_GPIO_CNTRL);  /* Write, Bank0, VIN0, Mode
                                                    selection enabled */
-    pm8058_write_one(0x98, GPIO25_GPIO_CNTRL);  /* Write, Bank1, OutOn/InOff,
+	pm8058_write_one(0x98, GPIO25_GPIO_CNTRL);  /* Write, Bank1, OutOn/InOff,
                                                    CMOS, Don't Invert Output */
-    pm8058_write_one(0xAA, GPIO25_GPIO_CNTRL);  /* Write, Bank2, GPIO no pull */
-    pm8058_write_one(0xB4, GPIO25_GPIO_CNTRL);  /* Write, Bank3, high drv
+	pm8058_write_one(0xAA, GPIO25_GPIO_CNTRL);  /* Write, Bank2, GPIO no pull */
+	pm8058_write_one(0xB4, GPIO25_GPIO_CNTRL);  /* Write, Bank3, high drv
                                                    strength */
-    pm8058_write_one(0xC6, GPIO25_GPIO_CNTRL);  /* Write, Bank4, Src: LPG_DRV2
+	pm8058_write_one(0xC6, GPIO25_GPIO_CNTRL);  /* Write, Bank4, Src: LPG_DRV2
                                                    (Spec. Fnc 2) */
-    pm8058_write_one(0xD8, GPIO25_GPIO_CNTRL);  /* Write, Bank5, Interrupt
+	pm8058_write_one(0xD8, GPIO25_GPIO_CNTRL);  /* Write, Bank5, Interrupt
                                                    polarity noninversion */
-
-    /* Configure PM8058 LPG channel 0 as non-LUT PWM for PM8058 GPIO24 */
-    pm8058_write_one(0x0, LPG_BANK_SEL);    /* Select LPG ch0 slice of control
-                                               regs */
-    pm8058_write_one(0x00, LPG_CTL_0);  /* Disable PWM, PWM output, and LPG
-                                           ramp generator */
-    pm8058_write_one(0x40, LPG_CTL_1);  /* Dont Toggle, Enable user PWM value,
-                                           no LUT high value idx */
-    pm8058_write_one(0x00, LPG_CTL_2);  /* Dont Loop, no LUT low value index */
-    pm8058_write_one(0xDE, LPG_CTL_3);  /* LS 8 bits of 9-bit PWM user value */
-    pm8058_write_one(0x7F, LPG_CTL_4);  /* MSbit of 9-bit PWM user value,
-                                           19.2MHz, Dev 6, Expo M = 7 */
-    pm8058_write_one(0x01, LPG_CTL_5);  /* PWM = 9bit, disable pause at high
-                                           value LUT index */
-    pm8058_write_one(0x00, LPG_CTL_6);  /* Disable pause at low value LUT index
-                                         */
-    pm8058_write_one(0x0C, LPG_CTL_0);  /* Enable PWM and PWM output, LPG ramp
-                                           generator remains disabled */
-
-    /* Configure PM8058 LPG chan 1 as PWM for PM8058 GPIO25 */
-    pm8058_write_one(0x1, LPG_BANK_SEL);    /* Select LPG ch1 slice of control
-                                               regs */
-    pm8058_write_one(0x00, LPG_CTL_0);  /* Disable PWM, PWM output, and LPG
-                                           ramp generator */
-    pm8058_write_one(0x40, LPG_CTL_1);  /* Dont Toggle, Enable user PWM value,
-                                           no LUT high value idx */
-    pm8058_write_one(0x00, LPG_CTL_2);  /* Dont Loop, no LUT low value index */
-    pm8058_write_one(0x00, LPG_CTL_3);  /* LS 8 bits of 9-bit PWM user value */
-    pm8058_write_one(0x7F, LPG_CTL_4);  /* MSbit of 9-bit PWM user value,
-                                           19.2MHz, Dev 6, Expo M = 7 */
-    pm8058_write_one(0x01, LPG_CTL_5);  /* PWM = 9bit, disable pause at high
-                                           value LUT index */
-    pm8058_write_one(0x00, LPG_CTL_6);  /* Disable pause at low value LUT index
-                                         */
-    pm8058_write_one(0x0C, LPG_CTL_0);  /* Enable PWM and PWM output, LPG ramp
-                                           generator remains disabled */
-
-    /* Enable both LPG channels to enable backlight driver */
-    pm8058_write_one(0x03, LPG_BANK_ENABLE);    /* Enable LPG ch0 (GPIO24) &
-                                                   ch1 (GPIO25) */
 }
 
 void board_lcd_enable(void)
 {
+    int rc = -1;
     dev = qup_i2c_init(GSBI8_BASE, 100000, 24000000);
 
     /* Make sure dev is created and initialized properly */
@@ -344,8 +345,12 @@
     /* Arbitrary delay */
     udelay(20000);
 
-    /* Set the backlight duty cycle via the PM8058 LPG_DRV1 and LPG_DRV2 */
-    bl_duty_cycle_init();
+    /* Set the GPIOs needed for backlight */
+    bl_gpio_init();
+    /* Set backlight level with API (to 8 by default) */
+    rc = panel_set_backlight(8);
+    if(rc)
+        dprintf(CRITICAL,"Error in setting panel backlight\n");
 
     dprintf(INFO, "Enable BACKLIGHT_EN line for output.\n");
     expander_write(GPIO_EXPANDER_REG_DIR_B, ~0x10 & dir_b);
diff --git a/platform/msm8x60/pmic.c b/platform/msm8x60/pmic.c
index 8c71600..6ae9302 100755
--- a/platform/msm8x60/pmic.c
+++ b/platform/msm8x60/pmic.c
@@ -42,8 +42,24 @@
                                   unsigned short);
 extern int pa1_ssbi2_write_bytes(unsigned char *buffer, unsigned short length,
                                  unsigned short slave_addr);
+extern int pa1_ssbi2_read_bytes(unsigned char *buffer, unsigned short length,
+                                 unsigned short slave_addr);
+extern int pa2_ssbi2_write_bytes(unsigned char *buffer, unsigned short length,
+                                 unsigned short slave_addr);
+extern int pa2_ssbi2_read_bytes(unsigned char *buffer, unsigned short length,
+                                unsigned short slave_addr);
 
-/*PM8058*/
+/* PM8058 APIs */
+int pm8058_write(uint16_t addr, uint8_t *data, uint16_t length)
+{
+    return pa1_ssbi2_write_bytes(data, length, addr);
+}
+
+int pm8058_read(uint16_t addr, uint8_t *data, uint16_t length)
+{
+    return pa1_ssbi2_read_bytes(data, length, addr);
+}
+
 void pm8058_write_one(unsigned data, unsigned address)
 {
     pm8058_write_func wr_function = &pa1_ssbi2_write_bytes;
@@ -106,11 +122,8 @@
 	return status;
 }
 
-/*PM8901*/
-extern int pa2_ssbi2_write_bytes(unsigned char *buffer, unsigned short length,
-                                 unsigned short slave_addr);
-extern int pa2_ssbi2_read_bytes(unsigned char *buffer, unsigned short length,
-                                unsigned short slave_addr);
+/* PM8901 APIs */
+
 /*
  * Write to the control registers on PMIC via the SSBI2 interface.
  * Returns : (0) on success and (-1) on error.
diff --git a/platform/msm8x60/pmic_pwm.c b/platform/msm8x60/pmic_pwm.c
new file mode 100644
index 0000000..4847612
--- /dev/null
+++ b/platform/msm8x60/pmic_pwm.c
@@ -0,0 +1,281 @@
+/*
+ * * Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials provided
+ *    with the distribution.
+ *  * Neither the name of Code Aurora Forum, Inc. nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <debug.h>
+#include <platform/pmic.h>
+#include <platform/pmic_pwm.h>
+
+static char *clks[NUM_CLOCKS] = {
+	"1K", "32768", "19.2M"
+};
+
+static unsigned pre_div[NUM_PRE_DIVIDE] = {
+	PRE_DIVIDE_0, PRE_DIVIDE_1, PRE_DIVIDE_2
+};
+
+static unsigned int pt_t[NUM_PRE_DIVIDE][NUM_CLOCKS] = {
+	{	PRE_DIVIDE_0 * NSEC_1000HZ,
+		PRE_DIVIDE_0 * NSEC_32768HZ,
+		PRE_DIVIDE_0 * NSEC_19P2MHZ,
+	},
+	{	PRE_DIVIDE_1 * NSEC_1000HZ,
+		PRE_DIVIDE_1 * NSEC_32768HZ,
+		PRE_DIVIDE_1 * NSEC_19P2MHZ,
+	},
+	{	PRE_DIVIDE_2 * NSEC_1000HZ,
+		PRE_DIVIDE_2 * NSEC_32768HZ,
+		PRE_DIVIDE_2 * NSEC_19P2MHZ,
+	},
+};
+
+static uint16_t duty_msec[PM_PWM_1KHZ_COUNT_MAX + 1] = {
+	0, 1, 2, 3, 4, 6, 8, 16, 18, 24, 32, 36, 64, 128, 256, 512
+};
+
+static uint16_t pause_count[PM_PWM_PAUSE_COUNT_MAX + 1] = {
+	1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+	23, 28, 31, 42, 47, 56, 63, 83, 94, 111, 125, 167, 188, 222, 250, 333,
+	375, 500, 667, 750, 800, 900, 1000, 1100,
+	1200, 1300, 1400, 1500, 1600, 1800, 2000, 2500,
+	3000, 3500, 4000, 4500, 5000, 5500, 6000, 6500,
+	7000
+};
+
+/* Function to get the PWM size, divider, clock for the given period */
+
+static void pm_pwm_calc_period(uint32_t period_us,
+					struct pm_pwm_config *pwm_conf)
+{
+	int n, m, clk, div;
+	int best_m, best_div, best_clk;
+	int last_err, cur_err, better_err, better_m;
+	uint32_t tmp_p, last_p, min_err, period_n;
+
+	/* PWM Period / N : handle underflow or overflow */
+	if (period_us < (PM_PWM_PERIOD_MAX / NSEC_PER_USEC))
+		period_n = (period_us * NSEC_PER_USEC) >> 6;
+	else
+		period_n = (period_us >> 6) * NSEC_PER_USEC;
+	if (period_n >= MAX_MPT) {
+		n = 9;
+		period_n >>= 3;
+	} else
+		n = 6;
+
+	min_err = MAX_MPT;
+	best_m = 0;
+	best_clk = 0;
+	best_div = 0;
+	for (clk = 0; clk < NUM_CLOCKS; clk++) {
+		for (div = 0; div < NUM_PRE_DIVIDE; div++) {
+			tmp_p = period_n;
+			last_p = tmp_p;
+			for (m = 0; m <= PM_PWM_M_MAX; m++) {
+				if (tmp_p <= pt_t[div][clk]) {
+					/* Found local best */
+					if (!m) {
+						better_err = pt_t[div][clk] - tmp_p;
+						better_m = m;
+					} else {
+						last_err = last_p - pt_t[div][clk];
+						cur_err = pt_t[div][clk] - tmp_p;
+
+						if (cur_err < last_err) {
+							better_err = cur_err;
+							better_m = m;
+						} else {
+							better_err = last_err;
+							better_m = m - 1;
+						}
+					}
+
+					if (better_err < min_err) {
+						min_err = better_err;
+						best_m = better_m;
+						best_clk = clk;
+						best_div = div;
+					}
+					break;
+				} else {
+					last_p = tmp_p;
+					tmp_p >>= 1;
+				}
+			}
+		}
+	}
+
+	pwm_conf->pwm_size = n;
+	pwm_conf->clk = best_clk;
+	pwm_conf->pre_div = best_div;
+	pwm_conf->pre_div_exp = best_m;
+}
+
+/* Function to configure PWM control registers with clock, divider values */
+
+static int pm_pwm_configure(uint8_t pwm_id, struct pm_pwm_config *pwm_conf)
+{
+	int i, len, rc = -1;
+	uint8_t reg;
+
+	reg = (pwm_conf->pwm_size > 6) ? PM_PWM_SIZE_9_BIT : 0;
+	pwm_conf->pwm_ctl[5] = reg;
+
+	reg = ((pwm_conf->clk + 1) << PM_PWM_CLK_SEL_SHIFT)
+		& PM_PWM_CLK_SEL_MASK;
+	reg |= (pwm_conf->pre_div << PM_PWM_PREDIVIDE_SHIFT)
+		& PM_PWM_PREDIVIDE_MASK;
+	reg |= pwm_conf->pre_div_exp & PM_PWM_M_MASK;
+	pwm_conf->pwm_ctl[4] = reg;
+
+	/* Just to let know we bypass LUT */
+	if (pwm_conf->bypass_lut) {
+		/* CTL0 is set in pwm_enable() */
+		pwm_conf->pwm_ctl[0] &= PM_PWM_PWM_START;
+		pwm_conf->pwm_ctl[1] = PM_PWM_BYPASS_LUT;
+		pwm_conf->pwm_ctl[2] = 0;
+
+		if (pwm_conf->pwm_size > 6) {
+			pwm_conf->pwm_ctl[3] = pwm_conf->pwm_value
+						& PM_PWM_VALUE_BIT7_0;
+			pwm_conf->pwm_ctl[4] |= (pwm_conf->pwm_value >> 1)
+						& PM_PWM_VALUE_BIT8;
+		} else {
+			pwm_conf->pwm_ctl[3] = pwm_conf->pwm_value
+						& PM_PWM_VALUE_BIT5_0;
+		}
+
+		len = 6;
+	}
+	else
+	{
+		/* Right now, we are not using LUT */
+		goto bail_out;
+	}
+
+	/* Selecting the bank */
+	rc = pm8058_write(PM8058_LPG_BANK_SEL, &pwm_id, 1);
+	if(rc)
+		goto bail_out;
+
+	for (i = 0; i < len; i++) {
+		rc = pm8058_write(PM8058_LPG_CTL(i),&pwm_conf->pwm_ctl[i], 1);
+		if (rc) {
+			dprintf(CRITICAL,"pm8058_write() failed in pwm_configure %d\n", rc);
+			break;
+		}
+	}
+
+bail_out:
+	if(rc)
+		dprintf(CRITICAL,"Error in pm_pwm_configure()\n");
+	return rc;
+}
+
+/* Top level function for configuring PWM */
+
+int pm_pwm_config(uint8_t pwm_id, uint32_t duty_us, uint32_t period_us)
+{
+	struct pm_pwm_config pwm_conf;
+	uint32_t max_pwm_value, tmp;
+	int rc = -1;
+
+	if((duty_us > period_us) || (period_us > PM_PWM_PERIOD_MAX) ||
+		(period_us < PM_PWM_PERIOD_MIN))
+	{
+		dprintf(CRITICAL,"Error in duty cycle and period\n");
+		return -1;
+	}
+
+	pm_pwm_calc_period(period_us, &pwm_conf);
+
+	/* Figure out pwm_value with overflow handling */
+	if (period_us > (1 << pwm_conf.pwm_size)) {
+		tmp = period_us;
+		tmp >>= pwm_conf.pwm_size;
+		pwm_conf.pwm_value = duty_us / tmp;
+	} else {
+		tmp = duty_us;
+		tmp <<= pwm_conf.pwm_size;
+		pwm_conf.pwm_value = tmp / period_us;
+	}
+	max_pwm_value = (1 << pwm_conf.pwm_size) - 1;
+	if (pwm_conf.pwm_value > max_pwm_value)
+		pwm_conf.pwm_value = max_pwm_value;
+
+	/* Bypassing LUT */
+	pwm_conf.bypass_lut = 1;
+
+	dprintf(SPEW,"duty/period=%u/%u usec: pwm_value=%d (of %d)\n",
+		 duty_us, period_us,pwm_conf.pwm_value,
+		 1 << pwm_conf.pwm_size);
+
+	rc = pm_pwm_configure(pwm_id, &pwm_conf);
+	if(rc)
+		dprintf(CRITICAL,"Error in pwm_config()\n");
+
+	return rc;
+}
+
+/* Top level function to enable PWM with specified id */
+
+int pm_pwm_enable(uint8_t pwm_id)
+{
+	int rc = -1;
+	uint8_t reg;
+
+	/* Read it before enabling other bank */
+	rc = pm8058_read(PM8058_LPG_BANK_ENABLE, &reg, 1);
+	if(rc)
+		goto bail_out;
+
+	reg |= (1 << pwm_id);
+
+	rc = pm8058_write(PM8058_LPG_BANK_ENABLE,&reg,1);
+	if(rc)
+		goto bail_out;
+
+	/* Selecting the bank */
+	rc = pm8058_write(PM8058_LPG_BANK_SEL, &pwm_id, 1);
+	if(rc)
+		goto bail_out;
+
+	/* Read it before setting PWM start */
+	rc = pm8058_read(PM8058_LPG_CTL(0), &reg, 1);
+	if(rc)
+		goto bail_out;
+
+	reg |= PM_PWM_PWM_START;
+	reg &= ~PM_PWM_RAMP_GEN_START;
+	rc = pm8058_write(PM8058_LPG_CTL(0),&reg, 1);
+
+bail_out:
+	if(rc)
+		dprintf(CRITICAL, "Error in pwm_enable()\n");
+	return rc;
+}
diff --git a/platform/msm8x60/rules.mk b/platform/msm8x60/rules.mk
index f258110..7076ee8 100644
--- a/platform/msm8x60/rules.mk
+++ b/platform/msm8x60/rules.mk
@@ -27,6 +27,7 @@
 	$(LOCAL_DIR)/gpio.o \
 	$(LOCAL_DIR)/panel.o \
 	$(LOCAL_DIR)/pmic.o \
+	$(LOCAL_DIR)/pmic_pwm.o \
 	$(LOCAL_DIR)/scm-io.o
 
 LINKER_SCRIPT += $(BUILDDIR)/system-onesegment.ld