platform: msm: Add qpnp-pwm driver

Add the qpnp-pwm driver to support LPG/PWM devices in Qualcomm
plug-and-play (QPNP) PMIC chips.  QPNP chips make use of
Qualcomm's SPMI register convention. LPG module provides a superset
of functionality provided by PWM device such as user defined pattern
generation, driving multiple PWM channels etc.

Change-Id: I29a22c6e655612d93cac10e6eea76383d37775e2
Signed-off-by: Jay Chokshi <jchokshi@codeaurora.org>
diff --git a/drivers/platform/msm/qpnp-pwm.c b/drivers/platform/msm/qpnp-pwm.c
new file mode 100644
index 0000000..c9cd0e0
--- /dev/null
+++ b/drivers/platform/msm/qpnp-pwm.c
@@ -0,0 +1,1661 @@
+/* Copyright (c) 2012, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+/*
+ * Qualcomm QPNP Pulse Width Modulation (PWM) driver
+ *
+ * The HW module is also called LPG (Light Pattern Generator).
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/spmi.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/radix-tree.h>
+#include <linux/qpnp/pwm.h>
+
+#define QPNP_LPG_DRIVER_NAME	"qcom,qpnp-pwm"
+
+/* LPG Control for LPG_PATTERN_CONFIG */
+#define QPNP_RAMP_DIRECTION_SHIFT	4
+#define QPNP_RAMP_DIRECTION_MASK	0x10
+#define QPNP_PATTERN_REPEAT_SHIFT	3
+#define QPNP_PATTERN_REPEAT_MASK	0x08
+#define QPNP_RAMP_TOGGLE_SHIFT		2
+#define QPNP_RAMP_TOGGLE_MASK		0x04
+#define QPNP_EN_PAUSE_HI_SHIFT		1
+#define QPNP_EN_PAUSE_HI_MASK		0x02
+#define QPNP_EN_PAUSE_LO_MASK		0x01
+
+/* LPG Control for LPG_PWM_SIZE_CLK */
+#define QPNP_PWM_SIZE_SHIFT			4
+#define QPNP_PWM_SIZE_MASK			0x30
+#define QPNP_PWM_FREQ_CLK_SELECT_SHIFT		0
+#define QPNP_PWM_FREQ_CLK_SELECT_MASK		0x03
+#define QPNP_PWM_SIZE_9_BIT			0x03
+
+#define QPNP_SET_PWM_CLK(val, clk, pwm_size) \
+do { \
+	val = (clk + 1) & QPNP_PWM_FREQ_CLK_SELECT_MASK; \
+	val |= ((pwm_size > 6 ? QPNP_PWM_SIZE_9_BIT : 0) << \
+			QPNP_PWM_SIZE_SHIFT) & QPNP_PWM_SIZE_MASK; \
+} while (0)
+
+#define QPNP_GET_PWM_SIZE(reg) ((reg & QPNP_PWM_SIZE_MASK) \
+					>> QPNP_PWM_SIZE_SHIFT)
+
+/* LPG Control for LPG_PWM_FREQ_PREDIV_CLK */
+#define QPNP_PWM_FREQ_PRE_DIVIDE_SHIFT		5
+#define QPNP_PWM_FREQ_PRE_DIVIDE_MASK		0x60
+#define QPNP_PWM_FREQ_EXP_MASK			0x07
+
+#define QPNP_SET_PWM_FREQ_PREDIV(val, pre_div, pre_div_exp) \
+do { \
+	val = (pre_div << QPNP_PWM_FREQ_PRE_DIVIDE_SHIFT) & \
+				QPNP_PWM_FREQ_PRE_DIVIDE_MASK;	\
+	val |= pre_div_exp & QPNP_PWM_FREQ_EXP_MASK;	\
+} while (0)
+
+/* LPG Control for LPG_PWM_TYPE_CONFIG */
+#define QPNP_EN_GLITCH_REMOVAL_SHIFT		5
+#define QPNP_EN_GLITCH_REMOVAL_MASK		0x20
+#define QPNP_EN_FULL_SCALE_SHIFT		3
+#define QPNP_EN_FULL_SCALE_MASK			0x08
+#define QPNP_EN_PHASE_STAGGER_SHIFT		2
+#define QPNP_EN_PHASE_STAGGER_MASK		0x04
+#define QPNP_PHASE_STAGGER_MASK			0x03
+
+/* LPG Control for PWM_VALUE_LSB */
+#define QPNP_PWM_VALUE_LSB_MASK			0xFF
+
+/* LPG Control for PWM_VALUE_MSB */
+#define QPNP_PWM_VALUE_MSB_SHIFT		8
+#define QPNP_PWM_VALUE_MSB_MASK			0x01
+
+/* LPG Control for ENABLE_CONTROL */
+#define QPNP_EN_PWM_HIGH_SHIFT			7
+#define QPNP_EN_PWM_HIGH_MASK			0x80
+#define QPNP_EN_PWM_LO_SHIFT			6
+#define QPNP_EN_PWM_LO_MASK			0x40
+#define QPNP_EN_PWM_OUTPUT_SHIFT		5
+#define QPNP_EN_PWM_OUTPUT_MASK			0x20
+#define QPNP_PWM_SRC_SELECT_SHIFT		2
+#define QPNP_PWM_SRC_SELECT_MASK		0x04
+#define QPNP_PWM_EN_RAMP_GEN_SHIFT		1
+#define QPNP_PWM_EN_RAMP_GEN_MASK		0x02
+
+#define QPNP_ENABLE_PWM(value) \
+	(value |= (1 << QPNP_EN_PWM_OUTPUT_SHIFT) & QPNP_EN_PWM_OUTPUT_MASK)
+
+#define QPNP_DISABLE_PWM(value)  (value &= ~QPNP_EN_PWM_OUTPUT_MASK)
+
+/* LPG Control for RAMP_CONTROL */
+#define QPNP_RAMP_START_MASK			0x01
+
+#define QPNP_ENABLE_LUT(value) (value |= QPNP_RAMP_START_MASK)
+#define QPNP_DISABLE_LUT(value) (value &= ~QPNP_RAMP_START_MASK)
+
+/* LPG Control for RAMP_STEP_DURATION_LSB */
+#define QPNP_RAMP_STEP_DURATION_LSB_MASK	0xFF
+
+/* LPG Control for RAMP_STEP_DURATION_MSB */
+#define QPNP_RAMP_STEP_DURATION_MSB_SHIFT	8
+#define QPNP_RAMP_STEP_DURATION_MSB_MASK	0x01
+
+#define QPNP_PWM_1KHZ				1024
+#define QPNP_GET_RAMP_STEP_DURATION(ramp_time_ms) \
+		((ramp_time_ms * QPNP_PWM_1KHZ) / 1000)
+
+/* LPG Control for PAUSE_HI_MULTIPLIER_LSB */
+#define QPNP_PAUSE_HI_MULTIPLIER_LSB_MASK	0xFF
+
+/* LPG Control for PAUSE_HI_MULTIPLIER_MSB */
+#define QPNP_PAUSE_HI_MULTIPLIER_MSB_SHIFT	8
+#define QPNP_PAUSE_HI_MULTIPLIER_MSB_MASK	0x1F
+
+/* LPG Control for PAUSE_LO_MULTIPLIER_LSB */
+#define QPNP_PAUSE_LO_MULTIPLIER_LSB_MASK	0xFF
+
+/* LPG Control for PAUSE_LO_MULTIPLIER_MSB */
+#define QPNP_PAUSE_LO_MULTIPLIER_MSB_SHIFT	8
+#define QPNP_PAUSE_LO_MULTIPLIER_MSB_MASK	0x1F
+
+/* LPG Control for HI_INDEX */
+#define QPNP_HI_INDEX_MASK			0x3F
+
+/* LPG Control for LO_INDEX */
+#define QPNP_LO_INDEX_MASK			0x3F
+
+#define NUM_CLOCKS				3
+#define QPNP_PWM_M_MAX				7
+#define NSEC_1024HZ	(NSEC_PER_SEC / 1024)
+#define NSEC_32768HZ	(NSEC_PER_SEC / 32768)
+#define NSEC_19P2MHZ	(NSEC_PER_SEC / 19200000)
+
+#define NUM_LPG_PRE_DIVIDE	4
+
+#define PRE_DIVIDE_1		1
+#define PRE_DIVIDE_3		3
+#define PRE_DIVIDE_5		5
+#define PRE_DIVIDE_6		6
+
+#define SPMI_LPG_REG_ADDR_BASE	0x40
+#define SPMI_LPG_REG_ADDR(b, n)	(b + SPMI_LPG_REG_ADDR_BASE + (n))
+#define SPMI_MAX_BUF_LEN	8
+
+/* SPMI LPG registers */
+enum qpnp_lpg_registers_list {
+	QPNP_LPG_PATTERN_CONFIG,
+	QPNP_LPG_PWM_SIZE_CLK,
+	QPNP_LPG_PWM_FREQ_PREDIV_CLK,
+	QPNP_LPG_PWM_TYPE_CONFIG,
+	QPNP_PWM_VALUE_LSB,
+	QPNP_PWM_VALUE_MSB,
+	QPNP_ENABLE_CONTROL,
+	QPNP_RAMP_CONTROL,
+	QPNP_RAMP_STEP_DURATION_LSB = QPNP_RAMP_CONTROL + 9,
+	QPNP_RAMP_STEP_DURATION_MSB,
+	QPNP_PAUSE_HI_MULTIPLIER_LSB,
+	QPNP_PAUSE_HI_MULTIPLIER_MSB,
+	QPNP_PAUSE_LO_MULTIPLIER_LSB,
+	QPNP_PAUSE_LO_MULTIPLIER_MSB,
+	QPNP_HI_INDEX,
+	QPNP_LO_INDEX,
+	QPNP_TOTAL_LPG_SPMI_REGISTERS
+};
+
+/*
+ * Formula from HSID,
+ * pause_time (hi/lo) = (pause_cnt- 1)*(ramp_ms)
+ * OR,
+ * pause_cnt = (pause_time / ramp_ms) + 1
+ */
+#define QPNP_SET_PAUSE_CNT(to_pause_cnt, from_pause, ramp_ms) \
+	(to_pause_cnt = (from_pause / (ramp_ms ? ramp_ms : 1)) + 1)
+
+
+static unsigned int pt_t[NUM_LPG_PRE_DIVIDE][NUM_CLOCKS] = {
+	{	PRE_DIVIDE_1 * NSEC_1024HZ,
+		PRE_DIVIDE_1 * NSEC_32768HZ,
+		PRE_DIVIDE_1 * NSEC_19P2MHZ,
+	},
+	{	PRE_DIVIDE_3 * NSEC_1024HZ,
+		PRE_DIVIDE_3 * NSEC_32768HZ,
+		PRE_DIVIDE_3 * NSEC_19P2MHZ,
+	},
+	{	PRE_DIVIDE_5 * NSEC_1024HZ,
+		PRE_DIVIDE_5 * NSEC_32768HZ,
+		PRE_DIVIDE_5 * NSEC_19P2MHZ,
+	},
+	{	PRE_DIVIDE_6 * NSEC_1024HZ,
+		PRE_DIVIDE_6 * NSEC_32768HZ,
+		PRE_DIVIDE_6 * NSEC_19P2MHZ,
+	},
+};
+
+static RADIX_TREE(lpg_dev_tree, GFP_KERNEL);
+
+struct qpnp_lut_default_config {
+	u32		*duty_pct_list;
+	int		size;
+	int		start_idx;
+};
+
+struct qpnp_lut_config {
+	struct qpnp_lut_default_config def_config;
+	u8		*duty_pct_list;
+	int		list_size;
+	int		lo_index;
+	int		hi_index;
+	int		lut_pause_hi_cnt;
+	int		lut_pause_lo_cnt;
+	int		ramp_step_ms;
+	bool		ramp_direction;
+	bool		pattern_repeat;
+	bool		ramp_toggle;
+	bool		enable_pause_hi;
+	bool		enable_pause_lo;
+};
+
+struct qpnp_lpg_config {
+	struct qpnp_lut_config	lut_config;
+	u16			base_addr;
+	u16			lut_base_addr;
+	u16			lut_size;
+	bool			bypass_lut;
+	bool			lpg_configured;
+};
+
+struct qpnp_pwm_config {
+	int				channel_id;
+	bool				in_use;
+	const char			*lable;
+	int				pwm_value;
+	int				pwm_period;
+	int				pwm_duty;
+	struct pwm_period_config	period;
+};
+
+/* Public facing structure */
+struct pwm_device {
+	struct qpnp_lpg_chip	*chip;
+	struct qpnp_pwm_config	pwm_config;
+};
+
+struct qpnp_lpg_chip {
+	struct	spmi_device	*spmi_dev;
+	struct	pwm_device	pwm_dev;
+	struct	mutex		lpg_mutex;
+	struct	qpnp_lpg_config	lpg_config;
+	u8	qpnp_lpg_registers[QPNP_TOTAL_LPG_SPMI_REGISTERS];
+};
+
+/* Internal functions */
+static inline void qpnp_set_pattern_config(u8 *val,
+			struct qpnp_lut_config *lut_config)
+{
+	*val = lut_config->enable_pause_lo & QPNP_EN_PAUSE_LO_MASK;
+	*val |= (lut_config->enable_pause_hi << QPNP_EN_PAUSE_HI_SHIFT) &
+						QPNP_EN_PAUSE_HI_MASK;
+	*val |= (lut_config->ramp_toggle << QPNP_RAMP_TOGGLE_SHIFT) &
+						QPNP_RAMP_TOGGLE_MASK;
+	*val |= (lut_config->pattern_repeat << QPNP_PATTERN_REPEAT_SHIFT) &
+						QPNP_PATTERN_REPEAT_MASK;
+	*val |= (lut_config->ramp_direction << QPNP_RAMP_DIRECTION_SHIFT) &
+						QPNP_RAMP_DIRECTION_MASK;
+}
+
+static inline void qpnp_set_pwm_type_config(u8 *val, bool glitch,
+			bool full_scale, bool en_phase, bool phase)
+{
+	*val = phase;
+	*val |= (en_phase << QPNP_EN_PHASE_STAGGER_SHIFT) &
+				QPNP_EN_PHASE_STAGGER_MASK;
+	*val |= (full_scale << QPNP_EN_FULL_SCALE_SHIFT) &
+				QPNP_EN_FULL_SCALE_MASK;
+	*val |= (glitch << QPNP_EN_GLITCH_REMOVAL_SHIFT) &
+				QPNP_EN_GLITCH_REMOVAL_MASK;
+}
+
+static inline void qpnp_set_control(u8 *val, bool pwm_hi, bool pwm_lo,
+			bool pwm_out, bool pwm_src, bool ramp_gen)
+{
+	*val = (ramp_gen << QPNP_PWM_EN_RAMP_GEN_SHIFT) &
+				QPNP_PWM_EN_RAMP_GEN_MASK;
+	*val |= (pwm_src << QPNP_PWM_SRC_SELECT_SHIFT) &
+				QPNP_PWM_SRC_SELECT_MASK;
+	*val |= (pwm_out << QPNP_EN_PWM_OUTPUT_SHIFT) &
+				QPNP_EN_PWM_OUTPUT_MASK;
+	*val |= (pwm_lo << QPNP_EN_PWM_LO_SHIFT) & QPNP_EN_PWM_LO_MASK;
+	*val |= (pwm_hi << QPNP_EN_PWM_HIGH_SHIFT) & QPNP_EN_PWM_HIGH_MASK;
+}
+
+#define QPNP_ENABLE_LUT_CONTROL(p_val)	qpnp_set_control(p_val, 1, 1, 1, 0, 1)
+#define QPNP_ENABLE_PWM_CONTROL(p_val)	qpnp_set_control(p_val, 1, 1, 0, 1, 0)
+
+static inline void qpnp_convert_to_lut_flags(int *flags,
+				struct qpnp_lut_config *l_config)
+{
+	*flags = ((l_config->ramp_direction ? PM_PWM_LUT_RAMP_UP : 0) |
+		(l_config->pattern_repeat ? PM_PWM_LUT_LOOP : 0)|
+		(l_config->ramp_toggle ? PM_PWM_LUT_REVERSE : 0) |
+		(l_config->enable_pause_hi ? PM_PWM_LUT_PAUSE_HI_EN : 0) |
+		(l_config->enable_pause_lo ? PM_PWM_LUT_PAUSE_LO_EN : 0));
+}
+
+static inline void qpnp_set_lut_params(struct lut_params *l_params,
+				struct qpnp_lut_config *l_config)
+{
+	l_params->start_idx = l_config->def_config.start_idx;
+	l_params->idx_len = l_config->def_config.size;
+	l_params->lut_pause_hi = l_config->lut_pause_hi_cnt;
+	l_params->lut_pause_lo = l_config->lut_pause_lo_cnt;
+	l_params->ramp_step_ms = l_config->ramp_step_ms;
+	qpnp_convert_to_lut_flags(&l_params->flags, l_config);
+}
+
+static void qpnp_lpg_save(u8 *u8p, u8 mask, u8 val)
+{
+	*u8p &= ~mask;
+	*u8p |= val & mask;
+}
+
+static int qpnp_lpg_save_and_write(u8 value, u8 mask, u8 *reg, u16 base_addr,
+			u16 offset, u16 size, struct qpnp_lpg_chip *chip)
+{
+	qpnp_lpg_save(reg, mask, value);
+
+	return spmi_ext_register_writel(chip->spmi_dev->ctrl,
+	chip->spmi_dev->sid, SPMI_LPG_REG_ADDR(base_addr, offset), reg, size);
+}
+
+/*
+ * PWM Frequency = Clock Frequency / (N * T)
+ *	or
+ * PWM Period = Clock Period * (N * T)
+ *	where
+ * N = 2^9 or 2^6 for 9-bit or 6-bit PWM size
+ * T = Pre-divide * 2^m, where m = 0..7 (exponent)
+ *
+ * This is the formula to figure out m for the best pre-divide and clock:
+ * (PWM Period / N) = (Pre-divide * Clock Period) * 2^m
+ */
+static void qpnp_lpg_calc_period(unsigned int period_us,
+				   struct pwm_period_config *period)
+{
+	int		n, m, clk, div;
+	int		best_m, best_div, best_clk;
+	unsigned int	last_err, cur_err, min_err;
+	unsigned int	tmp_p, period_n;
+
+	/* PWM Period / N */
+	if (period_us < ((unsigned)(-1) / NSEC_PER_USEC)) {
+		period_n = (period_us * NSEC_PER_USEC) >> 6;
+		n = 6;
+	} else {
+		period_n = (period_us >> 9) * NSEC_PER_USEC;
+		n = 9;
+	}
+
+	min_err = last_err = (unsigned)(-1);
+	best_m = 0;
+	best_clk = 0;
+	best_div = 0;
+	for (clk = 0; clk < NUM_CLOCKS; clk++) {
+		for (div = 0; div < NUM_LPG_PRE_DIVIDE; div++) {
+			/* period_n = (PWM Period / N) */
+			/* tmp_p = (Pre-divide * Clock Period) * 2^m */
+			tmp_p = pt_t[div][clk];
+			for (m = 0; m <= QPNP_PWM_M_MAX; m++) {
+				if (period_n > tmp_p)
+					cur_err = period_n - tmp_p;
+				else
+					cur_err = tmp_p - period_n;
+
+				if (cur_err < min_err) {
+					min_err = cur_err;
+					best_m = m;
+					best_clk = clk;
+					best_div = div;
+				}
+
+				if (m && cur_err > last_err)
+					/* Break for bigger cur_err */
+					break;
+
+				last_err = cur_err;
+				tmp_p <<= 1;
+			}
+		}
+	}
+
+	/* Use higher resolution */
+	if (best_m >= 3 && n == 6) {
+		n += 3;
+		best_m -= 3;
+	}
+
+	period->pwm_size = n;
+	period->clk = best_clk;
+	period->pre_div = best_div;
+	period->pre_div_exp = best_m;
+}
+
+static void qpnp_lpg_calc_pwm_value(struct pwm_device *pwm,
+				      unsigned int period_us,
+				      unsigned int duty_us)
+{
+	unsigned int		max_pwm_value, tmp;
+	struct qpnp_pwm_config	*pwm_config = &pwm->pwm_config;
+
+	/* Figure out pwm_value with overflow handling */
+	tmp = 1 << (sizeof(tmp) * 8 - pwm_config->period.pwm_size);
+	if (duty_us < tmp) {
+		tmp = duty_us << pwm_config->period.pwm_size;
+		pwm_config->pwm_value = tmp / period_us;
+	} else {
+		tmp = period_us >> pwm_config->period.pwm_size;
+		pwm_config->pwm_value = duty_us / tmp;
+	}
+	max_pwm_value = (1 << pwm_config->period.pwm_size) - 1;
+	if (pwm_config->pwm_value > max_pwm_value)
+		pwm_config->pwm_value = max_pwm_value;
+}
+
+static int qpnp_lpg_change_table(struct pwm_device *pwm,
+					int duty_pct[], int raw_value)
+{
+	unsigned int		pwm_value, max_pwm_value;
+	struct qpnp_lpg_chip	*chip = pwm->chip;
+	struct qpnp_lut_config	*lut = &chip->lpg_config.lut_config;
+	int			i, pwm_size, rc;
+	int			burst_size = SPMI_MAX_BUF_LEN;
+	int			list_len = lut->list_size << 1;
+	int			offset = lut->lo_index << 2;
+
+	pwm_size = QPNP_GET_PWM_SIZE(
+			chip->qpnp_lpg_registers[QPNP_LPG_PWM_SIZE_CLK]) &
+						QPNP_PWM_SIZE_9_BIT ? 9 : 6;
+
+	max_pwm_value = (1 << pwm_size) - 1;
+
+	if (unlikely(lut->list_size != (lut->hi_index - lut->lo_index + 1))) {
+		pr_err("LUT internal Data structure corruption detected\n");
+		pr_err("LUT list size: %d\n", lut->list_size);
+		pr_err("However, index size is: %d\n",
+				(lut->hi_index - lut->lo_index + 1));
+		return -EINVAL;
+	}
+
+	for (i = 0; i <= lut->list_size; i++) {
+		if (raw_value)
+			pwm_value = duty_pct[i];
+		else
+			pwm_value = (duty_pct[i] << pwm_size) / 100;
+
+		if (pwm_value > max_pwm_value)
+			pwm_value = max_pwm_value;
+
+		lut->duty_pct_list[i*2] = pwm_value;
+		lut->duty_pct_list[(i*2)+1] = (pwm_value >>
+			 QPNP_PWM_VALUE_MSB_SHIFT) & QPNP_PWM_VALUE_MSB_MASK;
+	}
+
+	/* Write with max allowable burst mode, each entry is of two bytes */
+	for (i = 0; i < list_len;) {
+		if (i + burst_size >= list_len)
+			burst_size = list_len - i;
+		rc = spmi_ext_register_writel(chip->spmi_dev->ctrl,
+			chip->spmi_dev->sid,
+			chip->lpg_config.lut_base_addr + offset + i,
+			lut->duty_pct_list + i, burst_size);
+		i += burst_size;
+	}
+
+	return rc;
+}
+
+static void qpnp_lpg_save_period(struct pwm_device *pwm)
+{
+	u8 mask, val;
+	struct qpnp_lpg_chip	*chip = pwm->chip;
+	struct qpnp_pwm_config	*pwm_config = &pwm->pwm_config;
+
+	QPNP_SET_PWM_CLK(val, pwm_config->period.clk,
+				pwm_config->period.pwm_size);
+
+	mask = QPNP_PWM_SIZE_MASK | QPNP_PWM_FREQ_CLK_SELECT_MASK;
+
+	qpnp_lpg_save(&chip->qpnp_lpg_registers[QPNP_LPG_PWM_SIZE_CLK],
+							mask, val);
+
+	QPNP_SET_PWM_FREQ_PREDIV(val, pwm_config->period.pre_div,
+					pwm_config->period.pre_div_exp);
+
+	mask = QPNP_PWM_FREQ_PRE_DIVIDE_MASK | QPNP_PWM_FREQ_EXP_MASK;
+
+	qpnp_lpg_save(&chip->qpnp_lpg_registers[QPNP_LPG_PWM_FREQ_PREDIV_CLK],
+								mask, val);
+}
+
+static int qpnp_lpg_save_pwm_value(struct pwm_device *pwm)
+{
+	unsigned int		max_pwm_value;
+	int			pwm_size;
+	u8			mask, value;
+	struct qpnp_lpg_chip	*chip = pwm->chip;
+	struct qpnp_pwm_config	*pwm_config = &pwm->pwm_config;
+	struct qpnp_lpg_config	*lpg_config = &chip->lpg_config;
+	int rc;
+
+	pwm_size = QPNP_GET_PWM_SIZE(
+			chip->qpnp_lpg_registers[QPNP_LPG_PWM_SIZE_CLK]) &
+						QPNP_PWM_SIZE_9_BIT ? 9 : 6;
+
+	max_pwm_value = (1 << pwm_size) - 1;
+
+	if (pwm_config->pwm_value > max_pwm_value)
+		pwm_config->pwm_value = max_pwm_value;
+
+	value = pwm_config->pwm_value;
+	mask = QPNP_PWM_VALUE_LSB_MASK;
+
+	rc = qpnp_lpg_save_and_write(value, mask,
+			&pwm->chip->qpnp_lpg_registers[QPNP_PWM_VALUE_LSB],
+			lpg_config->base_addr, QPNP_PWM_VALUE_LSB, 1, chip);
+	if (rc)
+		return rc;
+
+	value = (pwm_config->pwm_value >> QPNP_PWM_VALUE_MSB_SHIFT) &
+					QPNP_PWM_VALUE_MSB_MASK;
+
+	mask = QPNP_PWM_VALUE_MSB_MASK;
+
+	return qpnp_lpg_save_and_write(value, mask,
+			&pwm->chip->qpnp_lpg_registers[QPNP_PWM_VALUE_MSB],
+			lpg_config->base_addr, QPNP_PWM_VALUE_MSB, 1, chip);
+}
+
+static int qpnp_lpg_configure_pattern(struct pwm_device *pwm)
+{
+	struct qpnp_lpg_config	*lpg_config = &pwm->chip->lpg_config;
+	struct qpnp_lut_config	*lut_config = &lpg_config->lut_config;
+	struct qpnp_lpg_chip	*chip = pwm->chip;
+	u8			value, mask;
+
+	qpnp_set_pattern_config(&value, lut_config);
+
+	mask = QPNP_RAMP_DIRECTION_MASK | QPNP_PATTERN_REPEAT_MASK |
+			QPNP_RAMP_TOGGLE_MASK | QPNP_EN_PAUSE_HI_MASK |
+			QPNP_EN_PAUSE_LO_MASK;
+
+	return qpnp_lpg_save_and_write(value, mask,
+		&pwm->chip->qpnp_lpg_registers[QPNP_LPG_PATTERN_CONFIG],
+		lpg_config->base_addr, QPNP_LPG_PATTERN_CONFIG, 1, chip);
+}
+
+static int qpnp_lpg_configure_pwm(struct pwm_device *pwm)
+{
+	struct qpnp_lpg_config	*lpg_config = &pwm->chip->lpg_config;
+	struct qpnp_lpg_chip	*chip = pwm->chip;
+	int			rc;
+	u8			value, mask;
+
+	rc = spmi_ext_register_writel(chip->spmi_dev->ctrl, chip->spmi_dev->sid,
+		SPMI_LPG_REG_ADDR(lpg_config->base_addr, QPNP_LPG_PWM_SIZE_CLK),
+		&chip->qpnp_lpg_registers[QPNP_LPG_PWM_SIZE_CLK], 1);
+
+	if (rc)
+		return rc;
+
+	rc = spmi_ext_register_writel(chip->spmi_dev->ctrl, chip->spmi_dev->sid,
+		SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+		QPNP_LPG_PWM_FREQ_PREDIV_CLK),
+		&chip->qpnp_lpg_registers[QPNP_LPG_PWM_FREQ_PREDIV_CLK], 1);
+	if (rc)
+		return rc;
+
+	qpnp_set_pwm_type_config(&value, 1, 0, 0, 0);
+
+	mask = QPNP_EN_GLITCH_REMOVAL_MASK | QPNP_EN_FULL_SCALE_MASK |
+			QPNP_EN_PHASE_STAGGER_MASK | QPNP_PHASE_STAGGER_MASK;
+
+	return qpnp_lpg_save_and_write(value, mask,
+		&pwm->chip->qpnp_lpg_registers[QPNP_LPG_PWM_TYPE_CONFIG],
+		lpg_config->base_addr, QPNP_LPG_PWM_TYPE_CONFIG, 1, chip);
+}
+
+static int qpnp_pwm_configure_control(struct pwm_device *pwm)
+{
+	struct qpnp_lpg_config	*lpg_config = &pwm->chip->lpg_config;
+	struct qpnp_lpg_chip	*chip = pwm->chip;
+	u8			value, mask;
+
+	QPNP_ENABLE_PWM_CONTROL(&value);
+
+	mask = QPNP_EN_PWM_HIGH_MASK | QPNP_EN_PWM_LO_MASK |
+		QPNP_EN_PWM_OUTPUT_MASK | QPNP_PWM_SRC_SELECT_MASK |
+					QPNP_PWM_EN_RAMP_GEN_MASK;
+
+	return qpnp_lpg_save_and_write(value, mask,
+		&pwm->chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL],
+		lpg_config->base_addr, QPNP_ENABLE_CONTROL, 1, chip);
+
+}
+
+static int qpnp_lpg_configure_control(struct pwm_device *pwm)
+{
+	struct qpnp_lpg_config	*lpg_config = &pwm->chip->lpg_config;
+	struct qpnp_lpg_chip	*chip = pwm->chip;
+	u8			value, mask;
+
+	QPNP_ENABLE_LUT_CONTROL(&value);
+
+	mask = QPNP_EN_PWM_HIGH_MASK | QPNP_EN_PWM_LO_MASK |
+		QPNP_EN_PWM_OUTPUT_MASK | QPNP_PWM_SRC_SELECT_MASK |
+				QPNP_PWM_EN_RAMP_GEN_MASK;
+
+	return qpnp_lpg_save_and_write(value, mask,
+		&pwm->chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL],
+		lpg_config->base_addr, QPNP_ENABLE_CONTROL, 1, chip);
+
+}
+
+static int qpnp_lpg_configure_ramp_step_duration(struct pwm_device *pwm)
+{
+	struct qpnp_lpg_config	*lpg_config = &pwm->chip->lpg_config;
+	struct qpnp_lut_config	lut_config = lpg_config->lut_config;
+	struct qpnp_lpg_chip	*chip = pwm->chip;
+	int			rc, value;
+	u8			val, mask;
+
+	value = QPNP_GET_RAMP_STEP_DURATION(lut_config.ramp_step_ms);
+	val = value & QPNP_RAMP_STEP_DURATION_LSB_MASK;
+	mask = QPNP_RAMP_STEP_DURATION_LSB_MASK;
+
+	rc = qpnp_lpg_save_and_write(val, mask,
+		&pwm->chip->qpnp_lpg_registers[QPNP_RAMP_STEP_DURATION_LSB],
+		lpg_config->base_addr, QPNP_RAMP_STEP_DURATION_LSB, 1, chip);
+	if (rc)
+		return rc;
+
+	val = (value >> QPNP_RAMP_STEP_DURATION_MSB_SHIFT) &
+				QPNP_RAMP_STEP_DURATION_MSB_MASK;
+
+	mask = QPNP_RAMP_STEP_DURATION_MSB_MASK;
+
+	return qpnp_lpg_save_and_write(val, mask,
+		&pwm->chip->qpnp_lpg_registers[QPNP_RAMP_STEP_DURATION_MSB],
+		lpg_config->base_addr, QPNP_RAMP_STEP_DURATION_MSB, 1, chip);
+}
+
+static int qpnp_lpg_configure_pause(struct pwm_device *pwm)
+{
+	struct qpnp_lpg_config	*lpg_config = &pwm->chip->lpg_config;
+	struct qpnp_lut_config	lut_config = lpg_config->lut_config;
+	struct qpnp_lpg_chip	*chip = pwm->chip;
+	u8			value, mask;
+	int			rc = 0;
+
+	if (lut_config.enable_pause_hi) {
+		value = lut_config.lut_pause_hi_cnt;
+		mask = QPNP_PAUSE_HI_MULTIPLIER_LSB_MASK;
+
+		rc = qpnp_lpg_save_and_write(value, mask,
+		&pwm->chip->qpnp_lpg_registers[QPNP_PAUSE_HI_MULTIPLIER_LSB],
+		lpg_config->base_addr, QPNP_PAUSE_HI_MULTIPLIER_LSB, 1, chip);
+		if (rc)
+			return rc;
+
+		value = (lut_config.lut_pause_hi_cnt >>
+			QPNP_PAUSE_HI_MULTIPLIER_MSB_SHIFT) &
+					QPNP_PAUSE_HI_MULTIPLIER_MSB_MASK;
+
+		mask = QPNP_PAUSE_HI_MULTIPLIER_MSB_MASK;
+
+		rc = qpnp_lpg_save_and_write(value, mask,
+		&pwm->chip->qpnp_lpg_registers[QPNP_PAUSE_HI_MULTIPLIER_MSB],
+		lpg_config->base_addr, QPNP_PAUSE_HI_MULTIPLIER_MSB, 1, chip);
+	} else {
+		value = 0;
+		mask = QPNP_PAUSE_HI_MULTIPLIER_LSB_MASK;
+
+		rc = qpnp_lpg_save_and_write(value, mask,
+		&pwm->chip->qpnp_lpg_registers[QPNP_PAUSE_HI_MULTIPLIER_LSB],
+		lpg_config->base_addr, QPNP_PAUSE_HI_MULTIPLIER_LSB, 1, chip);
+		if (rc)
+			return rc;
+
+		mask = QPNP_PAUSE_HI_MULTIPLIER_MSB_MASK;
+
+		rc = qpnp_lpg_save_and_write(value, mask,
+		&pwm->chip->qpnp_lpg_registers[QPNP_PAUSE_HI_MULTIPLIER_MSB],
+		lpg_config->base_addr, QPNP_PAUSE_HI_MULTIPLIER_MSB, 1, chip);
+		if (rc)
+			return rc;
+
+	}
+
+	if (lut_config.enable_pause_lo) {
+		value = lut_config.lut_pause_lo_cnt;
+		mask = QPNP_PAUSE_LO_MULTIPLIER_LSB_MASK;
+
+		rc = qpnp_lpg_save_and_write(value, mask,
+		&pwm->chip->qpnp_lpg_registers[QPNP_PAUSE_LO_MULTIPLIER_LSB],
+		lpg_config->base_addr, QPNP_PAUSE_LO_MULTIPLIER_LSB, 1, chip);
+		if (rc)
+			return rc;
+
+		value = (lut_config.lut_pause_lo_cnt >>
+				QPNP_PAUSE_LO_MULTIPLIER_MSB_SHIFT) &
+					QPNP_PAUSE_LO_MULTIPLIER_MSB_MASK;
+
+		mask = QPNP_PAUSE_LO_MULTIPLIER_MSB_MASK;
+
+		rc = qpnp_lpg_save_and_write(value, mask,
+		&pwm->chip->qpnp_lpg_registers[QPNP_PAUSE_LO_MULTIPLIER_MSB],
+		lpg_config->base_addr, QPNP_PAUSE_LO_MULTIPLIER_MSB, 1, chip);
+	} else {
+		value = 0;
+		mask = QPNP_PAUSE_LO_MULTIPLIER_LSB_MASK;
+
+		rc = qpnp_lpg_save_and_write(value, mask,
+		&pwm->chip->qpnp_lpg_registers[QPNP_PAUSE_LO_MULTIPLIER_LSB],
+		lpg_config->base_addr, QPNP_PAUSE_LO_MULTIPLIER_LSB, 1, chip);
+		if (rc)
+			return rc;
+
+		mask = QPNP_PAUSE_LO_MULTIPLIER_MSB_MASK;
+
+		rc = qpnp_lpg_save_and_write(value, mask,
+		&pwm->chip->qpnp_lpg_registers[QPNP_PAUSE_LO_MULTIPLIER_MSB],
+		lpg_config->base_addr, QPNP_PAUSE_LO_MULTIPLIER_MSB, 1, chip);
+		return rc;
+	}
+
+	return rc;
+}
+
+static int qpnp_lpg_configure_index(struct pwm_device *pwm)
+{
+	struct qpnp_lpg_config	*lpg_config = &pwm->chip->lpg_config;
+	struct qpnp_lut_config	lut_config = lpg_config->lut_config;
+	struct qpnp_lpg_chip	*chip = pwm->chip;
+	u8			value, mask;
+	int			rc = 0;
+
+	value = lut_config.hi_index;
+	mask = QPNP_HI_INDEX_MASK;
+
+	rc = qpnp_lpg_save_and_write(value, mask,
+		&pwm->chip->qpnp_lpg_registers[QPNP_HI_INDEX],
+		lpg_config->base_addr, QPNP_HI_INDEX, 1, chip);
+	if (rc)
+		return rc;
+
+	value = lut_config.lo_index;
+	mask = QPNP_LO_INDEX_MASK;
+
+	rc = qpnp_lpg_save_and_write(value, mask,
+		&pwm->chip->qpnp_lpg_registers[QPNP_LO_INDEX],
+		lpg_config->base_addr, QPNP_LO_INDEX, 1, chip);
+
+	return rc;
+}
+
+static int qpnp_lpg_change_lut(struct pwm_device *pwm)
+{
+	int	rc;
+
+	rc = qpnp_lpg_configure_pattern(pwm);
+	if (rc) {
+		pr_err("Failed to configure LUT pattern");
+		return rc;
+	}
+	rc = qpnp_lpg_configure_pwm(pwm);
+	if (rc) {
+		pr_err("Failed to configure LUT pattern");
+		return rc;
+	}
+	rc = qpnp_lpg_configure_control(pwm);
+	if (rc) {
+		pr_err("Failed to configure pause registers");
+		return rc;
+	}
+	rc = qpnp_lpg_configure_ramp_step_duration(pwm);
+	if (rc) {
+		pr_err("Failed to configure duty time");
+		return rc;
+	}
+	rc = qpnp_lpg_configure_pause(pwm);
+	if (rc) {
+		pr_err("Failed to configure pause registers");
+		return rc;
+	}
+	rc = qpnp_lpg_configure_index(pwm);
+	if (rc) {
+		pr_err("Failed to configure index registers");
+		return rc;
+	}
+	return rc;
+}
+
+static int qpnp_lpg_enable_lut(struct pwm_device *pwm)
+{
+	struct qpnp_lpg_config	*lpg_config = &pwm->chip->lpg_config;
+	struct qpnp_lpg_chip	*chip = pwm->chip;
+	u8			value, mask;
+
+	value = pwm->chip->qpnp_lpg_registers[QPNP_RAMP_CONTROL];
+
+	QPNP_ENABLE_LUT(value);
+
+	mask = QPNP_RAMP_START_MASK;
+
+	return qpnp_lpg_save_and_write(value, mask,
+		&pwm->chip->qpnp_lpg_registers[QPNP_RAMP_CONTROL],
+		lpg_config->base_addr, QPNP_RAMP_CONTROL, 1, chip);
+}
+
+static int qpnp_lpg_disable_lut(struct pwm_device *pwm)
+{
+	struct qpnp_lpg_config	*lpg_config = &pwm->chip->lpg_config;
+	struct qpnp_lpg_chip	*chip = pwm->chip;
+	u8			value, mask;
+
+	value = pwm->chip->qpnp_lpg_registers[QPNP_RAMP_CONTROL];
+
+	QPNP_DISABLE_LUT(value);
+
+	mask = QPNP_RAMP_START_MASK;
+
+	return qpnp_lpg_save_and_write(value, mask,
+		&pwm->chip->qpnp_lpg_registers[QPNP_RAMP_CONTROL],
+		lpg_config->base_addr, QPNP_RAMP_CONTROL, 1, chip);
+}
+
+static int qpnp_lpg_enable_pwm(struct pwm_device *pwm)
+{
+	struct qpnp_lpg_config	*lpg_config = &pwm->chip->lpg_config;
+	struct qpnp_lpg_chip	*chip = pwm->chip;
+	u8			value, mask;
+
+	value = pwm->chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL];
+
+	QPNP_ENABLE_PWM(value);
+
+	mask = QPNP_EN_PWM_OUTPUT_MASK;
+
+	return qpnp_lpg_save_and_write(value, mask,
+		&pwm->chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL],
+		lpg_config->base_addr, QPNP_RAMP_CONTROL, 1, chip);
+}
+
+static int qpnp_lpg_disable_pwm(struct pwm_device *pwm)
+{
+	struct qpnp_lpg_config	*lpg_config = &pwm->chip->lpg_config;
+	struct qpnp_lpg_chip	*chip = pwm->chip;
+	u8			value, mask;
+
+	value = pwm->chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL];
+
+	QPNP_DISABLE_PWM(value);
+
+	mask = QPNP_EN_PWM_OUTPUT_MASK;
+
+	return qpnp_lpg_save_and_write(value, mask,
+		&pwm->chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL],
+		lpg_config->base_addr, QPNP_RAMP_CONTROL, 1, chip);
+}
+
+static int _pwm_config(struct pwm_device *pwm, int duty_us, int period_us)
+{
+	struct qpnp_pwm_config		*pwm_config;
+	struct qpnp_lpg_chip		*chip;
+	struct pwm_period_config	*period;
+	int				rc;
+
+	chip = pwm->chip;
+	pwm_config = &pwm->pwm_config;
+	period = &pwm_config->period;
+
+	if (pwm_config->pwm_period != period_us) {
+		qpnp_lpg_calc_period(period_us, period);
+		qpnp_lpg_save_period(pwm);
+		pwm_config->pwm_period = period_us;
+	}
+
+	pwm_config->pwm_duty = duty_us;
+	qpnp_lpg_calc_pwm_value(pwm, period_us, duty_us);
+	rc = qpnp_lpg_save_pwm_value(pwm);
+
+	if (rc) {
+		pr_err("Could not update PWM value for channel %d rc=%d\n",
+						pwm_config->channel_id, rc);
+		return rc;
+	}
+
+	rc = qpnp_lpg_configure_pwm(pwm);
+	if (rc) {
+		pr_err("Could not configure PWM clock for\n");
+		pr_err("channel %d rc=%d\n", pwm_config->channel_id, rc);
+		return rc;
+	}
+
+	rc = qpnp_pwm_configure_control(pwm);
+	if (rc) {
+		pr_err("Could not update PWM control for");
+		pr_err("channel %d rc=%d\n", pwm_config->channel_id, rc);
+		return rc;
+	}
+
+	pwm->chip->lpg_config.lpg_configured = 1;
+
+	pr_debug("duty/period=%u/%u usec: pwm_value=%d (of %d)\n",
+		 (unsigned)duty_us, (unsigned)period_us,
+		 pwm_config->pwm_value, 1 << period->pwm_size);
+
+	return 0;
+}
+
+static int _pwm_lut_config(struct pwm_device *pwm, int period_us,
+			int duty_pct[], struct lut_params lut_params)
+{
+	struct qpnp_lpg_config		*lpg_config;
+	struct qpnp_lut_config		*lut_config;
+	struct qpnp_lut_default_config  *def_lut_config =
+					&lut_config->def_config;
+	struct pwm_period_config	*period;
+	struct qpnp_pwm_config		*pwm_config;
+	int				start_idx = lut_params.start_idx;
+	int				len = lut_params.idx_len;
+	int				flags = lut_params.flags;
+	int				raw_lut, ramp_step_ms;
+	int				rc = 0;
+
+	pwm_config = &pwm->pwm_config;
+	lpg_config = &pwm->chip->lpg_config;
+	lut_config = &lpg_config->lut_config;
+	def_lut_config = &lut_config->def_config;
+
+	if ((start_idx + len) > lpg_config->lut_size) {
+		pr_err("Exceed LUT limit\n");
+		return -EINVAL;
+	}
+	if ((unsigned)period_us > PM_PWM_PERIOD_MAX ||
+		(unsigned)period_us < PM_PWM_PERIOD_MIN) {
+		pr_err("Period out of range\n");
+		return -EINVAL;
+	}
+
+	if (!pwm_config->in_use) {
+		pr_err("channel_id: %d: stale handle?\n",
+				pwm_config->channel_id);
+		return -EINVAL;
+	}
+
+	period = &pwm_config->period;
+
+	if (pwm_config->pwm_period != period_us) {
+		qpnp_lpg_calc_period(period_us, period);
+		qpnp_lpg_save_period(pwm);
+		pwm_config->pwm_period = period_us;
+	}
+
+	if (flags & PM_PWM_LUT_NO_TABLE)
+		goto after_table_write;
+
+	raw_lut = 0;
+	if (flags & PM_PWM_LUT_USE_RAW_VALUE)
+		raw_lut = 1;
+
+	lut_config->list_size = len;
+	lut_config->lo_index = start_idx;
+	lut_config->hi_index = start_idx + len - 1;
+
+	/*
+	 * LUT may not be specified in device tree by default.
+	 * This is the first time user is configuring it.
+	 */
+	if (lpg_config->bypass_lut) {
+		def_lut_config->duty_pct_list = kzalloc(sizeof(u32) *
+							len, GFP_KERNEL);
+		if (!def_lut_config->duty_pct_list) {
+			pr_err("kzalloc failed on def_duty_pct_list\n");
+			return -ENOMEM;
+		}
+
+		lut_config->duty_pct_list = kzalloc(lpg_config->lut_size *
+						sizeof(u16), GFP_KERNEL);
+		if (!lut_config->duty_pct_list) {
+			pr_err("kzalloc failed on duty_pct_list\n");
+			kfree(def_lut_config->duty_pct_list);
+			return -ENOMEM;
+		}
+
+		def_lut_config->size = len;
+		def_lut_config->start_idx = start_idx;
+		memcpy(def_lut_config->duty_pct_list, duty_pct, len);
+
+		lpg_config->bypass_lut = 0;
+	}
+
+	rc = qpnp_lpg_change_table(pwm, duty_pct, raw_lut);
+	if (rc) {
+		pr_err("qpnp_lpg_change_table: rc=%d\n", rc);
+		return -EINVAL;
+	}
+
+after_table_write:
+	ramp_step_ms = lut_params.ramp_step_ms;
+
+	if (ramp_step_ms > PM_PWM_LUT_RAMP_STEP_TIME_MAX)
+		ramp_step_ms = PM_PWM_LUT_RAMP_STEP_TIME_MAX;
+
+	QPNP_SET_PAUSE_CNT(lut_config->lut_pause_lo_cnt,
+			lut_params.lut_pause_lo, ramp_step_ms);
+	if (lut_config->lut_pause_lo_cnt > PM_PWM_LUT_PAUSE_MAX)
+		lut_config->lut_pause_lo_cnt = PM_PWM_LUT_PAUSE_MAX;
+
+	QPNP_SET_PAUSE_CNT(lut_config->lut_pause_hi_cnt,
+			lut_params.lut_pause_hi, ramp_step_ms);
+	if (lut_config->lut_pause_hi_cnt > PM_PWM_LUT_PAUSE_MAX)
+			lut_config->lut_pause_hi_cnt = PM_PWM_LUT_PAUSE_MAX;
+
+	lut_config->ramp_step_ms = ramp_step_ms;
+
+	lut_config->ramp_direction  = !!(flags & PM_PWM_LUT_RAMP_UP);
+	lut_config->pattern_repeat  = !!(flags & PM_PWM_LUT_LOOP);
+	lut_config->ramp_toggle	    = !!(flags & PM_PWM_LUT_REVERSE);
+	lut_config->enable_pause_hi = !!(flags & PM_PWM_LUT_PAUSE_HI_EN);
+	lut_config->enable_pause_lo = !!(flags & PM_PWM_LUT_PAUSE_LO_EN);
+	lpg_config->bypass_lut = 0;
+
+	rc = qpnp_lpg_change_lut(pwm);
+
+	if (!rc)
+		lpg_config->lpg_configured = 1;
+
+	return rc;
+}
+
+/* APIs */
+/**
+ * pwm_request - request a PWM device
+ * @channel_id: PWM id or channel
+ * @lable: the label to identify the user
+ */
+struct pwm_device *pwm_request(int pwm_id, const char *lable)
+{
+	struct qpnp_lpg_chip	*chip;
+	struct pwm_device	*pwm;
+
+	chip = radix_tree_lookup(&lpg_dev_tree, pwm_id);
+
+	if (!chip) {
+		pr_err("Could not find PWM Device for the\n");
+		pr_err("input pwm channel %d\n", pwm_id);
+		return ERR_PTR(-EINVAL);
+	}
+
+	mutex_lock(&chip->lpg_mutex);
+
+	pwm = &chip->pwm_dev;
+
+	if (pwm->pwm_config.in_use) {
+		pr_err("PWM device associated with the");
+		pr_err("input pwm id: %d is in use by %s",
+			pwm_id, pwm->pwm_config.lable);
+		pwm = ERR_PTR(-EBUSY);
+	} else {
+		pwm->pwm_config.in_use = 1;
+		pwm->pwm_config.lable  = lable;
+	}
+
+	mutex_unlock(&chip->lpg_mutex);
+
+	return pwm;
+}
+EXPORT_SYMBOL_GPL(pwm_request);
+
+/**
+ * pwm_free - free a PWM device
+ * @pwm: the PWM device
+ */
+void pwm_free(struct pwm_device *pwm)
+{
+	struct qpnp_pwm_config	*pwm_config;
+
+	if (pwm == NULL || IS_ERR(pwm) || pwm->chip == NULL) {
+		pr_err("Invalid pwm handle or no pwm_chip\n");
+		return;
+	}
+
+	mutex_lock(&pwm->chip->lpg_mutex);
+
+	pwm_config = &pwm->pwm_config;
+
+	if (pwm_config->in_use) {
+		qpnp_lpg_disable_pwm(pwm);
+		qpnp_lpg_disable_lut(pwm);
+		pwm_config->in_use = 0;
+		pwm_config->lable = NULL;
+		pwm->chip->lpg_config.lpg_configured = 0;
+	}
+
+	mutex_unlock(&pwm->chip->lpg_mutex);
+}
+EXPORT_SYMBOL_GPL(pwm_free);
+
+/**
+ * pwm_config - change a PWM device configuration
+ * @pwm: the PWM device
+ * @period_us: period in microseconds
+ * @duty_us: duty cycle in microseconds
+ */
+int pwm_config(struct pwm_device *pwm, int duty_us, int period_us)
+{
+	int rc;
+
+	if (pwm == NULL || IS_ERR(pwm) ||
+		duty_us > period_us ||
+		(unsigned)period_us > PM_PWM_PERIOD_MAX ||
+		(unsigned)period_us < PM_PWM_PERIOD_MIN) {
+		pr_err("Invalid pwm handle or parameters\n");
+		return -EINVAL;
+	}
+
+	if (!pwm->pwm_config.in_use)
+		return -EINVAL;
+
+	mutex_lock(&pwm->chip->lpg_mutex);
+	rc = _pwm_config(pwm, duty_us, period_us);
+	mutex_unlock(&pwm->chip->lpg_mutex);
+
+	return rc;
+}
+EXPORT_SYMBOL_GPL(pwm_config);
+
+/**
+ * pwm_enable - start a PWM output toggling
+ * @pwm: the PWM device
+ */
+int pwm_enable(struct pwm_device *pwm)
+{
+	struct qpnp_pwm_config	*p_config;
+	struct qpnp_lpg_chip	*chip;
+	int			rc = 0;
+
+	if (pwm == NULL || IS_ERR(pwm) || pwm->chip == NULL) {
+		pr_err("Invalid pwm handle or no pwm_chip\n");
+		return -EINVAL;
+	}
+
+	mutex_lock(&pwm->chip->lpg_mutex);
+
+	chip = pwm->chip;
+	p_config = &pwm->pwm_config;
+
+	if (!p_config->in_use) {
+		pr_err("channel_id: %d: stale handle?\n", p_config->channel_id);
+		rc = -EINVAL;
+		goto out_unlock;
+	}
+
+	if (!pwm->chip->lpg_config.lpg_configured) {
+		pr_err("Request received to enable PWM for channel Id: %d\n",
+							p_config->channel_id);
+		pr_err("However, PWM isn't configured\n");
+		pr_err("falling back to defaultconfiguration\n");
+		rc = _pwm_config(pwm, p_config->pwm_duty,
+					p_config->pwm_period);
+		if (rc) {
+			pr_err("Could not apply default PWM config\n");
+			goto out_unlock;
+		}
+	}
+
+	rc = qpnp_lpg_enable_pwm(pwm);
+
+out_unlock:
+	mutex_unlock(&pwm->chip->lpg_mutex);
+	return rc;
+}
+EXPORT_SYMBOL_GPL(pwm_enable);
+
+/**
+ * pwm_disable - stop a PWM output toggling
+ * @pwm: the PWM device
+ */
+void pwm_disable(struct pwm_device *pwm)
+{
+	struct qpnp_pwm_config	*pwm_config;
+	struct qpnp_lpg_chip	*chip;
+
+	if (pwm == NULL || IS_ERR(pwm) || pwm->chip == NULL) {
+		pr_err("Invalid pwm handle or no pwm_chip\n");
+		return;
+	}
+
+	mutex_lock(&pwm->chip->lpg_mutex);
+
+	chip = pwm->chip;
+	pwm_config = &pwm->pwm_config;
+
+	if (pwm_config->in_use) {
+		if (!pwm->chip->lpg_config.lpg_configured) {
+			pr_err("Request received to disable PWM for\n");
+			pr_err("channel Id: %d\n", pwm_config->channel_id);
+			pr_err("However PWM is not configured by any means\n");
+			goto out_unlock;
+		}
+		qpnp_lpg_disable_pwm(pwm);
+	}
+
+out_unlock:
+	mutex_unlock(&pwm->chip->lpg_mutex);
+}
+EXPORT_SYMBOL_GPL(pwm_disable);
+
+/**
+ * pwm_config_period - change PWM period
+ *
+ * @pwm: the PWM device
+ * @pwm_p: period in struct qpnp_lpg_period
+ */
+int pwm_config_period(struct pwm_device *pwm,
+			     struct pwm_period_config *period)
+{
+	struct qpnp_pwm_config	*pwm_config;
+	struct qpnp_lpg_config	*lpg_config;
+	struct qpnp_lpg_chip	*chip;
+	int			rc = 0;
+
+	if (pwm == NULL || IS_ERR(pwm) || period == NULL)
+		return -EINVAL;
+	if (pwm->chip == NULL)
+		return -ENODEV;
+
+	mutex_lock(&pwm->chip->lpg_mutex);
+
+	chip = pwm->chip;
+	pwm_config = &pwm->pwm_config;
+	lpg_config = &chip->lpg_config;
+
+	if (!pwm_config->in_use) {
+		rc = -EINVAL;
+		goto out_unlock;
+	}
+
+	pwm_config->period.pwm_size = period->pwm_size;
+	pwm_config->period.clk = period->clk;
+	pwm_config->period.pre_div = period->pre_div;
+	pwm_config->period.pre_div_exp = period->pre_div_exp;
+
+	qpnp_lpg_save_period(pwm);
+
+	rc = spmi_ext_register_writel(chip->spmi_dev->ctrl, chip->spmi_dev->sid,
+			SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+			QPNP_LPG_PWM_SIZE_CLK),
+			&chip->qpnp_lpg_registers[QPNP_LPG_PWM_SIZE_CLK], 1);
+
+	if (rc) {
+		pr_err("Write failed: QPNP_LPG_PWM_SIZE_CLK register, rc: %d\n",
+									rc);
+		goto out_unlock;
+	}
+
+	rc = spmi_ext_register_writel(chip->spmi_dev->ctrl, chip->spmi_dev->sid,
+			SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+			QPNP_LPG_PWM_FREQ_PREDIV_CLK),
+		&chip->qpnp_lpg_registers[QPNP_LPG_PWM_FREQ_PREDIV_CLK], 1);
+	if (rc) {
+		pr_err("Failed to write to QPNP_LPG_PWM_FREQ_PREDIV_CLK\n");
+		pr_err("register, rc = %d\n", rc);
+	}
+
+out_unlock:
+	mutex_unlock(&pwm->chip->lpg_mutex);
+	return rc;
+}
+EXPORT_SYMBOL(pwm_config_period);
+
+/**
+ * pwm_config_pwm_value - change a PWM device configuration
+ * @pwm: the PWM device
+ * @pwm_value: the duty cycle in raw PWM value (< 2^pwm_size)
+ */
+int pwm_config_pwm_value(struct pwm_device *pwm, int pwm_value)
+{
+	struct qpnp_lpg_config	*lpg_config;
+	struct qpnp_pwm_config	*pwm_config;
+	int			rc = 0;
+
+	if (pwm == NULL || IS_ERR(pwm))
+		return -EINVAL;
+
+	if (pwm->chip == NULL)
+		return -ENODEV;
+
+	lpg_config = &pwm->chip->lpg_config;
+	pwm_config = &pwm->pwm_config;
+
+	mutex_lock(&pwm->chip->lpg_mutex);
+
+	if (!pwm_config->in_use || !pwm_config->pwm_period) {
+		rc = -EINVAL;
+		goto out_unlock;
+	}
+
+	if (pwm_config->pwm_value == pwm_value)
+		goto out_unlock;
+
+	pwm_config->pwm_value = pwm_value;
+
+	rc = qpnp_lpg_save_pwm_value(pwm);
+
+	if (rc)
+		pr_err("Could not update PWM value for channel %d rc=%d\n",
+						pwm_config->channel_id, rc);
+
+out_unlock:
+	mutex_unlock(&pwm->chip->lpg_mutex);
+	return rc;
+}
+EXPORT_SYMBOL_GPL(pwm_config_pwm_value);
+
+/**
+ * pwm_lut_config - change LPG LUT device configuration
+ * @pwm: the PWM device
+ * @period_us: period in micro second
+ * @duty_pct: array of duty cycles in percent, like 20, 50.
+ * @lut_params: Lookup table parameters
+ */
+int pwm_lut_config(struct pwm_device *pwm, int period_us,
+		int duty_pct[], struct lut_params lut_params)
+{
+	int rc = 0;
+
+	if (pwm == NULL || IS_ERR(pwm) || !lut_params.idx_len) {
+		pr_err("Invalid pwm handle or idx_len=0\n");
+		return -EINVAL;
+	}
+
+	if (pwm->chip == NULL)
+		return -ENODEV;
+
+	if (duty_pct == NULL && !(lut_params.flags & PM_PWM_LUT_NO_TABLE)) {
+		pr_err("Invalid duty_pct with flag\n");
+		return -EINVAL;
+	}
+
+	mutex_lock(&pwm->chip->lpg_mutex);
+
+	rc = _pwm_lut_config(pwm, period_us, duty_pct, lut_params);
+
+	mutex_unlock(&pwm->chip->lpg_mutex);
+
+	return rc;
+}
+EXPORT_SYMBOL_GPL(pwm_lut_config);
+
+/**
+ * pwm_lut_enable - control a PWM device to start/stop LUT ramp
+ * @pwm: the PWM device
+ * @start: to start (1), or stop (0)
+ */
+int pwm_lut_enable(struct pwm_device *pwm, int start)
+{
+	struct qpnp_lpg_config	*lpg_config;
+	struct qpnp_pwm_config	*p_config;
+	struct lut_params	lut_params;
+	int			rc = 0;
+
+	if (pwm == NULL || IS_ERR(pwm)) {
+		pr_err("Invalid pwm handle\n");
+		return -EINVAL;
+	}
+
+	if (pwm->chip == NULL)
+		return -ENODEV;
+
+	lpg_config = &pwm->chip->lpg_config;
+	p_config = &pwm->pwm_config;
+
+	mutex_lock(&pwm->chip->lpg_mutex);
+
+	if (start) {
+		if (!lpg_config->lpg_configured) {
+			pr_err("Request received to enable LUT for\n");
+			pr_err("LPG channel %d\n", pwm->pwm_config.channel_id);
+			pr_err("But LPG is not configured, falling back to\n");
+			pr_err(" default LUT configuration if available\n");
+
+			if (lpg_config->bypass_lut) {
+				pr_err("No default LUT configuration found\n");
+				pr_err("Use pwm_lut_config() to configure\n");
+				rc = -EINVAL;
+				goto out;
+			}
+
+			qpnp_set_lut_params(&lut_params,
+					&lpg_config->lut_config);
+
+			rc = _pwm_lut_config(pwm, p_config->pwm_period,
+			(int *)lpg_config->lut_config.def_config.duty_pct_list,
+			lut_params);
+			if (rc) {
+				pr_err("Could not set the default LUT conf\n");
+				goto out;
+			}
+		}
+
+		rc = qpnp_lpg_enable_lut(pwm);
+	} else {
+		if (unlikely(!lpg_config->lpg_configured)) {
+			pr_err("LPG isn't configured\n");
+			rc = -EINVAL;
+			goto out;
+		}
+		rc = qpnp_lpg_disable_lut(pwm);
+	}
+
+out:
+	mutex_unlock(&pwm->chip->lpg_mutex);
+	return rc;
+}
+EXPORT_SYMBOL_GPL(pwm_lut_enable);
+
+/* Fill in lpg device elements based on values found in device tree. */
+static int qpnp_lpg_get_dt_config(struct spmi_device *spmi,
+					struct qpnp_lpg_chip *chip)
+{
+	int			rc;
+	struct resource		*res;
+	struct device_node	*of_node = spmi->dev.of_node;
+	struct qpnp_lpg_config	*lpg_config = &chip->lpg_config;
+	struct pwm_device	*pwm_dev = &chip->pwm_dev;
+	struct qpnp_lut_config	*lut_config = &chip->lpg_config.lut_config;
+	struct qpnp_lut_default_config	*def_lut_config =
+						&lut_config->def_config;
+
+	res = spmi_get_resource(spmi, 0, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(&spmi->dev, "%s: node is missing base address\n",
+			__func__);
+		return -EINVAL;
+	}
+
+	lpg_config->base_addr = res->start;
+
+	res = spmi_get_resource(spmi, 0, IORESOURCE_MEM, 1);
+	if (!res) {
+		dev_err(&spmi->dev, "%s: node is missing LUT base address\n",
+								__func__);
+		return -EINVAL;
+	}
+
+	lpg_config->lut_base_addr = res->start;
+	/* Each entry of LUT is of 2 bytes */
+	lpg_config->lut_size = resource_size(res) >> 1;
+
+
+	rc = of_property_read_u32(of_node, "qcom,channel-id",
+				&pwm_dev->pwm_config.channel_id);
+	if (rc) {
+		dev_err(&spmi->dev, "%s: node is missing LPG channel id",
+								__func__);
+		return rc;
+	}
+
+	rc = of_property_read_u32(of_node, "qcom,period",
+				&pwm_dev->pwm_config.pwm_period);
+	if (rc) {
+		dev_err(&spmi->dev, "%s: node is missing PWM Period value",
+								__func__);
+		return rc;
+	}
+
+	if (!of_get_property(of_node, "qcom,duty-percents",
+						&def_lut_config->size)) {
+		lpg_config->bypass_lut = 1;
+	}
+
+	if (lpg_config->bypass_lut)
+		goto read_opt_props;
+
+	rc = of_property_read_u32(of_node, "qcom,start-index",
+					&def_lut_config->start_idx);
+
+	if (rc) {
+		dev_err(&spmi->dev, "Missing start index");
+		return rc;
+	}
+
+	def_lut_config->size /= sizeof(u32);
+
+	def_lut_config->duty_pct_list = kzalloc(sizeof(u32) *
+					def_lut_config->size, GFP_KERNEL);
+	if (!def_lut_config->duty_pct_list) {
+		dev_err(&spmi->dev, "%s: kzalloc failed on duty_pct_list\n",
+								__func__);
+		return -ENOMEM;
+	}
+
+	rc = of_property_read_u32_array(of_node, "qcom,duty-percents",
+		def_lut_config->duty_pct_list, def_lut_config->size);
+	if (rc) {
+		dev_err(&spmi->dev, "invalid or missing property:\n");
+		dev_err(&spmi->dev, "qcom,duty-pcts-list\n");
+		kfree(def_lut_config->duty_pct_list);
+		return rc;
+	}
+
+	lut_config->duty_pct_list = kzalloc(lpg_config->lut_size * sizeof(u16),
+								GFP_KERNEL);
+	if (!lut_config->duty_pct_list) {
+		dev_err(&spmi->dev, "can not allocate duty pct list\n");
+		kfree(def_lut_config->duty_pct_list);
+		return -ENOMEM;
+	}
+
+read_opt_props:
+	/* Initialize optional config parameters from DT if provided */
+	of_property_read_u32(of_node, "qcom,duty",
+					&pwm_dev->pwm_config.pwm_duty);
+	of_property_read_u32(of_node, "qcom,ramp-step-duration",
+					&lut_config->ramp_step_ms);
+	of_property_read_u32(of_node, "qcom,lpg-lut-pause-hi",
+					&lut_config->lut_pause_hi_cnt);
+	of_property_read_u32(of_node, "qcom,lpg-lut-pause-lo",
+					&lut_config->lut_pause_lo_cnt);
+	of_property_read_u32(of_node, "qcom,lpg-lut-ramp-direction",
+					(u32 *)&lut_config->ramp_direction);
+	of_property_read_u32(of_node, "qcom,lpg-lut-pattern-repeat",
+					(u32 *)&lut_config->pattern_repeat);
+	of_property_read_u32(of_node, "qcom,lpg-lut-ramp-toggle",
+					(u32 *)&lut_config->ramp_toggle);
+	of_property_read_u32(of_node, "qcom,lpg-lut-enable-pause-hi",
+					(u32 *)&lut_config->enable_pause_hi);
+	of_property_read_u32(of_node, "qcom,lpg-lut-enable-pause-lo",
+					(u32 *)&lut_config->enable_pause_lo);
+
+	return 0;
+}
+
+static int __devinit qpnp_pwm_probe(struct spmi_device *spmi)
+{
+	struct qpnp_lpg_chip	*chip;
+	int			rc, id;
+
+	chip = kzalloc(sizeof *chip, GFP_KERNEL);
+	if (chip == NULL) {
+		pr_err("kzalloc() failed.\n");
+		return -ENOMEM;
+	}
+
+	mutex_init(&chip->lpg_mutex);
+
+	chip->spmi_dev = spmi;
+	chip->pwm_dev.chip = chip;
+	dev_set_drvdata(&spmi->dev, chip);
+
+	rc = qpnp_lpg_get_dt_config(spmi, chip);
+
+	if (rc)
+		goto failed_config;
+
+	id = chip->pwm_dev.pwm_config.channel_id;
+
+	rc = radix_tree_insert(&lpg_dev_tree, id, chip);
+
+	if (rc) {
+		dev_err(&spmi->dev, "%s: Failed to register LPG Channel %d\n",
+								__func__, id);
+		goto failed_insert;
+	}
+
+	return 0;
+
+failed_insert:
+	kfree(chip->lpg_config.lut_config.duty_pct_list);
+failed_config:
+	dev_set_drvdata(&spmi->dev, NULL);
+	mutex_destroy(&chip->lpg_mutex);
+	kfree(chip);
+	return rc;
+}
+
+static int __devexit qpnp_pwm_remove(struct spmi_device *spmi)
+{
+	struct qpnp_lpg_chip *chip;
+	struct qpnp_lpg_config *lpg_config;
+
+	chip = dev_get_drvdata(&spmi->dev);
+
+	dev_set_drvdata(&spmi->dev, NULL);
+
+	if (chip) {
+		lpg_config = &chip->lpg_config;
+		kfree(lpg_config->lut_config.duty_pct_list);
+		kfree(lpg_config->lut_config.def_config.duty_pct_list);
+		mutex_destroy(&chip->lpg_mutex);
+		kfree(chip);
+	}
+
+	return 0;
+}
+
+static struct of_device_id spmi_match_table[] = {
+	{ .compatible = QPNP_LPG_DRIVER_NAME, },
+	{}
+};
+
+static const struct spmi_device_id qpnp_lpg_id[] = {
+	{ QPNP_LPG_DRIVER_NAME, 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(spmi, qpnp_lpg_id);
+
+static struct spmi_driver qpnp_lpg_driver = {
+	.driver		= {
+		.name	= QPNP_LPG_DRIVER_NAME,
+		.of_match_table = spmi_match_table,
+		.owner = THIS_MODULE,
+	},
+	.probe		= qpnp_pwm_probe,
+	.remove		= __devexit_p(qpnp_pwm_remove),
+	.id_table	= qpnp_lpg_id,
+};
+
+/**
+ * qpnp_lpg_init() - register spmi driver for qpnp-lpg
+ */
+int __init qpnp_lpg_init(void)
+{
+	return spmi_driver_register(&qpnp_lpg_driver);
+}
+
+static void __exit qpnp_lpg_exit(void)
+{
+	spmi_driver_unregister(&qpnp_lpg_driver);
+}
+
+MODULE_DESCRIPTION("QPNP PMIC LPG driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" QPNP_LPG_DRIVER_NAME);
+
+subsys_initcall(qpnp_lpg_init);
+module_exit(qpnp_lpg_exit);