drivers: add a snapshot of various QPNP PMIC peripheral drivers
Add a snapshot of several Qualcomm Technologies, Inc. QPNP PMIC
peripheral drivers. These drivers manage various modules found
within PMIC chips.
This snapshot is taken as of msm-4.4
commit d24550bbf50f ("Merge "ARM: dts: msm: Add slimbus slave
device for wcn3990 on sdm630"").
Change-Id: I842f81737eec1ca11bf31534e9299bd7a6511f6c
Signed-off-by: David Collins <collinsd@codeaurora.org>
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index ed37e59..9338ff7 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -362,6 +362,25 @@
help
Say yes here to support the PXA GPIO device
+config GPIO_QPNP_PIN
+ tristate "Qualcomm Technologies, Inc. QPNP GPIO support"
+ depends on SPMI
+ help
+ Say 'y' here to include support for the Qualcomm Technologies, Inc.
+ QPNP GPIO driver. This driver supports Device Tree and allows a
+ device_node to be registered as a gpio-controller. It does not handle
+ GPIO interrupts directly; they are handled via the SPMI arbiter
+ interrupt driver.
+
+config GPIO_QPNP_PIN_DEBUG
+ bool "Qualcomm Technologies, Inc. QPNP GPIO debug support"
+ depends on GPIO_QPNP_PIN && DEBUG_FS
+ help
+ Say 'y' here to include debug support for the Qualcomm Technologies,
+ Inc. QPNP GPIO driver. This provides a userspace debug interface to
+ get and set all of the supported features of PMIC GPIO and MPP pins
+ including those which are managed by the gpio framework.
+
config GPIO_RCAR
tristate "Renesas R-Car GPIO"
depends on ARCH_RENESAS || COMPILE_TEST
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 1b08983..68418a6 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -91,6 +91,7 @@
obj-$(CONFIG_GPIO_PISOSR) += gpio-pisosr.o
obj-$(CONFIG_GPIO_PL061) += gpio-pl061.o
obj-$(CONFIG_GPIO_PXA) += gpio-pxa.o
+obj-$(CONFIG_GPIO_QPNP_PIN) += qpnp-pin.o
obj-$(CONFIG_GPIO_RC5T583) += gpio-rc5t583.o
obj-$(CONFIG_GPIO_RDC321X) += gpio-rdc321x.o
obj-$(CONFIG_GPIO_RCAR) += gpio-rcar.o
diff --git a/drivers/gpio/qpnp-pin.c b/drivers/gpio/qpnp-pin.c
new file mode 100644
index 0000000..d89aca9
--- /dev/null
+++ b/drivers/gpio/qpnp-pin.c
@@ -0,0 +1,1714 @@
+/* Copyright (c) 2012-2017, The Linux Foundation. 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.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/interrupt.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/platform_device.h>
+#include <linux/debugfs.h>
+#include <linux/gpio.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/export.h>
+#include <linux/qpnp/pin.h>
+
+#define Q_REG_ADDR(q_spec, reg_index) \
+ ((q_spec)->offset + reg_index)
+
+#define Q_REG_STATUS1 0x8
+#define Q_REG_STATUS1_VAL_MASK 0x1
+#define Q_REG_STATUS1_GPIO_EN_REV0_MASK 0x2
+#define Q_REG_STATUS1_GPIO_EN_MASK 0x80
+#define Q_REG_STATUS1_MPP_EN_MASK 0x80
+
+#define Q_NUM_CTL_REGS 0xD
+
+/* revision registers base address offsets */
+#define Q_REG_DIG_MINOR_REV 0x0
+#define Q_REG_DIG_MAJOR_REV 0x1
+#define Q_REG_ANA_MINOR_REV 0x2
+
+/* type registers base address offsets */
+#define Q_REG_TYPE 0x4
+#define Q_REG_SUBTYPE 0x5
+
+/* gpio peripheral type and subtype values */
+#define Q_GPIO_TYPE 0x10
+#define Q_GPIO_SUBTYPE_GPIO_4CH 0x1
+#define Q_GPIO_SUBTYPE_GPIOC_4CH 0x5
+#define Q_GPIO_SUBTYPE_GPIO_8CH 0x9
+#define Q_GPIO_SUBTYPE_GPIOC_8CH 0xD
+#define Q_GPIO_SUBTYPE_GPIO_LV 0x10
+#define Q_GPIO_SUBTYPE_GPIO_MV 0x11
+
+/* mpp peripheral type and subtype values */
+#define Q_MPP_TYPE 0x11
+#define Q_MPP_SUBTYPE_4CH_NO_ANA_OUT 0x3
+#define Q_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT 0x4
+#define Q_MPP_SUBTYPE_4CH_NO_SINK 0x5
+#define Q_MPP_SUBTYPE_ULT_4CH_NO_SINK 0x6
+#define Q_MPP_SUBTYPE_4CH_FULL_FUNC 0x7
+#define Q_MPP_SUBTYPE_8CH_FULL_FUNC 0xF
+
+/* control register base address offsets */
+#define Q_REG_MODE_CTL 0x40
+#define Q_REG_DIG_VIN_CTL 0x41
+#define Q_REG_DIG_PULL_CTL 0x42
+#define Q_REG_DIG_IN_CTL 0x43
+#define Q_REG_DIG_OUT_SRC_CTL 0x44
+#define Q_REG_DIG_OUT_CTL 0x45
+#define Q_REG_EN_CTL 0x46
+#define Q_REG_AOUT_CTL 0x48
+#define Q_REG_AIN_CTL 0x4A
+#define Q_REG_APASS_SEL_CTL 0x4A
+#define Q_REG_SINK_CTL 0x4C
+
+/* control register regs array indices */
+#define Q_REG_I_MODE_CTL 0
+#define Q_REG_I_DIG_VIN_CTL 1
+#define Q_REG_I_DIG_PULL_CTL 2
+#define Q_REG_I_DIG_IN_CTL 3
+#define Q_REG_I_DIG_OUT_SRC_CTL 4
+#define Q_REG_I_DIG_OUT_CTL 5
+#define Q_REG_I_EN_CTL 6
+#define Q_REG_I_AOUT_CTL 8
+#define Q_REG_I_APASS_SEL_CTL 10
+#define Q_REG_I_AIN_CTL 10
+#define Q_REG_I_SINK_CTL 12
+
+/* control reg: mode */
+#define Q_REG_OUT_INVERT_SHIFT 0
+#define Q_REG_OUT_INVERT_MASK 0x1
+#define Q_REG_SRC_SEL_SHIFT 1
+#define Q_REG_SRC_SEL_MASK 0xE
+#define Q_REG_MODE_SEL_SHIFT 4
+#define Q_REG_MODE_SEL_MASK 0x70
+#define Q_REG_LV_MV_MODE_SEL_SHIFT 0
+#define Q_REG_LV_MV_MODE_SEL_MASK 0x3
+
+/* control reg: dig_out_src (GPIO LV/MV only) */
+#define Q_REG_DIG_OUT_SRC_SRC_SEL_SHIFT 0
+#define Q_REG_DIG_OUT_SRC_SRC_SEL_MASK 0xF
+#define Q_REG_DIG_OUT_SRC_INVERT_SHIFT 7
+#define Q_REG_DIG_OUT_SRC_INVERT_MASK 0x80
+
+/* control reg: dig_vin */
+#define Q_REG_VIN_SHIFT 0
+#define Q_REG_VIN_MASK 0x7
+
+/* control reg: dig_pull */
+#define Q_REG_PULL_SHIFT 0
+#define Q_REG_PULL_MASK 0x7
+
+/* control reg: dig_out */
+#define Q_REG_OUT_STRENGTH_SHIFT 0
+#define Q_REG_OUT_STRENGTH_MASK 0x3
+#define Q_REG_OUT_TYPE_SHIFT 4
+#define Q_REG_OUT_TYPE_MASK 0x30
+
+/* control reg: dig_in_ctl */
+#define Q_REG_DTEST_SEL_SHIFT 0
+#define Q_REG_DTEST_SEL_MASK 0xF
+#define Q_REG_LV_MV_DTEST_SEL_CFG_SHIFT 0
+#define Q_REG_LV_MV_DTEST_SEL_CFG_MASK 0x7
+#define Q_REG_LV_MV_DTEST_SEL_EN_SHIFT 7
+#define Q_REG_LV_MV_DTEST_SEL_EN_MASK 0x80
+
+/* control reg: en */
+#define Q_REG_MASTER_EN_SHIFT 7
+#define Q_REG_MASTER_EN_MASK 0x80
+
+/* control reg: ana_out */
+#define Q_REG_AOUT_REF_SHIFT 0
+#define Q_REG_AOUT_REF_MASK 0x7
+
+/* control reg: ana_in */
+#define Q_REG_AIN_ROUTE_SHIFT 0
+#define Q_REG_AIN_ROUTE_MASK 0x7
+
+/* control reg: sink */
+#define Q_REG_CS_OUT_SHIFT 0
+#define Q_REG_CS_OUT_MASK 0x7
+
+/* control ref: apass_sel */
+#define Q_REG_APASS_SEL_SHIFT 0
+#define Q_REG_APASS_SEL_MASK 0x3
+
+enum qpnp_pin_param_type {
+ Q_PIN_CFG_MODE,
+ Q_PIN_CFG_OUTPUT_TYPE,
+ Q_PIN_CFG_INVERT,
+ Q_PIN_CFG_PULL,
+ Q_PIN_CFG_VIN_SEL,
+ Q_PIN_CFG_OUT_STRENGTH,
+ Q_PIN_CFG_SRC_SEL,
+ Q_PIN_CFG_MASTER_EN,
+ Q_PIN_CFG_AOUT_REF,
+ Q_PIN_CFG_AIN_ROUTE,
+ Q_PIN_CFG_CS_OUT,
+ Q_PIN_CFG_APASS_SEL,
+ Q_PIN_CFG_DTEST_SEL,
+ Q_PIN_CFG_INVALID,
+};
+
+#define Q_NUM_PARAMS Q_PIN_CFG_INVALID
+
+/* param error checking */
+#define QPNP_PIN_GPIO_MODE_INVALID 3
+#define QPNP_PIN_GPIO_LV_MV_MODE_INVALID 4
+#define QPNP_PIN_MPP_MODE_INVALID 7
+#define QPNP_PIN_INVERT_INVALID 2
+#define QPNP_PIN_OUT_BUF_INVALID 3
+#define QPNP_PIN_GPIO_LV_MV_OUT_BUF_INVALID 4
+#define QPNP_PIN_VIN_4CH_INVALID 5
+#define QPNP_PIN_VIN_8CH_INVALID 8
+#define QPNP_PIN_GPIO_LV_VIN_INVALID 1
+#define QPNP_PIN_GPIO_MV_VIN_INVALID 2
+#define QPNP_PIN_GPIO_PULL_INVALID 6
+#define QPNP_PIN_MPP_PULL_INVALID 4
+#define QPNP_PIN_OUT_STRENGTH_INVALID 4
+#define QPNP_PIN_SRC_INVALID 8
+#define QPNP_PIN_GPIO_LV_MV_SRC_INVALID 16
+#define QPNP_PIN_MASTER_INVALID 2
+#define QPNP_PIN_AOUT_REF_INVALID 8
+#define QPNP_PIN_AIN_ROUTE_INVALID 8
+#define QPNP_PIN_CS_OUT_INVALID 8
+#define QPNP_PIN_APASS_SEL_INVALID 4
+#define QPNP_PIN_DTEST_SEL_INVALID 4
+
+struct qpnp_pin_spec {
+ uint8_t slave; /* 0-15 */
+ uint16_t offset; /* 0-255 */
+ uint32_t gpio_chip_idx; /* offset from gpio_chip base */
+ uint32_t pmic_pin; /* PMIC pin number */
+ int irq; /* logical IRQ number */
+ u8 regs[Q_NUM_CTL_REGS]; /* Control regs */
+ u8 num_ctl_regs; /* usable number on this pin */
+ u8 type; /* peripheral type */
+ u8 subtype; /* peripheral subtype */
+ u8 dig_major_rev;
+ struct device_node *node;
+ enum qpnp_pin_param_type params[Q_NUM_PARAMS];
+ struct qpnp_pin_chip *q_chip;
+};
+
+struct qpnp_pin_chip {
+ struct gpio_chip gpio_chip;
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ struct qpnp_pin_spec **pmic_pins;
+ struct qpnp_pin_spec **chip_gpios;
+ uint32_t pmic_pin_lowest;
+ uint32_t pmic_pin_highest;
+ struct device_node *int_ctrl;
+ struct list_head chip_list;
+ struct dentry *dfs_dir;
+ bool chip_registered;
+};
+
+static LIST_HEAD(qpnp_pin_chips);
+static DEFINE_MUTEX(qpnp_pin_chips_lock);
+
+static inline void qpnp_pmic_pin_set_spec(struct qpnp_pin_chip *q_chip,
+ uint32_t pmic_pin,
+ struct qpnp_pin_spec *spec)
+{
+ q_chip->pmic_pins[pmic_pin - q_chip->pmic_pin_lowest] = spec;
+}
+
+static inline struct qpnp_pin_spec *qpnp_pmic_pin_get_spec(
+ struct qpnp_pin_chip *q_chip,
+ uint32_t pmic_pin)
+{
+ if (pmic_pin < q_chip->pmic_pin_lowest ||
+ pmic_pin > q_chip->pmic_pin_highest)
+ return NULL;
+
+ return q_chip->pmic_pins[pmic_pin - q_chip->pmic_pin_lowest];
+}
+
+static inline struct qpnp_pin_spec *qpnp_chip_gpio_get_spec(
+ struct qpnp_pin_chip *q_chip,
+ uint32_t chip_gpio)
+{
+ if (chip_gpio >= q_chip->gpio_chip.ngpio)
+ return NULL;
+
+ return q_chip->chip_gpios[chip_gpio];
+}
+
+static inline void qpnp_chip_gpio_set_spec(struct qpnp_pin_chip *q_chip,
+ uint32_t chip_gpio,
+ struct qpnp_pin_spec *spec)
+{
+ q_chip->chip_gpios[chip_gpio] = spec;
+}
+
+static bool is_gpio_lv_mv(struct qpnp_pin_spec *q_spec)
+{
+ if ((q_spec->type == Q_GPIO_TYPE) &&
+ (q_spec->subtype == Q_GPIO_SUBTYPE_GPIO_LV ||
+ q_spec->subtype == Q_GPIO_SUBTYPE_GPIO_MV))
+ return true;
+
+ return false;
+}
+
+/*
+ * Determines whether a specified param's configuration is correct.
+ * This check is two tier. First a check is done whether the hardware
+ * supports this param and value requested. The second check validates
+ * that the configuration is correct, given the fact that the hardware
+ * supports it.
+ *
+ * Returns
+ * -ENXIO is the hardware does not support this param.
+ * -EINVAL if the the hardware does support this param, but the
+ * requested value is outside the supported range.
+ */
+static int qpnp_pin_check_config(enum qpnp_pin_param_type idx,
+ struct qpnp_pin_spec *q_spec, uint32_t val)
+{
+ u8 subtype = q_spec->subtype;
+
+ switch (idx) {
+ case Q_PIN_CFG_MODE:
+ if (q_spec->type == Q_GPIO_TYPE) {
+ if (is_gpio_lv_mv(q_spec)) {
+ if (val >= QPNP_PIN_GPIO_LV_MV_MODE_INVALID)
+ return -EINVAL;
+ } else if (val >= QPNP_PIN_GPIO_MODE_INVALID) {
+ return -EINVAL;
+ }
+ } else if (q_spec->type == Q_MPP_TYPE) {
+ if (val >= QPNP_PIN_MPP_MODE_INVALID)
+ return -EINVAL;
+ if ((subtype == Q_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT ||
+ subtype == Q_MPP_SUBTYPE_ULT_4CH_NO_SINK) &&
+ (val == QPNP_PIN_MODE_BIDIR))
+ return -ENXIO;
+ }
+ break;
+ case Q_PIN_CFG_OUTPUT_TYPE:
+ if (q_spec->type != Q_GPIO_TYPE)
+ return -ENXIO;
+ if ((val == QPNP_PIN_OUT_BUF_OPEN_DRAIN_NMOS ||
+ val == QPNP_PIN_OUT_BUF_OPEN_DRAIN_PMOS) &&
+ (subtype == Q_GPIO_SUBTYPE_GPIOC_4CH ||
+ (subtype == Q_GPIO_SUBTYPE_GPIOC_8CH)))
+ return -EINVAL;
+ else if (is_gpio_lv_mv(q_spec) &&
+ val >= QPNP_PIN_GPIO_LV_MV_OUT_BUF_INVALID)
+ return -EINVAL;
+ else if (val >= QPNP_PIN_OUT_BUF_INVALID)
+ return -EINVAL;
+ break;
+ case Q_PIN_CFG_INVERT:
+ if (val >= QPNP_PIN_INVERT_INVALID)
+ return -EINVAL;
+ break;
+ case Q_PIN_CFG_PULL:
+ if (q_spec->type == Q_GPIO_TYPE &&
+ val >= QPNP_PIN_GPIO_PULL_INVALID)
+ return -EINVAL;
+ if (q_spec->type == Q_MPP_TYPE) {
+ if (val >= QPNP_PIN_MPP_PULL_INVALID)
+ return -EINVAL;
+ if (subtype == Q_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT ||
+ subtype == Q_MPP_SUBTYPE_ULT_4CH_NO_SINK)
+ return -ENXIO;
+ }
+ break;
+ case Q_PIN_CFG_VIN_SEL:
+ if (is_gpio_lv_mv(q_spec)) {
+ if (subtype == Q_GPIO_SUBTYPE_GPIO_LV) {
+ if (val >= QPNP_PIN_GPIO_LV_VIN_INVALID)
+ return -EINVAL;
+ } else {
+ if (val >= QPNP_PIN_GPIO_MV_VIN_INVALID)
+ return -EINVAL;
+ }
+ } else if (val >= QPNP_PIN_VIN_8CH_INVALID) {
+ return -EINVAL;
+ } else if (val >= QPNP_PIN_VIN_4CH_INVALID) {
+ if (q_spec->type == Q_GPIO_TYPE &&
+ (subtype == Q_GPIO_SUBTYPE_GPIO_4CH ||
+ subtype == Q_GPIO_SUBTYPE_GPIOC_4CH))
+ return -EINVAL;
+ if (q_spec->type == Q_MPP_TYPE &&
+ (subtype == Q_MPP_SUBTYPE_4CH_NO_ANA_OUT ||
+ subtype == Q_MPP_SUBTYPE_4CH_NO_SINK ||
+ subtype == Q_MPP_SUBTYPE_4CH_FULL_FUNC ||
+ subtype == Q_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT ||
+ subtype == Q_MPP_SUBTYPE_ULT_4CH_NO_SINK))
+ return -EINVAL;
+ }
+ break;
+ case Q_PIN_CFG_OUT_STRENGTH:
+ if (q_spec->type != Q_GPIO_TYPE)
+ return -ENXIO;
+ if (val >= QPNP_PIN_OUT_STRENGTH_INVALID ||
+ val == 0)
+ return -EINVAL;
+ break;
+ case Q_PIN_CFG_SRC_SEL:
+ if (q_spec->type == Q_MPP_TYPE &&
+ (val == QPNP_PIN_SEL_FUNC_1 ||
+ val == QPNP_PIN_SEL_FUNC_2))
+ return -EINVAL;
+ if (is_gpio_lv_mv(q_spec)) {
+ if (val >= QPNP_PIN_GPIO_LV_MV_SRC_INVALID)
+ return -EINVAL;
+ } else if (val >= QPNP_PIN_SRC_INVALID) {
+ return -EINVAL;
+ }
+ break;
+ case Q_PIN_CFG_MASTER_EN:
+ if (val >= QPNP_PIN_MASTER_INVALID)
+ return -EINVAL;
+ break;
+ case Q_PIN_CFG_AOUT_REF:
+ if (q_spec->type != Q_MPP_TYPE)
+ return -ENXIO;
+ if (subtype == Q_MPP_SUBTYPE_4CH_NO_ANA_OUT ||
+ subtype == Q_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT)
+ return -ENXIO;
+ if (val >= QPNP_PIN_AOUT_REF_INVALID)
+ return -EINVAL;
+ break;
+ case Q_PIN_CFG_AIN_ROUTE:
+ if (q_spec->type != Q_MPP_TYPE)
+ return -ENXIO;
+ if (val >= QPNP_PIN_AIN_ROUTE_INVALID)
+ return -EINVAL;
+ break;
+ case Q_PIN_CFG_CS_OUT:
+ if (q_spec->type != Q_MPP_TYPE)
+ return -ENXIO;
+ if (subtype == Q_MPP_SUBTYPE_4CH_NO_SINK ||
+ subtype == Q_MPP_SUBTYPE_ULT_4CH_NO_SINK)
+ return -ENXIO;
+ if (val >= QPNP_PIN_CS_OUT_INVALID)
+ return -EINVAL;
+ break;
+ case Q_PIN_CFG_APASS_SEL:
+ if (!is_gpio_lv_mv(q_spec))
+ return -ENXIO;
+ if (val >= QPNP_PIN_APASS_SEL_INVALID)
+ return -EINVAL;
+ break;
+ case Q_PIN_CFG_DTEST_SEL:
+ if (val > QPNP_PIN_DTEST_SEL_INVALID)
+ return -EINVAL;
+ break;
+ default:
+ pr_err("invalid param type %u specified\n", idx);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+#define Q_CHK_INVALID(idx, q_spec, val) \
+ (qpnp_pin_check_config(idx, q_spec, val) == -EINVAL)
+
+static int qpnp_pin_check_constraints(struct qpnp_pin_spec *q_spec,
+ struct qpnp_pin_cfg *param)
+{
+ int pin = q_spec->pmic_pin;
+ const char *name;
+
+ name = (q_spec->type == Q_GPIO_TYPE) ? "gpio" : "mpp";
+
+ if (Q_CHK_INVALID(Q_PIN_CFG_MODE, q_spec, param->mode))
+ pr_err("invalid direction value %d for %s %d\n",
+ param->mode, name, pin);
+ else if (Q_CHK_INVALID(Q_PIN_CFG_INVERT, q_spec, param->invert))
+ pr_err("invalid invert polarity value %d for %s %d\n",
+ param->invert, name, pin);
+ else if (Q_CHK_INVALID(Q_PIN_CFG_SRC_SEL, q_spec, param->src_sel))
+ pr_err("invalid source select value %d for %s %d\n",
+ param->src_sel, name, pin);
+ else if (Q_CHK_INVALID(Q_PIN_CFG_OUT_STRENGTH,
+ q_spec, param->out_strength))
+ pr_err("invalid out strength value %d for %s %d\n",
+ param->out_strength, name, pin);
+ else if (Q_CHK_INVALID(Q_PIN_CFG_OUTPUT_TYPE,
+ q_spec, param->output_type))
+ pr_err("invalid out type value %d for %s %d\n",
+ param->output_type, name, pin);
+ else if (Q_CHK_INVALID(Q_PIN_CFG_VIN_SEL, q_spec, param->vin_sel))
+ pr_err("invalid vin select %d value for %s %d\n",
+ param->vin_sel, name, pin);
+ else if (Q_CHK_INVALID(Q_PIN_CFG_PULL, q_spec, param->pull))
+ pr_err("invalid pull value %d for pin %s %d\n",
+ param->pull, name, pin);
+ else if (Q_CHK_INVALID(Q_PIN_CFG_MASTER_EN, q_spec, param->master_en))
+ pr_err("invalid master_en value %d for %s %d\n",
+ param->master_en, name, pin);
+ else if (Q_CHK_INVALID(Q_PIN_CFG_AOUT_REF, q_spec, param->aout_ref))
+ pr_err("invalid aout_reg value %d for %s %d\n",
+ param->aout_ref, name, pin);
+ else if (Q_CHK_INVALID(Q_PIN_CFG_AIN_ROUTE, q_spec, param->ain_route))
+ pr_err("invalid ain_route value %d for %s %d\n",
+ param->ain_route, name, pin);
+ else if (Q_CHK_INVALID(Q_PIN_CFG_CS_OUT, q_spec, param->cs_out))
+ pr_err("invalid cs_out value %d for %s %d\n",
+ param->cs_out, name, pin);
+ else if (Q_CHK_INVALID(Q_PIN_CFG_APASS_SEL, q_spec, param->apass_sel))
+ pr_err("invalid apass_sel value %d for %s %d\n",
+ param->apass_sel, name, pin);
+ else if (Q_CHK_INVALID(Q_PIN_CFG_DTEST_SEL, q_spec, param->dtest_sel))
+ pr_err("invalid dtest_sel value %d for %s %d\n",
+ param->dtest_sel, name, pin);
+ else
+ return 0;
+
+ return -EINVAL;
+}
+
+static inline u8 q_reg_get(u8 *reg, int shift, int mask)
+{
+ return (*reg & mask) >> shift;
+}
+
+static inline void q_reg_set(u8 *reg, int shift, int mask, int value)
+{
+ *reg |= (value << shift) & mask;
+}
+
+static inline void q_reg_clr_set(u8 *reg, int shift, int mask, int value)
+{
+ *reg &= ~mask;
+ *reg |= (value << shift) & mask;
+}
+
+/*
+ * Calculate the minimum number of registers that must be read / written
+ * in order to satisfy the full feature set of the given pin.
+ */
+static int qpnp_pin_ctl_regs_init(struct qpnp_pin_spec *q_spec)
+{
+ if (q_spec->type == Q_GPIO_TYPE) {
+ if (is_gpio_lv_mv(q_spec))
+ q_spec->num_ctl_regs = 11;
+ else
+ q_spec->num_ctl_regs = 7;
+ } else if (q_spec->type == Q_MPP_TYPE) {
+ switch (q_spec->subtype) {
+ case Q_MPP_SUBTYPE_4CH_NO_SINK:
+ case Q_MPP_SUBTYPE_ULT_4CH_NO_SINK:
+ q_spec->num_ctl_regs = 12;
+ break;
+ case Q_MPP_SUBTYPE_4CH_NO_ANA_OUT:
+ case Q_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT:
+ case Q_MPP_SUBTYPE_4CH_FULL_FUNC:
+ case Q_MPP_SUBTYPE_8CH_FULL_FUNC:
+ q_spec->num_ctl_regs = 13;
+ break;
+ default:
+ pr_err("Invalid MPP subtype 0x%x\n", q_spec->subtype);
+ return -EINVAL;
+ }
+ } else {
+ pr_err("Invalid type 0x%x\n", q_spec->type);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int qpnp_pin_read_regs(struct qpnp_pin_chip *q_chip,
+ struct qpnp_pin_spec *q_spec)
+{
+ int bytes_left = q_spec->num_ctl_regs;
+ int rc;
+ char *buf_p = &q_spec->regs[0];
+ u16 reg_addr = Q_REG_ADDR(q_spec, Q_REG_MODE_CTL);
+
+ while (bytes_left > 0) {
+ rc = regmap_bulk_read(q_chip->regmap, reg_addr, buf_p,
+ bytes_left < 8 ? bytes_left : 8);
+ if (rc)
+ return rc;
+ bytes_left -= 8;
+ buf_p += 8;
+ reg_addr += 8;
+ }
+ return 0;
+}
+
+static int qpnp_pin_write_regs(struct qpnp_pin_chip *q_chip,
+ struct qpnp_pin_spec *q_spec)
+{
+ int bytes_left = q_spec->num_ctl_regs;
+ int rc;
+ char *buf_p = &q_spec->regs[0];
+ u16 reg_addr = Q_REG_ADDR(q_spec, Q_REG_MODE_CTL);
+
+ while (bytes_left > 0) {
+ rc = regmap_bulk_write(q_chip->regmap, reg_addr, buf_p,
+ bytes_left < 8 ? bytes_left : 8);
+ if (rc)
+ return rc;
+ bytes_left -= 8;
+ buf_p += 8;
+ reg_addr += 8;
+ }
+ return 0;
+}
+
+static int qpnp_pin_cache_regs(struct qpnp_pin_chip *q_chip,
+ struct qpnp_pin_spec *q_spec)
+{
+ int rc;
+ struct device *dev = &q_chip->pdev->dev;
+
+ rc = qpnp_pin_read_regs(q_chip, q_spec);
+ if (rc)
+ dev_err(dev, "%s: unable to read control regs\n", __func__);
+
+ return rc;
+}
+
+#define Q_HAVE_HW_SP(idx, q_spec, val) \
+ (qpnp_pin_check_config(idx, q_spec, val) == 0)
+
+static int _qpnp_pin_config(struct qpnp_pin_chip *q_chip,
+ struct qpnp_pin_spec *q_spec,
+ struct qpnp_pin_cfg *param)
+{
+ struct device *dev = &q_chip->pdev->dev;
+ int rc;
+ u8 shift, mask, *reg;
+
+ rc = qpnp_pin_check_constraints(q_spec, param);
+ if (rc)
+ goto gpio_cfg;
+
+ /* set mode */
+ if (Q_HAVE_HW_SP(Q_PIN_CFG_MODE, q_spec, param->mode)) {
+ if (is_gpio_lv_mv(q_spec)) {
+ shift = Q_REG_LV_MV_MODE_SEL_SHIFT;
+ mask = Q_REG_LV_MV_MODE_SEL_MASK;
+ } else {
+ shift = Q_REG_MODE_SEL_SHIFT;
+ mask = Q_REG_MODE_SEL_MASK;
+ }
+ q_reg_clr_set(&q_spec->regs[Q_REG_I_MODE_CTL],
+ shift, mask, param->mode);
+ }
+
+ /* output specific configuration */
+ if (Q_HAVE_HW_SP(Q_PIN_CFG_INVERT, q_spec, param->invert)) {
+ if (is_gpio_lv_mv(q_spec)) {
+ shift = Q_REG_DIG_OUT_SRC_INVERT_SHIFT;
+ mask = Q_REG_DIG_OUT_SRC_INVERT_MASK;
+ reg = &q_spec->regs[Q_REG_I_DIG_OUT_SRC_CTL];
+ } else {
+ shift = Q_REG_OUT_INVERT_SHIFT;
+ mask = Q_REG_OUT_INVERT_MASK;
+ reg = &q_spec->regs[Q_REG_I_MODE_CTL];
+ }
+ q_reg_clr_set(reg, shift, mask, param->invert);
+ }
+
+ if (Q_HAVE_HW_SP(Q_PIN_CFG_SRC_SEL, q_spec, param->src_sel)) {
+ if (is_gpio_lv_mv(q_spec)) {
+ shift = Q_REG_DIG_OUT_SRC_SRC_SEL_SHIFT;
+ mask = Q_REG_DIG_OUT_SRC_SRC_SEL_MASK;
+ reg = &q_spec->regs[Q_REG_I_DIG_OUT_SRC_CTL];
+ } else {
+ shift = Q_REG_SRC_SEL_SHIFT;
+ mask = Q_REG_SRC_SEL_MASK;
+ reg = &q_spec->regs[Q_REG_I_MODE_CTL];
+ }
+ q_reg_clr_set(reg, shift, mask, param->src_sel);
+ }
+
+ if (Q_HAVE_HW_SP(Q_PIN_CFG_OUT_STRENGTH, q_spec, param->out_strength))
+ q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_OUT_CTL],
+ Q_REG_OUT_STRENGTH_SHIFT, Q_REG_OUT_STRENGTH_MASK,
+ param->out_strength);
+ if (Q_HAVE_HW_SP(Q_PIN_CFG_OUTPUT_TYPE, q_spec, param->output_type))
+ q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_OUT_CTL],
+ Q_REG_OUT_TYPE_SHIFT, Q_REG_OUT_TYPE_MASK,
+ param->output_type);
+
+ /* input config */
+ if (Q_HAVE_HW_SP(Q_PIN_CFG_DTEST_SEL, q_spec, param->dtest_sel)
+ && param->dtest_sel) {
+ if (is_gpio_lv_mv(q_spec)) {
+ q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_IN_CTL],
+ Q_REG_LV_MV_DTEST_SEL_CFG_SHIFT,
+ Q_REG_LV_MV_DTEST_SEL_CFG_MASK,
+ param->dtest_sel - 1);
+ q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_IN_CTL],
+ Q_REG_LV_MV_DTEST_SEL_EN_SHIFT,
+ Q_REG_LV_MV_DTEST_SEL_EN_MASK, 0x1);
+ } else {
+ q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_IN_CTL],
+ Q_REG_DTEST_SEL_SHIFT,
+ Q_REG_DTEST_SEL_MASK,
+ BIT(param->dtest_sel - 1));
+ }
+ }
+
+ /* config applicable for both input / output */
+ if (Q_HAVE_HW_SP(Q_PIN_CFG_VIN_SEL, q_spec, param->vin_sel))
+ q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_VIN_CTL],
+ Q_REG_VIN_SHIFT, Q_REG_VIN_MASK,
+ param->vin_sel);
+ if (Q_HAVE_HW_SP(Q_PIN_CFG_PULL, q_spec, param->pull))
+ q_reg_clr_set(&q_spec->regs[Q_REG_I_DIG_PULL_CTL],
+ Q_REG_PULL_SHIFT, Q_REG_PULL_MASK,
+ param->pull);
+ if (Q_HAVE_HW_SP(Q_PIN_CFG_MASTER_EN, q_spec, param->master_en))
+ q_reg_clr_set(&q_spec->regs[Q_REG_I_EN_CTL],
+ Q_REG_MASTER_EN_SHIFT, Q_REG_MASTER_EN_MASK,
+ param->master_en);
+
+ /* mpp specific config */
+ if (Q_HAVE_HW_SP(Q_PIN_CFG_AOUT_REF, q_spec, param->aout_ref))
+ q_reg_clr_set(&q_spec->regs[Q_REG_I_AOUT_CTL],
+ Q_REG_AOUT_REF_SHIFT, Q_REG_AOUT_REF_MASK,
+ param->aout_ref);
+ if (Q_HAVE_HW_SP(Q_PIN_CFG_AIN_ROUTE, q_spec, param->ain_route))
+ q_reg_clr_set(&q_spec->regs[Q_REG_I_AIN_CTL],
+ Q_REG_AIN_ROUTE_SHIFT, Q_REG_AIN_ROUTE_MASK,
+ param->ain_route);
+ if (Q_HAVE_HW_SP(Q_PIN_CFG_CS_OUT, q_spec, param->cs_out))
+ q_reg_clr_set(&q_spec->regs[Q_REG_I_SINK_CTL],
+ Q_REG_CS_OUT_SHIFT, Q_REG_CS_OUT_MASK,
+ param->cs_out);
+ if (Q_HAVE_HW_SP(Q_PIN_CFG_APASS_SEL, q_spec, param->apass_sel))
+ q_reg_clr_set(&q_spec->regs[Q_REG_I_APASS_SEL_CTL],
+ Q_REG_APASS_SEL_SHIFT, Q_REG_APASS_SEL_MASK,
+ param->apass_sel);
+
+ rc = qpnp_pin_write_regs(q_chip, q_spec);
+ if (rc) {
+ dev_err(&q_chip->pdev->dev,
+ "%s: unable to write master enable\n",
+ __func__);
+ goto gpio_cfg;
+ }
+
+ return 0;
+
+gpio_cfg:
+ dev_err(dev, "%s: unable to set default config for pmic pin %d\n",
+ __func__, q_spec->pmic_pin);
+
+ return rc;
+}
+
+int qpnp_pin_config(int gpio, struct qpnp_pin_cfg *param)
+{
+ int rc, chip_offset;
+ struct qpnp_pin_chip *q_chip;
+ struct qpnp_pin_spec *q_spec = NULL;
+ struct gpio_chip *gpio_chip;
+
+ if (param == NULL)
+ return -EINVAL;
+
+ mutex_lock(&qpnp_pin_chips_lock);
+ list_for_each_entry(q_chip, &qpnp_pin_chips, chip_list) {
+ gpio_chip = &q_chip->gpio_chip;
+ if (gpio >= gpio_chip->base
+ && gpio < gpio_chip->base + gpio_chip->ngpio) {
+ chip_offset = gpio - gpio_chip->base;
+ q_spec = qpnp_chip_gpio_get_spec(q_chip, chip_offset);
+ if (WARN_ON(!q_spec)) {
+ mutex_unlock(&qpnp_pin_chips_lock);
+ return -ENODEV;
+ }
+ break;
+ }
+ }
+ mutex_unlock(&qpnp_pin_chips_lock);
+
+ if (!q_spec)
+ return -ENODEV;
+
+ rc = _qpnp_pin_config(q_chip, q_spec, param);
+
+ return rc;
+}
+EXPORT_SYMBOL(qpnp_pin_config);
+
+int qpnp_pin_map(const char *name, uint32_t pmic_pin)
+{
+ struct qpnp_pin_chip *q_chip;
+ struct qpnp_pin_spec *q_spec = NULL;
+
+ if (!name)
+ return -EINVAL;
+
+ mutex_lock(&qpnp_pin_chips_lock);
+ list_for_each_entry(q_chip, &qpnp_pin_chips, chip_list) {
+ if (strcmp(q_chip->gpio_chip.label, name) != 0)
+ continue;
+ if (q_chip->pmic_pin_lowest <= pmic_pin &&
+ q_chip->pmic_pin_highest >= pmic_pin) {
+ q_spec = qpnp_pmic_pin_get_spec(q_chip, pmic_pin);
+ mutex_unlock(&qpnp_pin_chips_lock);
+ if (WARN_ON(!q_spec))
+ return -ENODEV;
+ return q_chip->gpio_chip.base + q_spec->gpio_chip_idx;
+ }
+ }
+ mutex_unlock(&qpnp_pin_chips_lock);
+ return -EINVAL;
+}
+EXPORT_SYMBOL(qpnp_pin_map);
+
+static int qpnp_pin_to_irq(struct gpio_chip *gpio_chip, unsigned int offset)
+{
+ struct qpnp_pin_chip *q_chip = gpiochip_get_data(gpio_chip);
+ struct qpnp_pin_spec *q_spec;
+ struct of_phandle_args oirq;
+
+ q_spec = qpnp_chip_gpio_get_spec(q_chip, offset);
+ if (!q_spec)
+ return -EINVAL;
+
+ /* if we have mapped this pin previously return the virq */
+ if (q_spec->irq)
+ return q_spec->irq;
+
+ /* call into irq_domain to get irq mapping */
+ oirq.np = q_chip->int_ctrl;
+ oirq.args[0] = to_spmi_device(q_chip->pdev->dev.parent)->usid;
+ oirq.args[1] = (q_spec->offset >> 8) & 0xFF;
+ oirq.args[2] = 0;
+ oirq.args[3] = IRQ_TYPE_NONE;
+ oirq.args_count = 4;
+
+ q_spec->irq = irq_create_of_mapping(&oirq);
+ if (!q_spec->irq) {
+ dev_err(&q_chip->pdev->dev, "%s: invalid irq for gpio %u\n",
+ __func__, q_spec->pmic_pin);
+ WARN_ON(1);
+ return -EINVAL;
+ }
+
+ return q_spec->irq;
+}
+
+static int qpnp_pin_get(struct gpio_chip *gpio_chip, unsigned int offset)
+{
+ struct qpnp_pin_chip *q_chip = gpiochip_get_data(gpio_chip);
+ struct qpnp_pin_spec *q_spec = NULL;
+ u8 buf, en_mask, shift, mask, reg;
+ unsigned int val;
+ int rc;
+
+ if (WARN_ON(!q_chip))
+ return -ENODEV;
+
+ q_spec = qpnp_chip_gpio_get_spec(q_chip, offset);
+ if (WARN_ON(!q_spec))
+ return -ENODEV;
+
+ if (is_gpio_lv_mv(q_spec)) {
+ mask = Q_REG_LV_MV_MODE_SEL_MASK;
+ shift = Q_REG_LV_MV_MODE_SEL_SHIFT;
+ } else {
+ mask = Q_REG_MODE_SEL_MASK;
+ shift = Q_REG_MODE_SEL_SHIFT;
+ }
+
+ /* gpio val is from RT status iff input is enabled */
+ if (q_reg_get(&q_spec->regs[Q_REG_I_MODE_CTL], shift, mask)
+ == QPNP_PIN_MODE_DIG_IN) {
+ rc = regmap_read(q_chip->regmap,
+ Q_REG_ADDR(q_spec, Q_REG_STATUS1), &val);
+ if (rc)
+ return rc;
+ buf = val;
+
+ if (q_spec->type == Q_GPIO_TYPE && q_spec->dig_major_rev == 0)
+ en_mask = Q_REG_STATUS1_GPIO_EN_REV0_MASK;
+ else if (q_spec->type == Q_GPIO_TYPE &&
+ q_spec->dig_major_rev > 0)
+ en_mask = Q_REG_STATUS1_GPIO_EN_MASK;
+ else /* MPP */
+ en_mask = Q_REG_STATUS1_MPP_EN_MASK;
+
+ if (!(buf & en_mask))
+ return -EPERM;
+
+ return buf & Q_REG_STATUS1_VAL_MASK;
+ }
+
+ if (is_gpio_lv_mv(q_spec)) {
+ shift = Q_REG_DIG_OUT_SRC_INVERT_SHIFT;
+ mask = Q_REG_DIG_OUT_SRC_INVERT_MASK;
+ reg = q_spec->regs[Q_REG_I_DIG_OUT_SRC_CTL];
+ } else {
+ shift = Q_REG_OUT_INVERT_SHIFT;
+ mask = Q_REG_OUT_INVERT_MASK;
+ reg = q_spec->regs[Q_REG_I_MODE_CTL];
+ }
+
+ return (reg & mask) >> shift;
+}
+
+static int __qpnp_pin_set(struct qpnp_pin_chip *q_chip,
+ struct qpnp_pin_spec *q_spec, int value)
+{
+ int rc;
+ u8 shift, mask, *reg;
+ u16 address;
+
+ if (!q_chip || !q_spec)
+ return -EINVAL;
+
+ if (is_gpio_lv_mv(q_spec)) {
+ shift = Q_REG_DIG_OUT_SRC_INVERT_SHIFT;
+ mask = Q_REG_DIG_OUT_SRC_INVERT_MASK;
+ reg = &q_spec->regs[Q_REG_I_DIG_OUT_SRC_CTL];
+ address = Q_REG_ADDR(q_spec, Q_REG_DIG_OUT_SRC_CTL);
+ } else {
+ shift = Q_REG_OUT_INVERT_SHIFT;
+ mask = Q_REG_OUT_INVERT_MASK;
+ reg = &q_spec->regs[Q_REG_I_MODE_CTL];
+ address = Q_REG_ADDR(q_spec, Q_REG_MODE_CTL);
+ }
+
+ q_reg_clr_set(reg, shift, mask, !!value);
+
+ rc = regmap_write(q_chip->regmap, address, *reg);
+ if (rc)
+ dev_err(&q_chip->pdev->dev, "%s: spmi write failed\n",
+ __func__);
+ return rc;
+}
+
+
+static void qpnp_pin_set(struct gpio_chip *gpio_chip,
+ unsigned int offset, int value)
+{
+ struct qpnp_pin_chip *q_chip = gpiochip_get_data(gpio_chip);
+ struct qpnp_pin_spec *q_spec;
+
+ if (WARN_ON(!q_chip))
+ return;
+
+ q_spec = qpnp_chip_gpio_get_spec(q_chip, offset);
+ if (WARN_ON(!q_spec))
+ return;
+
+ __qpnp_pin_set(q_chip, q_spec, value);
+}
+
+static int qpnp_pin_set_mode(struct qpnp_pin_chip *q_chip,
+ struct qpnp_pin_spec *q_spec, int mode)
+{
+ int rc;
+ u8 shift, mask;
+
+ if (!q_chip || !q_spec)
+ return -EINVAL;
+
+ if (qpnp_pin_check_config(Q_PIN_CFG_MODE, q_spec, mode)) {
+ pr_err("invalid mode specification %d\n", mode);
+ return -EINVAL;
+ }
+
+ if (is_gpio_lv_mv(q_spec)) {
+ shift = Q_REG_LV_MV_MODE_SEL_SHIFT;
+ mask = Q_REG_LV_MV_MODE_SEL_MASK;
+ } else {
+ shift = Q_REG_MODE_SEL_SHIFT;
+ mask = Q_REG_MODE_SEL_MASK;
+ }
+
+ q_reg_clr_set(&q_spec->regs[Q_REG_I_MODE_CTL], shift, mask, mode);
+
+ rc = regmap_write(q_chip->regmap, Q_REG_ADDR(q_spec, Q_REG_MODE_CTL),
+ *&q_spec->regs[Q_REG_I_MODE_CTL]);
+ return rc;
+}
+
+static int qpnp_pin_direction_input(struct gpio_chip *gpio_chip,
+ unsigned int offset)
+{
+ struct qpnp_pin_chip *q_chip = gpiochip_get_data(gpio_chip);
+ struct qpnp_pin_spec *q_spec;
+
+ if (WARN_ON(!q_chip))
+ return -ENODEV;
+
+ q_spec = qpnp_chip_gpio_get_spec(q_chip, offset);
+ if (WARN_ON(!q_spec))
+ return -ENODEV;
+
+ return qpnp_pin_set_mode(q_chip, q_spec, QPNP_PIN_MODE_DIG_IN);
+}
+
+static int qpnp_pin_direction_output(struct gpio_chip *gpio_chip,
+ unsigned int offset, int val)
+{
+ int rc;
+ struct qpnp_pin_chip *q_chip = gpiochip_get_data(gpio_chip);
+ struct qpnp_pin_spec *q_spec;
+
+ if (WARN_ON(!q_chip))
+ return -ENODEV;
+
+ q_spec = qpnp_chip_gpio_get_spec(q_chip, offset);
+ if (WARN_ON(!q_spec))
+ return -ENODEV;
+
+ rc = __qpnp_pin_set(q_chip, q_spec, val);
+ if (rc)
+ return rc;
+
+ rc = qpnp_pin_set_mode(q_chip, q_spec, QPNP_PIN_MODE_DIG_OUT);
+
+ return rc;
+}
+
+static int qpnp_pin_of_gpio_xlate(struct gpio_chip *gpio_chip,
+ const struct of_phandle_args *gpio_spec,
+ u32 *flags)
+{
+ struct qpnp_pin_chip *q_chip = gpiochip_get_data(gpio_chip);
+ struct qpnp_pin_spec *q_spec;
+
+ if (WARN_ON(gpio_chip->of_gpio_n_cells < 2)) {
+ pr_err("of_gpio_n_cells < 2\n");
+ return -EINVAL;
+ }
+
+ q_spec = qpnp_pmic_pin_get_spec(q_chip, gpio_spec->args[0]);
+ if (!q_spec) {
+ pr_err("no such PMIC gpio %u in device topology\n",
+ gpio_spec->args[0]);
+ return -EINVAL;
+ }
+
+ if (flags)
+ *flags = gpio_spec->args[1];
+
+ return q_spec->gpio_chip_idx;
+}
+
+static int qpnp_pin_apply_config(struct qpnp_pin_chip *q_chip,
+ struct qpnp_pin_spec *q_spec)
+{
+ struct qpnp_pin_cfg param;
+ struct device_node *node = q_spec->node;
+ int rc;
+ u8 shift, mask, *reg;
+
+ if (is_gpio_lv_mv(q_spec)) {
+ shift = Q_REG_LV_MV_MODE_SEL_SHIFT;
+ mask = Q_REG_LV_MV_MODE_SEL_MASK;
+ } else {
+ shift = Q_REG_MODE_SEL_SHIFT;
+ mask = Q_REG_MODE_SEL_MASK;
+ }
+ param.mode = q_reg_get(&q_spec->regs[Q_REG_I_MODE_CTL],
+ shift, mask);
+
+ param.output_type = q_reg_get(&q_spec->regs[Q_REG_I_DIG_OUT_CTL],
+ Q_REG_OUT_TYPE_SHIFT,
+ Q_REG_OUT_TYPE_MASK);
+
+ if (is_gpio_lv_mv(q_spec)) {
+ shift = Q_REG_DIG_OUT_SRC_INVERT_SHIFT;
+ mask = Q_REG_DIG_OUT_SRC_INVERT_MASK;
+ reg = &q_spec->regs[Q_REG_I_DIG_OUT_SRC_CTL];
+ } else {
+ shift = Q_REG_OUT_INVERT_SHIFT;
+ mask = Q_REG_OUT_INVERT_MASK;
+ reg = &q_spec->regs[Q_REG_I_MODE_CTL];
+ }
+ param.invert = q_reg_get(reg, shift, mask);
+
+ param.pull = q_reg_get(&q_spec->regs[Q_REG_I_DIG_PULL_CTL],
+ Q_REG_PULL_SHIFT, Q_REG_PULL_MASK);
+ param.vin_sel = q_reg_get(&q_spec->regs[Q_REG_I_DIG_VIN_CTL],
+ Q_REG_VIN_SHIFT, Q_REG_VIN_MASK);
+ param.out_strength = q_reg_get(&q_spec->regs[Q_REG_I_DIG_OUT_CTL],
+ Q_REG_OUT_STRENGTH_SHIFT,
+ Q_REG_OUT_STRENGTH_MASK);
+
+ if (is_gpio_lv_mv(q_spec)) {
+ shift = Q_REG_DIG_OUT_SRC_SRC_SEL_SHIFT;
+ mask = Q_REG_DIG_OUT_SRC_SRC_SEL_MASK;
+ reg = &q_spec->regs[Q_REG_I_DIG_OUT_SRC_CTL];
+ } else {
+ shift = Q_REG_SRC_SEL_SHIFT;
+ mask = Q_REG_SRC_SEL_MASK;
+ reg = &q_spec->regs[Q_REG_I_MODE_CTL];
+ }
+ param.src_sel = q_reg_get(reg, shift, mask);
+
+ param.master_en = q_reg_get(&q_spec->regs[Q_REG_I_EN_CTL],
+ Q_REG_MASTER_EN_SHIFT,
+ Q_REG_MASTER_EN_MASK);
+ param.aout_ref = q_reg_get(&q_spec->regs[Q_REG_I_AOUT_CTL],
+ Q_REG_AOUT_REF_SHIFT,
+ Q_REG_AOUT_REF_MASK);
+ param.ain_route = q_reg_get(&q_spec->regs[Q_REG_I_AIN_CTL],
+ Q_REG_AIN_ROUTE_SHIFT,
+ Q_REG_AIN_ROUTE_MASK);
+ param.cs_out = q_reg_get(&q_spec->regs[Q_REG_I_SINK_CTL],
+ Q_REG_CS_OUT_SHIFT,
+ Q_REG_CS_OUT_MASK);
+ param.apass_sel = q_reg_get(&q_spec->regs[Q_REG_I_APASS_SEL_CTL],
+ Q_REG_APASS_SEL_SHIFT,
+ Q_REG_APASS_SEL_MASK);
+ if (is_gpio_lv_mv(q_spec)) {
+ param.dtest_sel = q_reg_get(&q_spec->regs[Q_REG_I_DIG_IN_CTL],
+ Q_REG_LV_MV_DTEST_SEL_CFG_SHIFT,
+ Q_REG_LV_MV_DTEST_SEL_CFG_MASK);
+ } else {
+ param.dtest_sel = q_reg_get(&q_spec->regs[Q_REG_I_DIG_IN_CTL],
+ Q_REG_DTEST_SEL_SHIFT,
+ Q_REG_DTEST_SEL_MASK);
+ }
+
+ of_property_read_u32(node, "qcom,mode",
+ ¶m.mode);
+ of_property_read_u32(node, "qcom,output-type",
+ ¶m.output_type);
+ of_property_read_u32(node, "qcom,invert",
+ ¶m.invert);
+ of_property_read_u32(node, "qcom,pull",
+ ¶m.pull);
+ of_property_read_u32(node, "qcom,vin-sel",
+ ¶m.vin_sel);
+ of_property_read_u32(node, "qcom,out-strength",
+ ¶m.out_strength);
+ of_property_read_u32(node, "qcom,src-sel",
+ ¶m.src_sel);
+ of_property_read_u32(node, "qcom,master-en",
+ ¶m.master_en);
+ of_property_read_u32(node, "qcom,aout-ref",
+ ¶m.aout_ref);
+ of_property_read_u32(node, "qcom,ain-route",
+ ¶m.ain_route);
+ of_property_read_u32(node, "qcom,cs-out",
+ ¶m.cs_out);
+ of_property_read_u32(node, "qcom,apass-sel",
+ ¶m.apass_sel);
+ of_property_read_u32(node, "qcom,dtest-sel",
+ ¶m.dtest_sel);
+
+ rc = _qpnp_pin_config(q_chip, q_spec, ¶m);
+
+ return rc;
+}
+
+static int qpnp_pin_free_chip(struct qpnp_pin_chip *q_chip)
+{
+ int i, rc = 0;
+
+ if (q_chip->chip_gpios)
+ for (i = 0; i < q_chip->gpio_chip.ngpio; i++)
+ kfree(q_chip->chip_gpios[i]);
+
+ mutex_lock(&qpnp_pin_chips_lock);
+ list_del(&q_chip->chip_list);
+ mutex_unlock(&qpnp_pin_chips_lock);
+ if (q_chip->chip_registered)
+ gpiochip_remove(&q_chip->gpio_chip);
+
+ kfree(q_chip->chip_gpios);
+ kfree(q_chip->pmic_pins);
+ kfree(q_chip);
+ return rc;
+}
+
+#ifdef CONFIG_GPIO_QPNP_PIN_DEBUG
+struct qpnp_pin_reg {
+ uint32_t addr;
+ uint32_t idx;
+ uint32_t shift;
+ uint32_t mask;
+};
+
+static struct dentry *driver_dfs_dir;
+
+static int qpnp_pin_reg_attr(enum qpnp_pin_param_type type,
+ struct qpnp_pin_reg *cfg,
+ struct qpnp_pin_spec *q_spec)
+{
+ switch (type) {
+ case Q_PIN_CFG_MODE:
+ if (is_gpio_lv_mv(q_spec)) {
+ cfg->shift = Q_REG_LV_MV_MODE_SEL_SHIFT;
+ cfg->mask = Q_REG_LV_MV_MODE_SEL_MASK;
+ } else {
+ cfg->shift = Q_REG_MODE_SEL_SHIFT;
+ cfg->mask = Q_REG_MODE_SEL_MASK;
+ }
+ cfg->addr = Q_REG_MODE_CTL;
+ cfg->idx = Q_REG_I_MODE_CTL;
+ break;
+ case Q_PIN_CFG_OUTPUT_TYPE:
+ cfg->addr = Q_REG_DIG_OUT_CTL;
+ cfg->idx = Q_REG_I_DIG_OUT_CTL;
+ cfg->shift = Q_REG_OUT_TYPE_SHIFT;
+ cfg->mask = Q_REG_OUT_TYPE_MASK;
+ break;
+ case Q_PIN_CFG_INVERT:
+ if (is_gpio_lv_mv(q_spec)) {
+ cfg->addr = Q_REG_DIG_OUT_SRC_CTL;
+ cfg->idx = Q_REG_I_DIG_OUT_SRC_CTL;
+ cfg->shift = Q_REG_DIG_OUT_SRC_INVERT_SHIFT;
+ cfg->mask = Q_REG_DIG_OUT_SRC_INVERT_MASK;
+ } else {
+ cfg->addr = Q_REG_MODE_CTL;
+ cfg->idx = Q_REG_I_MODE_CTL;
+ cfg->shift = Q_REG_OUT_INVERT_SHIFT;
+ cfg->mask = Q_REG_OUT_INVERT_MASK;
+ }
+ break;
+ case Q_PIN_CFG_PULL:
+ cfg->addr = Q_REG_DIG_PULL_CTL;
+ cfg->idx = Q_REG_I_DIG_PULL_CTL;
+ cfg->shift = Q_REG_PULL_SHIFT;
+ cfg->mask = Q_REG_PULL_MASK;
+ break;
+ case Q_PIN_CFG_VIN_SEL:
+ cfg->addr = Q_REG_DIG_VIN_CTL;
+ cfg->idx = Q_REG_I_DIG_VIN_CTL;
+ cfg->shift = Q_REG_VIN_SHIFT;
+ cfg->mask = Q_REG_VIN_MASK;
+ break;
+ case Q_PIN_CFG_OUT_STRENGTH:
+ cfg->addr = Q_REG_DIG_OUT_CTL;
+ cfg->idx = Q_REG_I_DIG_OUT_CTL;
+ cfg->shift = Q_REG_OUT_STRENGTH_SHIFT;
+ cfg->mask = Q_REG_OUT_STRENGTH_MASK;
+ break;
+ case Q_PIN_CFG_SRC_SEL:
+ if (is_gpio_lv_mv(q_spec)) {
+ cfg->addr = Q_REG_DIG_OUT_SRC_CTL;
+ cfg->idx = Q_REG_I_DIG_OUT_SRC_CTL;
+ cfg->shift = Q_REG_DIG_OUT_SRC_SRC_SEL_SHIFT;
+ cfg->mask = Q_REG_DIG_OUT_SRC_SRC_SEL_MASK;
+ } else {
+ cfg->addr = Q_REG_MODE_CTL;
+ cfg->idx = Q_REG_I_MODE_CTL;
+ cfg->shift = Q_REG_SRC_SEL_SHIFT;
+ cfg->mask = Q_REG_SRC_SEL_MASK;
+ }
+ break;
+ case Q_PIN_CFG_MASTER_EN:
+ cfg->addr = Q_REG_EN_CTL;
+ cfg->idx = Q_REG_I_EN_CTL;
+ cfg->shift = Q_REG_MASTER_EN_SHIFT;
+ cfg->mask = Q_REG_MASTER_EN_MASK;
+ break;
+ case Q_PIN_CFG_AOUT_REF:
+ cfg->addr = Q_REG_AOUT_CTL;
+ cfg->idx = Q_REG_I_AOUT_CTL;
+ cfg->shift = Q_REG_AOUT_REF_SHIFT;
+ cfg->mask = Q_REG_AOUT_REF_MASK;
+ break;
+ case Q_PIN_CFG_AIN_ROUTE:
+ cfg->addr = Q_REG_AIN_CTL;
+ cfg->idx = Q_REG_I_AIN_CTL;
+ cfg->shift = Q_REG_AIN_ROUTE_SHIFT;
+ cfg->mask = Q_REG_AIN_ROUTE_MASK;
+ break;
+ case Q_PIN_CFG_CS_OUT:
+ cfg->addr = Q_REG_SINK_CTL;
+ cfg->idx = Q_REG_I_SINK_CTL;
+ cfg->shift = Q_REG_CS_OUT_SHIFT;
+ cfg->mask = Q_REG_CS_OUT_MASK;
+ break;
+ case Q_PIN_CFG_APASS_SEL:
+ cfg->addr = Q_REG_APASS_SEL_CTL;
+ cfg->idx = Q_REG_I_APASS_SEL_CTL;
+ cfg->shift = Q_REG_APASS_SEL_SHIFT;
+ cfg->mask = Q_REG_APASS_SEL_MASK;
+ break;
+ case Q_PIN_CFG_DTEST_SEL:
+ if (is_gpio_lv_mv(q_spec)) {
+ cfg->shift = Q_REG_LV_MV_DTEST_SEL_CFG_SHIFT;
+ cfg->mask = Q_REG_LV_MV_DTEST_SEL_CFG_MASK;
+ } else {
+ cfg->shift = Q_REG_DTEST_SEL_SHIFT;
+ cfg->mask = Q_REG_DTEST_SEL_MASK;
+ }
+ cfg->addr = Q_REG_DIG_IN_CTL;
+ cfg->idx = Q_REG_I_DIG_IN_CTL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int qpnp_pin_debugfs_get(void *data, u64 *val)
+{
+ enum qpnp_pin_param_type *idx = data;
+ struct qpnp_pin_spec *q_spec;
+ struct qpnp_pin_reg cfg = {};
+ int rc;
+
+ q_spec = container_of(idx, struct qpnp_pin_spec, params[*idx]);
+
+ rc = qpnp_pin_reg_attr(*idx, &cfg, q_spec);
+ if (rc)
+ return rc;
+
+ *val = q_reg_get(&q_spec->regs[cfg.idx], cfg.shift, cfg.mask);
+ return 0;
+}
+
+static int qpnp_pin_debugfs_set(void *data, u64 val)
+{
+ enum qpnp_pin_param_type *idx = data;
+ struct qpnp_pin_spec *q_spec;
+ struct qpnp_pin_chip *q_chip;
+ struct qpnp_pin_reg cfg = {};
+ int rc;
+
+ q_spec = container_of(idx, struct qpnp_pin_spec, params[*idx]);
+ q_chip = q_spec->q_chip;
+
+ /*
+ * special handling for GPIO_LV/MV 'dtest-sel'
+ * if (dtest_sel == 0) then disable dtest-sel
+ * else enable and set dtest.
+ */
+ if ((q_spec->subtype == Q_GPIO_SUBTYPE_GPIO_LV ||
+ q_spec->subtype == Q_GPIO_SUBTYPE_GPIO_MV) &&
+ *idx == Q_PIN_CFG_DTEST_SEL) {
+ /* enable/disable DTEST */
+ cfg.shift = Q_REG_LV_MV_DTEST_SEL_EN_SHIFT;
+ cfg.mask = Q_REG_LV_MV_DTEST_SEL_EN_MASK;
+ cfg.addr = Q_REG_DIG_IN_CTL;
+ cfg.idx = Q_REG_I_DIG_IN_CTL;
+ q_reg_clr_set(&q_spec->regs[cfg.idx],
+ cfg.shift, cfg.mask, !!val);
+ }
+
+ rc = qpnp_pin_check_config(*idx, q_spec, val);
+ if (rc)
+ return rc;
+
+ rc = qpnp_pin_reg_attr(*idx, &cfg, q_spec);
+ if (rc)
+ return rc;
+
+ if (*idx == Q_PIN_CFG_DTEST_SEL && val) {
+ if (is_gpio_lv_mv(q_spec))
+ val -= 1;
+ else
+ val = BIT(val - 1);
+ }
+
+ q_reg_clr_set(&q_spec->regs[cfg.idx], cfg.shift, cfg.mask, val);
+ rc = regmap_write(q_chip->regmap, Q_REG_ADDR(q_spec, cfg.addr),
+ *&q_spec->regs[cfg.idx]);
+
+ return rc;
+}
+DEFINE_SIMPLE_ATTRIBUTE(qpnp_pin_fops, qpnp_pin_debugfs_get,
+ qpnp_pin_debugfs_set, "%llu\n");
+
+#define DEBUGFS_BUF_SIZE 11 /* supports 2^32 in decimal */
+
+struct qpnp_pin_debugfs_args {
+ enum qpnp_pin_param_type type;
+ const char *filename;
+};
+
+static struct qpnp_pin_debugfs_args dfs_args[Q_NUM_PARAMS] = {
+ { Q_PIN_CFG_MODE, "mode" },
+ { Q_PIN_CFG_OUTPUT_TYPE, "output_type" },
+ { Q_PIN_CFG_INVERT, "invert" },
+ { Q_PIN_CFG_PULL, "pull" },
+ { Q_PIN_CFG_VIN_SEL, "vin_sel" },
+ { Q_PIN_CFG_OUT_STRENGTH, "out_strength" },
+ { Q_PIN_CFG_SRC_SEL, "src_sel" },
+ { Q_PIN_CFG_MASTER_EN, "master_en" },
+ { Q_PIN_CFG_AOUT_REF, "aout_ref" },
+ { Q_PIN_CFG_AIN_ROUTE, "ain_route" },
+ { Q_PIN_CFG_CS_OUT, "cs_out" },
+ { Q_PIN_CFG_APASS_SEL, "apass_sel" },
+ { Q_PIN_CFG_DTEST_SEL, "dtest-sel" },
+};
+
+static int qpnp_pin_debugfs_create(struct qpnp_pin_chip *q_chip)
+{
+ struct platform_device *pdev = q_chip->pdev;
+ struct device *dev = &pdev->dev;
+ struct qpnp_pin_spec *q_spec;
+ enum qpnp_pin_param_type *params;
+ enum qpnp_pin_param_type type;
+ char pmic_pin[DEBUGFS_BUF_SIZE];
+ const char *filename;
+ struct dentry *dfs, *dfs_io_dir;
+ int i, j, rc;
+
+ q_chip->dfs_dir = debugfs_create_dir(q_chip->gpio_chip.label,
+ driver_dfs_dir);
+ if (q_chip->dfs_dir == NULL) {
+ dev_err(dev, "%s: cannot register chip debugfs directory %s\n",
+ __func__, dev->of_node->name);
+ return -ENODEV;
+ }
+
+ for (i = 0; i < q_chip->gpio_chip.ngpio; i++) {
+ q_spec = qpnp_chip_gpio_get_spec(q_chip, i);
+ params = q_spec->params;
+ snprintf(pmic_pin, DEBUGFS_BUF_SIZE, "%u", q_spec->pmic_pin);
+ dfs_io_dir = debugfs_create_dir(pmic_pin, q_chip->dfs_dir);
+ if (dfs_io_dir == NULL)
+ goto dfs_err;
+
+ for (j = 0; j < Q_NUM_PARAMS; j++) {
+ type = dfs_args[j].type;
+ filename = dfs_args[j].filename;
+
+ /*
+ * Use a value of '0' to see if the pin has even basic
+ * support for a function. Do not create a file if
+ * it doesn't.
+ */
+ rc = qpnp_pin_check_config(type, q_spec, 0);
+ if (rc == -ENXIO)
+ continue;
+
+ params[type] = type;
+ dfs = debugfs_create_file(filename, 0644, dfs_io_dir,
+ &q_spec->params[type], &qpnp_pin_fops);
+ if (dfs == NULL)
+ goto dfs_err;
+ }
+ }
+ return 0;
+dfs_err:
+ dev_err(dev, "%s: cannot register debugfs for pmic gpio %u on chip %s\n",
+ __func__, q_spec->pmic_pin, dev->of_node->name);
+ debugfs_remove_recursive(q_chip->dfs_dir);
+ return -ENFILE;
+}
+#else
+static int qpnp_pin_debugfs_create(struct qpnp_pin_chip *q_chip)
+{
+ return 0;
+}
+#endif
+
+static int qpnp_pin_is_valid_pin(struct qpnp_pin_spec *q_spec)
+{
+ if (q_spec->type == Q_GPIO_TYPE)
+ switch (q_spec->subtype) {
+ case Q_GPIO_SUBTYPE_GPIO_4CH:
+ case Q_GPIO_SUBTYPE_GPIOC_4CH:
+ case Q_GPIO_SUBTYPE_GPIO_8CH:
+ case Q_GPIO_SUBTYPE_GPIOC_8CH:
+ case Q_GPIO_SUBTYPE_GPIO_LV:
+ case Q_GPIO_SUBTYPE_GPIO_MV:
+ return 1;
+ }
+ else if (q_spec->type == Q_MPP_TYPE)
+ switch (q_spec->subtype) {
+ case Q_MPP_SUBTYPE_4CH_NO_ANA_OUT:
+ case Q_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT:
+ case Q_MPP_SUBTYPE_4CH_NO_SINK:
+ case Q_MPP_SUBTYPE_ULT_4CH_NO_SINK:
+ case Q_MPP_SUBTYPE_4CH_FULL_FUNC:
+ case Q_MPP_SUBTYPE_8CH_FULL_FUNC:
+ return 1;
+ }
+
+ return 0;
+}
+
+static int qpnp_pin_probe(struct platform_device *pdev)
+{
+ struct qpnp_pin_chip *q_chip;
+ struct qpnp_pin_spec *q_spec;
+ unsigned int base;
+ struct device_node *child;
+ int i, rc;
+ u32 lowest_gpio = UINT_MAX, highest_gpio = 0;
+ u32 gpio;
+ char version[Q_REG_SUBTYPE - Q_REG_DIG_MAJOR_REV + 1];
+ const char *pin_dev_name;
+
+ pin_dev_name = dev_name(&pdev->dev);
+ if (!pin_dev_name) {
+ dev_err(&pdev->dev,
+ "%s: label binding undefined for node %s\n",
+ __func__,
+ pdev->dev.of_node->full_name);
+ return -EINVAL;
+ }
+
+ q_chip = kzalloc(sizeof(*q_chip), GFP_KERNEL);
+ if (!q_chip)
+ return -ENOMEM;
+
+ q_chip->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!q_chip->regmap) {
+ dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+ return -EINVAL;
+ }
+ q_chip->pdev = pdev;
+ dev_set_drvdata(&pdev->dev, q_chip);
+
+ mutex_lock(&qpnp_pin_chips_lock);
+ list_add(&q_chip->chip_list, &qpnp_pin_chips);
+ mutex_unlock(&qpnp_pin_chips_lock);
+
+ /* first scan through nodes to find the range required for allocation */
+ i = 0;
+ for_each_available_child_of_node(pdev->dev.of_node, child) {
+ rc = of_property_read_u32(child, "qcom,pin-num", &gpio);
+ if (rc) {
+ dev_err(&pdev->dev,
+ "%s: unable to get qcom,pin-num property\n",
+ __func__);
+ goto err_probe;
+ }
+
+ if (gpio < lowest_gpio)
+ lowest_gpio = gpio;
+ if (gpio > highest_gpio)
+ highest_gpio = gpio;
+ i++;
+ }
+ q_chip->gpio_chip.ngpio = i;
+
+ if (highest_gpio < lowest_gpio) {
+ dev_err(&pdev->dev,
+ "%s: no device nodes specified in topology\n",
+ __func__);
+ rc = -EINVAL;
+ goto err_probe;
+ } else if (lowest_gpio == 0) {
+ dev_err(&pdev->dev, "%s: 0 is not a valid PMIC GPIO\n",
+ __func__);
+ rc = -EINVAL;
+ goto err_probe;
+ }
+
+ q_chip->pmic_pin_lowest = lowest_gpio;
+ q_chip->pmic_pin_highest = highest_gpio;
+
+ /* allocate gpio lookup tables */
+ q_chip->pmic_pins = kzalloc(sizeof(struct qpnp_pin_spec *) *
+ (highest_gpio - lowest_gpio + 1),
+ GFP_KERNEL);
+ q_chip->chip_gpios = kzalloc(sizeof(struct qpnp_pin_spec *) *
+ q_chip->gpio_chip.ngpio,
+ GFP_KERNEL);
+ if (!q_chip->pmic_pins || !q_chip->chip_gpios) {
+ dev_err(&pdev->dev, "%s: unable to allocate memory\n",
+ __func__);
+ rc = -ENOMEM;
+ goto err_probe;
+ }
+
+ /* get interrupt controller device_node */
+ q_chip->int_ctrl = of_irq_find_parent(pdev->dev.of_node);
+ if (!q_chip->int_ctrl) {
+ dev_err(&pdev->dev, "%s: Can't find interrupt parent\n",
+ __func__);
+ rc = -EINVAL;
+ goto err_probe;
+ }
+ i = 0;
+ /* now scan through again and populate the lookup table */
+ for_each_available_child_of_node(pdev->dev.of_node, child) {
+ rc = of_property_read_u32(child, "reg", &base);
+ if (rc < 0) {
+ dev_err(&pdev->dev,
+ "Couldn't find reg in node = %s rc = %d\n",
+ child->full_name, rc);
+ goto err_probe;
+ }
+
+ rc = of_property_read_u32(child, "qcom,pin-num", &gpio);
+ if (rc) {
+ dev_err(&pdev->dev,
+ "%s: unable to get qcom,pin-num property\n",
+ __func__);
+ goto err_probe;
+ }
+
+ q_spec = kzalloc(sizeof(struct qpnp_pin_spec), GFP_KERNEL);
+ if (!q_spec) {
+ rc = -ENOMEM;
+ goto err_probe;
+ }
+
+ q_spec->slave = to_spmi_device(pdev->dev.parent)->usid;
+ q_spec->offset = base;
+ q_spec->gpio_chip_idx = i;
+ q_spec->pmic_pin = gpio;
+ q_spec->node = child;
+ q_spec->q_chip = q_chip;
+
+ rc = regmap_bulk_read(q_chip->regmap,
+ Q_REG_ADDR(q_spec, Q_REG_DIG_MAJOR_REV),
+ &version[0],
+ ARRAY_SIZE(version));
+ if (rc) {
+ dev_err(&pdev->dev, "%s: unable to read type regs\n",
+ __func__);
+ goto err_probe;
+ }
+ q_spec->dig_major_rev = version[Q_REG_DIG_MAJOR_REV -
+ Q_REG_DIG_MAJOR_REV];
+ q_spec->type = version[Q_REG_TYPE - Q_REG_DIG_MAJOR_REV];
+ q_spec->subtype = version[Q_REG_SUBTYPE - Q_REG_DIG_MAJOR_REV];
+
+ if (!qpnp_pin_is_valid_pin(q_spec)) {
+ dev_err(&pdev->dev,
+ "%s: invalid pin type (type=0x%x subtype=0x%x)\n",
+ __func__, q_spec->type, q_spec->subtype);
+ goto err_probe;
+ }
+
+ rc = qpnp_pin_ctl_regs_init(q_spec);
+ if (rc)
+ goto err_probe;
+
+ /* initialize lookup table params */
+ qpnp_pmic_pin_set_spec(q_chip, gpio, q_spec);
+ qpnp_chip_gpio_set_spec(q_chip, i, q_spec);
+ i++;
+ }
+
+ q_chip->gpio_chip.base = -1;
+ q_chip->gpio_chip.label = pin_dev_name;
+ q_chip->gpio_chip.direction_input = qpnp_pin_direction_input;
+ q_chip->gpio_chip.direction_output = qpnp_pin_direction_output;
+ q_chip->gpio_chip.to_irq = qpnp_pin_to_irq;
+ q_chip->gpio_chip.get = qpnp_pin_get;
+ q_chip->gpio_chip.set = qpnp_pin_set;
+ q_chip->gpio_chip.parent = &pdev->dev;
+ q_chip->gpio_chip.of_xlate = qpnp_pin_of_gpio_xlate;
+ q_chip->gpio_chip.of_gpio_n_cells = 2;
+ q_chip->gpio_chip.can_sleep = 0;
+
+ rc = gpiochip_add_data(&q_chip->gpio_chip, q_chip);
+ if (rc) {
+ dev_err(&pdev->dev, "%s: Can't add gpio chip, rc = %d\n",
+ __func__, rc);
+ goto err_probe;
+ }
+
+ q_chip->chip_registered = true;
+ /* now configure gpio config defaults if they exist */
+ for (i = 0; i < q_chip->gpio_chip.ngpio; i++) {
+ q_spec = qpnp_chip_gpio_get_spec(q_chip, i);
+ if (WARN_ON(!q_spec)) {
+ rc = -ENODEV;
+ goto err_probe;
+ }
+
+ rc = qpnp_pin_cache_regs(q_chip, q_spec);
+ if (rc)
+ goto err_probe;
+
+ rc = qpnp_pin_apply_config(q_chip, q_spec);
+ if (rc)
+ goto err_probe;
+ }
+
+ dev_dbg(&pdev->dev, "%s: gpio_chip registered between %d-%u\n",
+ __func__, q_chip->gpio_chip.base,
+ (q_chip->gpio_chip.base + q_chip->gpio_chip.ngpio) - 1);
+
+ rc = qpnp_pin_debugfs_create(q_chip);
+ if (rc) {
+ dev_err(&pdev->dev, "%s: debugfs creation failed\n",
+ __func__);
+ goto err_probe;
+ }
+
+ return 0;
+
+err_probe:
+ qpnp_pin_free_chip(q_chip);
+ return rc;
+}
+
+static int qpnp_pin_remove(struct platform_device *pdev)
+{
+ struct qpnp_pin_chip *q_chip = dev_get_drvdata(&pdev->dev);
+
+ debugfs_remove_recursive(q_chip->dfs_dir);
+
+ return qpnp_pin_free_chip(q_chip);
+}
+
+static const struct of_device_id spmi_match_table[] = {
+ { .compatible = "qcom,qpnp-pin",
+ },
+ {}
+};
+
+static const struct platform_device_id qpnp_pin_id[] = {
+ { "qcom,qpnp-pin", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(spmi, qpnp_pin_id);
+
+static struct platform_driver qpnp_pin_driver = {
+ .driver = {
+ .name = "qcom,qpnp-pin",
+ .of_match_table = spmi_match_table,
+ },
+ .probe = qpnp_pin_probe,
+ .remove = qpnp_pin_remove,
+ .id_table = qpnp_pin_id,
+};
+
+static int __init qpnp_pin_init(void)
+{
+#ifdef CONFIG_GPIO_QPNP_PIN_DEBUG
+ driver_dfs_dir = debugfs_create_dir("qpnp_pin", NULL);
+ if (driver_dfs_dir == NULL)
+ pr_err("Cannot register top level debugfs directory\n");
+#endif
+
+ return platform_driver_register(&qpnp_pin_driver);
+}
+
+static void __exit qpnp_pin_exit(void)
+{
+#ifdef CONFIG_GPIO_QPNP_PIN_DEBUG
+ debugfs_remove_recursive(driver_dfs_dir);
+#endif
+ platform_driver_unregister(&qpnp_pin_driver);
+}
+
+MODULE_DESCRIPTION("QPNP PMIC gpio driver");
+MODULE_LICENSE("GPL v2");
+
+subsys_initcall(qpnp_pin_init);
+module_exit(qpnp_pin_exit);
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 99c0514..2efd8d0 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -408,6 +408,15 @@
To compile this driver as a module, choose M here: the module will
be called qcom-spmi-vadc.
+config QCOM_TADC
+ tristate "Qualcomm Technologies, Inc. TADC driver"
+ depends on MFD_I2C_PMIC
+ help
+ Say yes here to support the Qualcomm Technologies, Inc. telemetry ADC.
+ The TADC provides battery temperature, skin temperature,
+ die temperature, battery voltage, battery current, input voltage,
+ input current, and OTG current.
+
config ROCKCHIP_SARADC
tristate "Rockchip SARADC driver"
depends on ARCH_ROCKCHIP || (ARM && COMPILE_TEST)
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 7a40c04..1da80bd 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -39,6 +39,7 @@
obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
+obj-$(CONFIG_QCOM_TADC) += qcom-tadc.o
obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
obj-$(CONFIG_STX104) += stx104.o
obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
diff --git a/drivers/iio/adc/qcom-tadc.c b/drivers/iio/adc/qcom-tadc.c
new file mode 100644
index 0000000..9241288
--- /dev/null
+++ b/drivers/iio/adc/qcom-tadc.c
@@ -0,0 +1,1089 @@
+/* Copyright (c) 2016-2017, The Linux Foundation. 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.
+ */
+
+#define pr_fmt(fmt) "TADC: %s: " fmt, __func__
+
+#include <linux/iio/iio.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define TADC_REVISION1_REG 0x00
+#define TADC_REVISION2_REG 0x01
+#define TADC_REVISION3_REG 0x02
+#define TADC_REVISION4_REG 0x03
+#define TADC_PERPH_TYPE_REG 0x04
+#define TADC_PERPH_SUBTYPE_REG 0x05
+
+/* TADC register definitions */
+#define TADC_SW_CH_CONV_REG(chip) (chip->tadc_base + 0x06)
+#define TADC_MBG_ERR_REG(chip) (chip->tadc_base + 0x07)
+#define TADC_EN_CTL_REG(chip) (chip->tadc_base + 0x46)
+#define TADC_CONV_REQ_REG(chip) (chip->tadc_base + 0x51)
+#define TADC_HWTRIG_CONV_CH_EN_REG(chip) (chip->tadc_base + 0x52)
+#define TADC_HW_SETTLE_DELAY_REG(chip) (chip->tadc_base + 0x53)
+#define TADC_LONG_HW_SETTLE_DLY_EN_REG(chip) (chip->tadc_base + 0x54)
+#define TADC_LONG_HW_SETTLE_DLY_REG(chip) (chip->tadc_base + 0x55)
+#define TADC_ADC_BUF_CH_REG(chip) (chip->tadc_base + 0x56)
+#define TADC_ADC_AAF_CH_REG(chip) (chip->tadc_base + 0x57)
+#define TADC_ADC_DATA_RDBK_REG(chip) (chip->tadc_base + 0x58)
+#define TADC_CH1_ADC_LO_REG(chip) (chip->tadc_base + 0x60)
+#define TADC_CH1_ADC_HI_REG(chip) (chip->tadc_base + 0x61)
+#define TADC_CH2_ADC_LO_REG(chip) (chip->tadc_base + 0x62)
+#define TADC_CH2_ADC_HI_REG(chip) (chip->tadc_base + 0x63)
+#define TADC_CH3_ADC_LO_REG(chip) (chip->tadc_base + 0x64)
+#define TADC_CH3_ADC_HI_REG(chip) (chip->tadc_base + 0x65)
+#define TADC_CH4_ADC_LO_REG(chip) (chip->tadc_base + 0x66)
+#define TADC_CH4_ADC_HI_REG(chip) (chip->tadc_base + 0x67)
+#define TADC_CH5_ADC_LO_REG(chip) (chip->tadc_base + 0x68)
+#define TADC_CH5_ADC_HI_REG(chip) (chip->tadc_base + 0x69)
+#define TADC_CH6_ADC_LO_REG(chip) (chip->tadc_base + 0x70)
+#define TADC_CH6_ADC_HI_REG(chip) (chip->tadc_base + 0x71)
+#define TADC_CH7_ADC_LO_REG(chip) (chip->tadc_base + 0x72)
+#define TADC_CH7_ADC_HI_REG(chip) (chip->tadc_base + 0x73)
+#define TADC_CH8_ADC_LO_REG(chip) (chip->tadc_base + 0x74)
+#define TADC_CH8_ADC_HI_REG(chip) (chip->tadc_base + 0x75)
+
+/* TADC_CMP register definitions */
+#define TADC_CMP_THR1_CMP_REG(chip) (chip->tadc_cmp_base + 0x51)
+#define TADC_CMP_THR1_CH1_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x52)
+#define TADC_CMP_THR1_CH1_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x53)
+#define TADC_CMP_THR1_CH2_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x54)
+#define TADC_CMP_THR1_CH2_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x55)
+#define TADC_CMP_THR1_CH3_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x56)
+#define TADC_CMP_THR1_CH3_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x57)
+#define TADC_CMP_THR2_CMP_REG(chip) (chip->tadc_cmp_base + 0x67)
+#define TADC_CMP_THR2_CH1_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x68)
+#define TADC_CMP_THR2_CH1_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x69)
+#define TADC_CMP_THR2_CH2_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x6A)
+#define TADC_CMP_THR2_CH2_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x6B)
+#define TADC_CMP_THR2_CH3_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x6C)
+#define TADC_CMP_THR2_CH3_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x6D)
+#define TADC_CMP_THR3_CMP_REG(chip) (chip->tadc_cmp_base + 0x7D)
+#define TADC_CMP_THR3_CH1_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x7E)
+#define TADC_CMP_THR3_CH1_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x7F)
+#define TADC_CMP_THR3_CH2_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x80)
+#define TADC_CMP_THR3_CH2_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x81)
+#define TADC_CMP_THR3_CH3_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x82)
+#define TADC_CMP_THR3_CH3_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x83)
+#define TADC_CMP_THR4_CMP_REG(chip) (chip->tadc_cmp_base + 0x93)
+#define TADC_CMP_THR4_CH1_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x94)
+#define TADC_CMP_THR4_CH1_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x95)
+#define TADC_CMP_THR1_CH1_HYST_REG(chip) (chip->tadc_cmp_base + 0xB0)
+#define TADC_CMP_THR2_CH1_HYST_REG(chip) (chip->tadc_cmp_base + 0xB1)
+#define TADC_CMP_THR3_CH1_HYST_REG(chip) (chip->tadc_cmp_base + 0xB2)
+#define TADC_CMP_THR4_CH1_HYST_REG(chip) (chip->tadc_cmp_base + 0xB3)
+
+/* 10 bits of resolution */
+#define TADC_RESOLUTION 1024
+/* number of hardware channels */
+#define TADC_NUM_CH 8
+
+enum tadc_chan_id {
+ TADC_THERM1 = 0,
+ TADC_THERM2,
+ TADC_DIE_TEMP,
+ TADC_BATT_I,
+ TADC_BATT_V,
+ TADC_INPUT_I,
+ TADC_INPUT_V,
+ TADC_OTG_I,
+ /* virtual channels */
+ TADC_BATT_P,
+ TADC_INPUT_P,
+ TADC_THERM1_THR1,
+ TADC_THERM1_THR2,
+ TADC_THERM1_THR3,
+ TADC_THERM1_THR4,
+ TADC_THERM2_THR1,
+ TADC_THERM2_THR2,
+ TADC_THERM2_THR3,
+ TADC_DIE_TEMP_THR1,
+ TADC_DIE_TEMP_THR2,
+ TADC_DIE_TEMP_THR3,
+ TADC_CHAN_ID_MAX,
+};
+
+#define TADC_CHAN(_name, _type, _channel, _info_mask) \
+{ \
+ .type = _type, \
+ .channel = _channel, \
+ .info_mask_separate = _info_mask, \
+ .extend_name = _name, \
+}
+
+#define TADC_THERM_CHAN(_name, _channel) \
+TADC_CHAN(_name, IIO_TEMP, _channel, \
+ BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_PROCESSED))
+
+#define TADC_TEMP_CHAN(_name, _channel) \
+TADC_CHAN(_name, IIO_TEMP, _channel, \
+ BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_PROCESSED) | \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_OFFSET))
+
+#define TADC_CURRENT_CHAN(_name, _channel) \
+TADC_CHAN(_name, IIO_CURRENT, _channel, \
+ BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_PROCESSED) | \
+ BIT(IIO_CHAN_INFO_SCALE))
+
+
+#define TADC_VOLTAGE_CHAN(_name, _channel) \
+TADC_CHAN(_name, IIO_VOLTAGE, _channel, \
+ BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_PROCESSED) | \
+ BIT(IIO_CHAN_INFO_SCALE))
+
+#define TADC_POWER_CHAN(_name, _channel) \
+TADC_CHAN(_name, IIO_POWER, _channel, \
+ BIT(IIO_CHAN_INFO_PROCESSED))
+
+static const struct iio_chan_spec tadc_iio_chans[] = {
+ [TADC_THERM1] = TADC_THERM_CHAN(
+ "batt", TADC_THERM1),
+ [TADC_THERM2] = TADC_THERM_CHAN(
+ "skin", TADC_THERM2),
+ [TADC_DIE_TEMP] = TADC_TEMP_CHAN(
+ "die", TADC_DIE_TEMP),
+ [TADC_BATT_I] = TADC_CURRENT_CHAN(
+ "batt", TADC_BATT_I),
+ [TADC_BATT_V] = TADC_VOLTAGE_CHAN(
+ "batt", TADC_BATT_V),
+ [TADC_INPUT_I] = TADC_CURRENT_CHAN(
+ "input", TADC_INPUT_I),
+ [TADC_INPUT_V] = TADC_VOLTAGE_CHAN(
+ "input", TADC_INPUT_V),
+ [TADC_OTG_I] = TADC_CURRENT_CHAN(
+ "otg", TADC_OTG_I),
+ [TADC_BATT_P] = TADC_POWER_CHAN(
+ "batt", TADC_BATT_P),
+ [TADC_INPUT_P] = TADC_POWER_CHAN(
+ "input", TADC_INPUT_P),
+ [TADC_THERM1_THR1] = TADC_THERM_CHAN(
+ "batt_warm", TADC_THERM1_THR1),
+ [TADC_THERM1_THR2] = TADC_THERM_CHAN(
+ "batt_cool", TADC_THERM1_THR2),
+ [TADC_THERM1_THR3] = TADC_THERM_CHAN(
+ "batt_cold", TADC_THERM1_THR3),
+ [TADC_THERM1_THR4] = TADC_THERM_CHAN(
+ "batt_hot", TADC_THERM1_THR4),
+ [TADC_THERM2_THR1] = TADC_THERM_CHAN(
+ "skin_lb", TADC_THERM2_THR1),
+ [TADC_THERM2_THR2] = TADC_THERM_CHAN(
+ "skin_ub", TADC_THERM2_THR2),
+ [TADC_THERM2_THR3] = TADC_THERM_CHAN(
+ "skin_rst", TADC_THERM2_THR3),
+ [TADC_DIE_TEMP_THR1] = TADC_THERM_CHAN(
+ "die_lb", TADC_DIE_TEMP_THR1),
+ [TADC_DIE_TEMP_THR2] = TADC_THERM_CHAN(
+ "die_ub", TADC_DIE_TEMP_THR2),
+ [TADC_DIE_TEMP_THR3] = TADC_THERM_CHAN(
+ "die_rst", TADC_DIE_TEMP_THR3),
+};
+
+struct tadc_therm_thr {
+ int addr_lo;
+ int addr_hi;
+};
+
+struct tadc_chan_data {
+ s32 scale;
+ s32 offset;
+ u32 rbias;
+ const struct tadc_pt *table;
+ size_t tablesize;
+ struct tadc_therm_thr thr[4];
+};
+
+struct tadc_chip {
+ struct device *dev;
+ struct regmap *regmap;
+ u32 tadc_base;
+ u32 tadc_cmp_base;
+ struct tadc_chan_data chans[TADC_NUM_CH];
+ struct completion eoc_complete;
+ struct mutex write_lock;
+};
+
+struct tadc_pt {
+ s32 x;
+ s32 y;
+};
+
+/*
+ * Thermistor tables are generated by the B-parameter equation which is a
+ * simplifed version of the Steinhart-Hart equation.
+ *
+ * (1 / T) = (1 / T0) + (1 / B) * ln(R / R0)
+ *
+ * Where R0 is the resistance at temperature T0, and T0 is typically room
+ * temperature (25C).
+ */
+static const struct tadc_pt tadc_therm_3450b_68k[] = {
+ { 4151, 120000 },
+ { 4648, 115000 },
+ { 5220, 110000 },
+ { 5880, 105000 },
+ { 6644, 100000 },
+ { 7533, 95000 },
+ { 8571, 90000 },
+ { 9786, 85000 },
+ { 11216, 80000 },
+ { 12906, 75000 },
+ { 14910, 70000 },
+ { 17300, 65000 },
+ { 20163, 60000 },
+ { 23609, 55000 },
+ { 27780, 50000 },
+ { 32855, 45000 },
+ { 39065, 40000 },
+ { 46712, 35000 },
+ { 56185, 30000 },
+ { 68000, 25000 },
+ { 82837, 20000 },
+ { 101604, 15000 },
+ { 125525, 10000 },
+ { 156261, 5000 },
+ { 196090, 0 },
+ { 248163, -5000 },
+ { 316887, -10000 },
+ { 408493, -15000 },
+ { 531889, -20000 },
+ { 699966, -25000 },
+ { 931618, -30000 },
+ { 1254910, -35000 },
+ { 1712127, -40000 },
+};
+
+static bool tadc_is_reg_locked(struct tadc_chip *chip, u16 reg)
+{
+ if ((reg & 0xFF00) == chip->tadc_cmp_base)
+ return true;
+
+ if (reg == TADC_HWTRIG_CONV_CH_EN_REG(chip))
+ return true;
+
+ return false;
+}
+
+static int tadc_read(struct tadc_chip *chip, u16 reg, u8 *val, size_t count)
+{
+ int rc = 0;
+
+ rc = regmap_bulk_read(chip->regmap, reg, val, count);
+ if (rc < 0)
+ pr_err("Couldn't read 0x%04x rc=%d\n", reg, rc);
+
+ return rc;
+}
+
+static int tadc_write(struct tadc_chip *chip, u16 reg, u8 data)
+{
+ int rc = 0;
+
+ mutex_lock(&chip->write_lock);
+ if (tadc_is_reg_locked(chip, reg)) {
+ rc = regmap_write(chip->regmap, (reg & 0xFF00) | 0xD0, 0xA5);
+ if (rc < 0) {
+ pr_err("Couldn't unlock secure register rc=%d\n", rc);
+ goto unlock;
+ }
+ }
+
+ rc = regmap_write(chip->regmap, reg, data);
+ if (rc < 0) {
+ pr_err("Couldn't write 0x%02x to 0x%04x rc=%d\n",
+ data, reg, rc);
+ goto unlock;
+ }
+
+unlock:
+ mutex_unlock(&chip->write_lock);
+ return rc;
+}
+static int tadc_bulk_write(struct tadc_chip *chip, u16 reg, u8 *data,
+ size_t count)
+{
+ int rc = 0, i;
+
+ mutex_lock(&chip->write_lock);
+ for (i = 0; i < count; ++i, ++reg) {
+ if (tadc_is_reg_locked(chip, reg)) {
+ rc = regmap_write(chip->regmap,
+ (reg & 0xFF00) | 0xD0, 0xA5);
+ if (rc < 0) {
+ pr_err("Couldn't unlock secure register rc=%d\n",
+ rc);
+ goto unlock;
+ }
+ }
+
+ rc = regmap_write(chip->regmap, reg, data[i]);
+ if (rc < 0) {
+ pr_err("Couldn't write 0x%02x to 0x%04x rc=%d\n",
+ data[i], reg, rc);
+ goto unlock;
+ }
+ }
+
+unlock:
+ mutex_unlock(&chip->write_lock);
+ return rc;
+}
+
+static int tadc_lerp(const struct tadc_pt *pts, size_t size, bool inv,
+ s32 input, s32 *output)
+{
+ int i;
+ s64 temp;
+ bool ascending;
+
+ if (pts == NULL) {
+ pr_err("Table is NULL\n");
+ return -EINVAL;
+ }
+
+ if (size < 1) {
+ pr_err("Table has no entries\n");
+ return -ENOENT;
+ }
+
+ if (size == 1) {
+ *output = inv ? pts[0].x : pts[0].y;
+ return 0;
+ }
+
+ ascending = inv ? (pts[0].y < pts[1].y) : (pts[0].x < pts[1].x);
+ if (ascending ? (input <= (inv ? pts[0].y : pts[0].x)) :
+ (input >= (inv ? pts[0].y : pts[0].x))) {
+ *output = inv ? pts[0].x : pts[0].y;
+ return 0;
+ }
+
+ if (ascending ? (input >= (inv ? pts[size - 1].y : pts[size - 1].x)) :
+ (input <= (inv ? pts[size - 1].y : pts[size - 1].x))) {
+ *output = inv ? pts[size - 1].x : pts[size - 1].y;
+ return 0;
+ }
+
+ for (i = 1; i < size; i++)
+ if (ascending ? (input <= (inv ? pts[i].y : pts[i].x)) :
+ (input >= (inv ? pts[i].y : pts[i].x)))
+ break;
+
+ if (inv) {
+ temp = (s64)(pts[i].x - pts[i - 1].x) *
+ (s64)(input - pts[i - 1].y);
+ temp = div_s64(temp, pts[i].y - pts[i - 1].y);
+ *output = temp + pts[i - 1].x;
+ } else {
+ temp = (s64)(pts[i].y - pts[i - 1].y) *
+ (s64)(input - pts[i - 1].x);
+ temp = div_s64(temp, pts[i].x - pts[i - 1].x);
+ *output = temp + pts[i - 1].y;
+ }
+
+ return 0;
+}
+
+/*
+ * Process the result of a thermistor reading.
+ *
+ * The voltage input to the ADC is a result of a voltage divider circuit.
+ * Vout = (Rtherm / (Rbias + Rtherm)) * Vbias
+ *
+ * The ADC value is based on the output voltage of the voltage divider, and the
+ * bias voltage.
+ * ADC = (Vin * 1024) / Vbias
+ *
+ * Combine these equations and solve for Rtherm
+ * Rtherm = (ADC * Rbias) / (1024 - ADC)
+ */
+static int tadc_get_processed_therm(const struct tadc_chan_data *chan_data,
+ s16 adc, s32 *result)
+{
+ s32 rtherm;
+
+ rtherm = div_s64((s64)adc * chan_data->rbias, TADC_RESOLUTION - adc);
+ return tadc_lerp(chan_data->table, chan_data->tablesize, false, rtherm,
+ result);
+}
+
+static int tadc_get_raw_therm(const struct tadc_chan_data *chan_data,
+ int mdegc, int *result)
+{
+ int rc;
+ s32 rtherm;
+
+ rc = tadc_lerp(chan_data->table, chan_data->tablesize, true, mdegc,
+ &rtherm);
+ if (rc < 0) {
+ pr_err("Couldn't interpolate %d\n rc=%d", mdegc, rc);
+ return rc;
+ }
+
+ *result = div64_s64((s64)rtherm * TADC_RESOLUTION,
+ (s64)chan_data->rbias + rtherm);
+ return 0;
+}
+
+static int tadc_read_channel(struct tadc_chip *chip, u16 address, int *adc)
+{
+ u8 val[2];
+ int rc;
+
+ rc = tadc_read(chip, address, val, ARRAY_SIZE(val));
+ if (rc < 0) {
+ pr_err("Couldn't read channel rc=%d\n", rc);
+ return rc;
+ }
+
+ /* the 10th bit is the sign bit for all channels */
+ *adc = sign_extend32(val[0] | val[1] << BITS_PER_BYTE, 10);
+ return rc;
+}
+
+static int tadc_write_channel(struct tadc_chip *chip, u16 address, int adc)
+{
+ u8 val[2];
+ int rc;
+
+ /* the 10th bit is the sign bit for all channels */
+ adc = sign_extend32(adc, 10);
+ val[0] = (u8)adc;
+ val[1] = (u8)(adc >> BITS_PER_BYTE);
+ rc = tadc_bulk_write(chip, address, val, 2);
+ if (rc < 0) {
+ pr_err("Couldn't write to channel rc=%d\n", rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+#define CONVERSION_TIMEOUT_MS 100
+static int tadc_do_conversion(struct tadc_chip *chip, u8 channels, s16 *adc)
+{
+ unsigned long timeout, timeleft;
+ u8 val[TADC_NUM_CH * 2];
+ int rc, i;
+
+ rc = tadc_read(chip, TADC_MBG_ERR_REG(chip), val, 1);
+ if (rc < 0) {
+ pr_err("Couldn't read mbg error status rc=%d\n", rc);
+ return rc;
+ }
+
+ if (val[0] != 0) {
+ tadc_write(chip, TADC_EN_CTL_REG(chip), 0);
+ tadc_write(chip, TADC_EN_CTL_REG(chip), 0x80);
+ }
+
+ rc = tadc_write(chip, TADC_CONV_REQ_REG(chip), channels);
+ if (rc < 0) {
+ pr_err("Couldn't write conversion request rc=%d\n", rc);
+ return rc;
+ }
+
+ timeout = msecs_to_jiffies(CONVERSION_TIMEOUT_MS);
+ timeleft = wait_for_completion_timeout(&chip->eoc_complete, timeout);
+
+ if (timeleft == 0) {
+ rc = tadc_read(chip, TADC_SW_CH_CONV_REG(chip), val, 1);
+ if (rc < 0) {
+ pr_err("Couldn't read conversion status rc=%d\n", rc);
+ return rc;
+ }
+
+ if (val[0] != channels) {
+ pr_err("Conversion timed out\n");
+ return -ETIMEDOUT;
+ }
+ }
+
+ rc = tadc_read(chip, TADC_CH1_ADC_LO_REG(chip), val, ARRAY_SIZE(val));
+ if (rc < 0) {
+ pr_err("Couldn't read adc channels rc=%d\n", rc);
+ return rc;
+ }
+
+ for (i = 0; i < TADC_NUM_CH; i++)
+ adc[i] = (s16)(val[i * 2] | (u16)val[i * 2 + 1] << 8);
+
+ return jiffies_to_msecs(timeout - timeleft);
+}
+
+static int tadc_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val, int *val2,
+ long mask)
+{
+ struct tadc_chip *chip = iio_priv(indio_dev);
+ struct tadc_chan_data *chan_data = NULL;
+ int rc, offset = 0, scale, scale2, scale_type;
+ s16 adc[TADC_NUM_CH];
+
+ switch (chan->channel) {
+ case TADC_THERM1_THR1:
+ case TADC_THERM1_THR2:
+ case TADC_THERM1_THR3:
+ case TADC_THERM1_THR4:
+ chan_data = &chip->chans[TADC_THERM1];
+ break;
+ case TADC_THERM2_THR1:
+ case TADC_THERM2_THR2:
+ case TADC_THERM2_THR3:
+ chan_data = &chip->chans[TADC_THERM2];
+ break;
+ case TADC_DIE_TEMP_THR1:
+ case TADC_DIE_TEMP_THR2:
+ case TADC_DIE_TEMP_THR3:
+ chan_data = &chip->chans[TADC_DIE_TEMP];
+ break;
+ default:
+ if (chan->channel >= ARRAY_SIZE(chip->chans)) {
+ pr_err("Channel %d is out of bounds\n", chan->channel);
+ return -EINVAL;
+ }
+
+ chan_data = &chip->chans[chan->channel];
+ break;
+ }
+
+ if (!chan_data)
+ return -EINVAL;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ switch (chan->channel) {
+ case TADC_THERM1_THR1:
+ case TADC_THERM2_THR1:
+ case TADC_DIE_TEMP_THR1:
+ rc = tadc_read_channel(chip,
+ chan_data->thr[0].addr_lo, val);
+ break;
+ case TADC_THERM1_THR2:
+ case TADC_THERM2_THR2:
+ case TADC_DIE_TEMP_THR2:
+ rc = tadc_read_channel(chip,
+ chan_data->thr[1].addr_lo, val);
+ break;
+ case TADC_THERM1_THR3:
+ case TADC_THERM2_THR3:
+ case TADC_DIE_TEMP_THR3:
+ rc = tadc_read_channel(chip,
+ chan_data->thr[2].addr_lo, val);
+ break;
+ case TADC_THERM1_THR4:
+ rc = tadc_read_channel(chip,
+ chan_data->thr[3].addr_lo, val);
+ break;
+ default:
+ rc = tadc_do_conversion(chip, BIT(chan->channel), adc);
+ if (rc >= 0)
+ *val = adc[chan->channel];
+ break;
+ }
+
+ if (rc < 0) {
+ pr_err("Couldn't read channel %d\n", chan->channel);
+ return rc;
+ }
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_PROCESSED:
+ switch (chan->channel) {
+ case TADC_THERM1:
+ case TADC_THERM2:
+ case TADC_THERM1_THR1:
+ case TADC_THERM1_THR2:
+ case TADC_THERM1_THR3:
+ case TADC_THERM1_THR4:
+ case TADC_THERM2_THR1:
+ case TADC_THERM2_THR2:
+ case TADC_THERM2_THR3:
+ rc = tadc_read_raw(indio_dev, chan, val, NULL,
+ IIO_CHAN_INFO_RAW);
+ if (rc < 0)
+ return rc;
+
+ rc = tadc_get_processed_therm(chan_data, *val, val);
+ if (rc < 0) {
+ pr_err("Couldn't process 0x%04x from channel %d rc=%d\n",
+ *val, chan->channel, rc);
+ return rc;
+ }
+ break;
+ case TADC_BATT_P:
+ rc = tadc_do_conversion(chip,
+ BIT(TADC_BATT_I) | BIT(TADC_BATT_V), adc);
+ if (rc < 0) {
+ pr_err("Couldn't read battery current and voltage channels rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ *val = adc[TADC_BATT_I] * adc[TADC_BATT_V];
+ break;
+ case TADC_INPUT_P:
+ rc = tadc_do_conversion(chip,
+ BIT(TADC_INPUT_I) | BIT(TADC_INPUT_V), adc);
+ if (rc < 0) {
+ pr_err("Couldn't read input current and voltage channels rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ *val = adc[TADC_INPUT_I] * adc[TADC_INPUT_V];
+ break;
+ default:
+ rc = tadc_read_raw(indio_dev, chan, val, NULL,
+ IIO_CHAN_INFO_RAW);
+ if (rc < 0)
+ return rc;
+
+ /* offset is optional */
+ rc = tadc_read_raw(indio_dev, chan, &offset, NULL,
+ IIO_CHAN_INFO_OFFSET);
+ if (rc < 0)
+ return rc;
+
+ scale_type = tadc_read_raw(indio_dev, chan,
+ &scale, &scale2, IIO_CHAN_INFO_SCALE);
+ switch (scale_type) {
+ case IIO_VAL_INT:
+ *val = *val * scale + offset;
+ break;
+ case IIO_VAL_FRACTIONAL:
+ *val = div_s64((s64)*val * scale + offset,
+ scale2);
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ }
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->channel) {
+ case TADC_DIE_TEMP:
+ case TADC_DIE_TEMP_THR1:
+ case TADC_DIE_TEMP_THR2:
+ *val = chan_data->scale;
+ return IIO_VAL_INT;
+ case TADC_BATT_I:
+ case TADC_BATT_V:
+ case TADC_INPUT_I:
+ case TADC_INPUT_V:
+ case TADC_OTG_I:
+ *val = chan_data->scale;
+ *val2 = TADC_RESOLUTION;
+ return IIO_VAL_FRACTIONAL;
+ }
+
+ return -EINVAL;
+ case IIO_CHAN_INFO_OFFSET:
+ *val = chan_data->offset;
+ return IIO_VAL_INT;
+ }
+
+ return -EINVAL;
+}
+
+static int tadc_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int val, int val2,
+ long mask)
+{
+ struct tadc_chip *chip = iio_priv(indio_dev);
+ const struct tadc_chan_data *chan_data;
+ int rc, raw;
+ s32 rem;
+
+ switch (chan->channel) {
+ case TADC_THERM1_THR1:
+ case TADC_THERM1_THR2:
+ case TADC_THERM1_THR3:
+ case TADC_THERM1_THR4:
+ chan_data = &chip->chans[TADC_THERM1];
+ break;
+ case TADC_THERM2_THR1:
+ case TADC_THERM2_THR2:
+ case TADC_THERM2_THR3:
+ chan_data = &chip->chans[TADC_THERM2];
+ break;
+ case TADC_DIE_TEMP_THR1:
+ case TADC_DIE_TEMP_THR2:
+ case TADC_DIE_TEMP_THR3:
+ chan_data = &chip->chans[TADC_DIE_TEMP];
+ break;
+ default:
+ if (chan->channel >= ARRAY_SIZE(chip->chans)) {
+ pr_err("Channel %d is out of bounds\n", chan->channel);
+ return -EINVAL;
+ }
+
+ chan_data = &chip->chans[chan->channel];
+ break;
+ }
+
+ if (!chan_data)
+ return -EINVAL;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_PROCESSED:
+ switch (chan->channel) {
+ case TADC_THERM1_THR1:
+ case TADC_THERM1_THR2:
+ case TADC_THERM1_THR3:
+ case TADC_THERM1_THR4:
+ case TADC_THERM2_THR1:
+ case TADC_THERM2_THR2:
+ case TADC_THERM2_THR3:
+ rc = tadc_get_raw_therm(chan_data, val, &raw);
+ if (rc < 0) {
+ pr_err("Couldn't get raw value rc=%d\n", rc);
+ return rc;
+ }
+ break;
+ case TADC_DIE_TEMP_THR1:
+ case TADC_DIE_TEMP_THR2:
+ case TADC_DIE_TEMP_THR3:
+ /* DIV_ROUND_CLOSEST does not like negative numbers */
+ raw = div_s64_rem(val - chan_data->offset,
+ chan_data->scale, &rem);
+ if (abs(rem) >= abs(chan_data->scale / 2))
+ raw++;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ rc = tadc_write_raw(indio_dev, chan, raw, 0,
+ IIO_CHAN_INFO_RAW);
+ if (rc < 0) {
+ pr_err("Couldn't write raw rc=%d\n", rc);
+ return rc;
+ }
+
+ break;
+ case IIO_CHAN_INFO_RAW:
+ switch (chan->channel) {
+ case TADC_THERM1_THR1:
+ case TADC_THERM2_THR1:
+ case TADC_DIE_TEMP_THR1:
+ rc = tadc_write_channel(chip,
+ chan_data->thr[0].addr_lo, val);
+ break;
+ case TADC_THERM1_THR2:
+ case TADC_THERM2_THR2:
+ case TADC_DIE_TEMP_THR2:
+ rc = tadc_write_channel(chip,
+ chan_data->thr[1].addr_lo, val);
+ break;
+ case TADC_THERM1_THR3:
+ case TADC_THERM2_THR3:
+ case TADC_DIE_TEMP_THR3:
+ rc = tadc_write_channel(chip,
+ chan_data->thr[2].addr_lo, val);
+ break;
+ case TADC_THERM1_THR4:
+ rc = tadc_write_channel(chip,
+ chan_data->thr[3].addr_lo, val);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (rc < 0) {
+ pr_err("Couldn't write channel %d\n", chan->channel);
+ return rc;
+ }
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+
+static irqreturn_t handle_eoc(int irq, void *dev_id)
+{
+ struct tadc_chip *chip = dev_id;
+
+ complete(&chip->eoc_complete);
+ return IRQ_HANDLED;
+}
+
+static int tadc_set_therm_table(struct tadc_chan_data *chan_data, u32 beta,
+ u32 rtherm)
+{
+ if (beta == 3450 && rtherm == 68000) {
+ chan_data->table = tadc_therm_3450b_68k;
+ chan_data->tablesize = ARRAY_SIZE(tadc_therm_3450b_68k);
+ return 0;
+ }
+
+ return -ENOENT;
+}
+
+static int tadc_parse_dt(struct tadc_chip *chip)
+{
+ struct device_node *child, *node;
+ struct tadc_chan_data *chan_data;
+ u32 chan_id, rtherm, beta;
+ int rc = 0;
+
+ node = chip->dev->of_node;
+ for_each_available_child_of_node(node, child) {
+ rc = of_property_read_u32(child, "reg", &chan_id);
+ if (rc < 0) {
+ pr_err("Couldn't find channel for %s rc=%d",
+ child->name, rc);
+ return rc;
+ }
+
+ if (chan_id > TADC_NUM_CH - 1) {
+ pr_err("Channel %d is out of range [0, %d]\n",
+ chan_id, TADC_NUM_CH - 1);
+ return -EINVAL;
+ }
+
+ chan_data = &chip->chans[chan_id];
+ if (chan_id == TADC_THERM1 || chan_id == TADC_THERM2) {
+ rc = of_property_read_u32(child,
+ "qcom,rbias", &chan_data->rbias);
+ if (rc < 0) {
+ pr_err("Couldn't read qcom,rbias rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(child,
+ "qcom,beta-coefficient", &beta);
+ if (rc < 0) {
+ pr_err("Couldn't read qcom,beta-coefficient rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(child,
+ "qcom,rtherm-at-25degc", &rtherm);
+ if (rc < 0) {
+ pr_err("Couldn't read qcom,rtherm-at-25degc rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = tadc_set_therm_table(chan_data, beta, rtherm);
+ if (rc < 0) {
+ pr_err("Couldn't set therm table rc=%d\n", rc);
+ return rc;
+ }
+ } else {
+ rc = of_property_read_s32(child, "qcom,scale",
+ &chan_data->scale);
+ if (rc < 0) {
+ pr_err("Couldn't read scale rc=%d\n", rc);
+ return rc;
+ }
+
+ of_property_read_s32(child, "qcom,offset",
+ &chan_data->offset);
+ }
+ }
+
+ return rc;
+}
+
+static int tadc_init_hw(struct tadc_chip *chip)
+{
+ int rc;
+
+ chip->chans[TADC_THERM1].thr[0].addr_lo =
+ TADC_CMP_THR1_CH1_CMP_LO_REG(chip);
+ chip->chans[TADC_THERM1].thr[0].addr_hi =
+ TADC_CMP_THR1_CH1_CMP_HI_REG(chip);
+ chip->chans[TADC_THERM1].thr[1].addr_lo =
+ TADC_CMP_THR2_CH1_CMP_LO_REG(chip);
+ chip->chans[TADC_THERM1].thr[1].addr_hi =
+ TADC_CMP_THR2_CH1_CMP_HI_REG(chip);
+ chip->chans[TADC_THERM1].thr[2].addr_lo =
+ TADC_CMP_THR3_CH1_CMP_LO_REG(chip);
+ chip->chans[TADC_THERM1].thr[2].addr_hi =
+ TADC_CMP_THR3_CH1_CMP_HI_REG(chip);
+ chip->chans[TADC_THERM1].thr[3].addr_lo =
+ TADC_CMP_THR4_CH1_CMP_LO_REG(chip);
+ chip->chans[TADC_THERM1].thr[3].addr_hi =
+ TADC_CMP_THR4_CH1_CMP_HI_REG(chip);
+
+ chip->chans[TADC_THERM2].thr[0].addr_lo =
+ TADC_CMP_THR1_CH2_CMP_LO_REG(chip);
+ chip->chans[TADC_THERM2].thr[0].addr_hi =
+ TADC_CMP_THR1_CH2_CMP_HI_REG(chip);
+ chip->chans[TADC_THERM2].thr[1].addr_lo =
+ TADC_CMP_THR2_CH2_CMP_LO_REG(chip);
+ chip->chans[TADC_THERM2].thr[1].addr_hi =
+ TADC_CMP_THR2_CH2_CMP_HI_REG(chip);
+ chip->chans[TADC_THERM2].thr[2].addr_lo =
+ TADC_CMP_THR3_CH2_CMP_LO_REG(chip);
+ chip->chans[TADC_THERM2].thr[2].addr_hi =
+ TADC_CMP_THR3_CH2_CMP_HI_REG(chip);
+
+ chip->chans[TADC_DIE_TEMP].thr[0].addr_lo =
+ TADC_CMP_THR1_CH3_CMP_LO_REG(chip);
+ chip->chans[TADC_DIE_TEMP].thr[0].addr_hi =
+ TADC_CMP_THR1_CH3_CMP_HI_REG(chip);
+ chip->chans[TADC_DIE_TEMP].thr[1].addr_lo =
+ TADC_CMP_THR2_CH3_CMP_LO_REG(chip);
+ chip->chans[TADC_DIE_TEMP].thr[1].addr_hi =
+ TADC_CMP_THR2_CH3_CMP_HI_REG(chip);
+ chip->chans[TADC_DIE_TEMP].thr[2].addr_lo =
+ TADC_CMP_THR3_CH3_CMP_LO_REG(chip);
+ chip->chans[TADC_DIE_TEMP].thr[2].addr_hi =
+ TADC_CMP_THR3_CH3_CMP_HI_REG(chip);
+
+ rc = tadc_write(chip, TADC_CMP_THR1_CMP_REG(chip), 0);
+ if (rc < 0) {
+ pr_err("Couldn't enable hardware triggers rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = tadc_write(chip, TADC_CMP_THR2_CMP_REG(chip), 0);
+ if (rc < 0) {
+ pr_err("Couldn't enable hardware triggers rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = tadc_write(chip, TADC_CMP_THR3_CMP_REG(chip), 0);
+ if (rc < 0) {
+ pr_err("Couldn't enable hardware triggers rc=%d\n", rc);
+ return rc;
+ }
+
+ /* enable all temperature hardware triggers */
+ rc = tadc_write(chip, TADC_HWTRIG_CONV_CH_EN_REG(chip),
+ BIT(TADC_THERM1) |
+ BIT(TADC_THERM2) |
+ BIT(TADC_DIE_TEMP));
+ if (rc < 0) {
+ pr_err("Couldn't enable hardware triggers rc=%d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static const struct iio_info tadc_info = {
+ .read_raw = &tadc_read_raw,
+ .write_raw = &tadc_write_raw,
+ .driver_module = THIS_MODULE,
+};
+
+static int tadc_probe(struct platform_device *pdev)
+{
+ struct device_node *node = pdev->dev.of_node;
+ struct iio_dev *indio_dev;
+ struct tadc_chip *chip;
+ int rc, irq;
+
+ indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*chip));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ chip = iio_priv(indio_dev);
+ chip->dev = &pdev->dev;
+ init_completion(&chip->eoc_complete);
+
+ rc = of_property_read_u32(node, "reg", &chip->tadc_base);
+ if (rc < 0) {
+ pr_err("Couldn't read base address rc=%d\n", rc);
+ return rc;
+ }
+ chip->tadc_cmp_base = chip->tadc_base + 0x100;
+
+ mutex_init(&chip->write_lock);
+ chip->regmap = dev_get_regmap(chip->dev->parent, NULL);
+ if (!chip->regmap) {
+ pr_err("Couldn't get regmap\n");
+ return -ENODEV;
+ }
+
+ rc = tadc_parse_dt(chip);
+ if (rc < 0) {
+ pr_err("Couldn't parse device tree rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = tadc_init_hw(chip);
+ if (rc < 0) {
+ pr_err("Couldn't initialize hardware rc=%d\n", rc);
+ return rc;
+ }
+
+ irq = of_irq_get_byname(node, "eoc");
+ if (irq < 0) {
+ pr_err("Couldn't get eoc irq rc=%d\n", irq);
+ return irq;
+ }
+
+ rc = devm_request_threaded_irq(chip->dev, irq, NULL, handle_eoc,
+ IRQF_ONESHOT, "eoc", chip);
+ if (rc < 0) {
+ pr_err("Couldn't request irq %d rc=%d\n", irq, rc);
+ return rc;
+ }
+
+ indio_dev->dev.parent = chip->dev;
+ indio_dev->name = pdev->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &tadc_info;
+ indio_dev->channels = tadc_iio_chans;
+ indio_dev->num_channels = ARRAY_SIZE(tadc_iio_chans);
+
+ rc = devm_iio_device_register(chip->dev, indio_dev);
+ if (rc < 0) {
+ pr_err("Couldn't register IIO device rc=%d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int tadc_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static const struct of_device_id tadc_match_table[] = {
+ { .compatible = "qcom,tadc" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, tadc_match_table);
+
+static struct platform_driver tadc_driver = {
+ .driver = {
+ .name = "qcom-tadc",
+ .of_match_table = tadc_match_table,
+ },
+ .probe = tadc_probe,
+ .remove = tadc_remove,
+};
+module_platform_driver(tadc_driver);
+
+MODULE_DESCRIPTION("Qualcomm Technologies Inc. TADC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 7a628c6..5f144a6 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -659,6 +659,43 @@
This option enabled support for the LEDs on the Mellanox
boards. Say Y to enabled these.
+config LEDS_QPNP
+ tristate "Support for QPNP LEDs"
+ depends on LEDS_CLASS && SPMI
+ help
+ This driver supports the LED functionality of Qualcomm Technologies,
+ Inc. QPNP PMICs. It primarily supports controlling tri-color RGB
+ LEDs in both PWM and light pattern generator (LPG) modes. For older
+ PMICs, it also supports WLEDs and flash LEDs.
+
+config LEDS_QPNP_FLASH
+ tristate "Support for QPNP Flash LEDs"
+ depends on LEDS_CLASS && SPMI
+ help
+ This driver supports the flash LED functionality of Qualcomm
+ Technologies, Inc. QPNP PMICs. This driver supports PMICs up through
+ PM8994. It can configure the flash LED target current for several
+ independent channels.
+
+config LEDS_QPNP_FLASH_V2
+ tristate "Support for QPNP V2 Flash LEDs"
+ depends on LEDS_CLASS && MFD_SPMI_PMIC && !LEDS_QPNP_FLASH
+ help
+ This driver supports the flash V2 LED functionality of Qualcomm
+ Technologies, Inc. QPNP PMICs. This driver supports PMICs starting
+ from PMI8998. It can configure the flash LED target current for
+ several independent channels. It also supports various over current
+ and over temperature mitigation features.
+
+config LEDS_QPNP_WLED
+ tristate "Support for QPNP WLED"
+ depends on LEDS_CLASS && SPMI
+ help
+ This driver supports the WLED (White LED) functionality of Qualcomm
+ Technologies, Inc. QPNP PMICs. WLED is used for LCD backlight with
+ variable brightness. It also supports outputting the Avdd supply for
+ AMOLED displays.
+
comment "LED Triggers"
source "drivers/leds/trigger/Kconfig"
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 3965070..e0ca2e8 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -71,6 +71,10 @@
obj-$(CONFIG_LEDS_IS31FL32XX) += leds-is31fl32xx.o
obj-$(CONFIG_LEDS_PM8058) += leds-pm8058.o
obj-$(CONFIG_LEDS_MLXCPLD) += leds-mlxcpld.o
+obj-$(CONFIG_LEDS_QPNP) += leds-qpnp.o
+obj-$(CONFIG_LEDS_QPNP_FLASH) += leds-qpnp-flash.o
+obj-$(CONFIG_LEDS_QPNP_FLASH_V2) += leds-qpnp-flash-v2.o
+obj-$(CONFIG_LEDS_QPNP_WLED) += leds-qpnp-wled.o
# LED SPI Drivers
obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o
diff --git a/drivers/leds/leds-qpnp-flash-v2.c b/drivers/leds/leds-qpnp-flash-v2.c
new file mode 100644
index 0000000..08809a9
--- /dev/null
+++ b/drivers/leds/leds-qpnp-flash-v2.c
@@ -0,0 +1,2265 @@
+/* Copyright (c) 2016-2017, The Linux Foundation. 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.
+ */
+
+#define pr_fmt(fmt) "flashv2: %s: " fmt, __func__
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_gpio.h>
+#include <linux/gpio.h>
+#include <linux/regmap.h>
+#include <linux/power_supply.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/regulator/consumer.h>
+#include <linux/leds-qpnp-flash.h>
+#include <linux/leds-qpnp-flash-v2.h>
+#include <linux/qpnp/qpnp-revid.h>
+#include <linux/log2.h>
+#include "leds.h"
+
+#define FLASH_LED_REG_LED_STATUS1(base) (base + 0x08)
+#define FLASH_LED_REG_LED_STATUS2(base) (base + 0x09)
+#define FLASH_LED_REG_INT_RT_STS(base) (base + 0x10)
+#define FLASH_LED_REG_SAFETY_TMR(base) (base + 0x40)
+#define FLASH_LED_REG_TGR_CURRENT(base) (base + 0x43)
+#define FLASH_LED_REG_MOD_CTRL(base) (base + 0x46)
+#define FLASH_LED_REG_IRES(base) (base + 0x47)
+#define FLASH_LED_REG_STROBE_CFG(base) (base + 0x48)
+#define FLASH_LED_REG_STROBE_CTRL(base) (base + 0x49)
+#define FLASH_LED_EN_LED_CTRL(base) (base + 0x4C)
+#define FLASH_LED_REG_HDRM_PRGM(base) (base + 0x4D)
+#define FLASH_LED_REG_HDRM_AUTO_MODE_CTRL(base) (base + 0x50)
+#define FLASH_LED_REG_WARMUP_DELAY(base) (base + 0x51)
+#define FLASH_LED_REG_ISC_DELAY(base) (base + 0x52)
+#define FLASH_LED_REG_THERMAL_RMP_DN_RATE(base) (base + 0x55)
+#define FLASH_LED_REG_THERMAL_THRSH1(base) (base + 0x56)
+#define FLASH_LED_REG_THERMAL_THRSH2(base) (base + 0x57)
+#define FLASH_LED_REG_THERMAL_THRSH3(base) (base + 0x58)
+#define FLASH_LED_REG_THERMAL_HYSTERESIS(base) (base + 0x59)
+#define FLASH_LED_REG_THERMAL_DEBOUNCE(base) (base + 0x5A)
+#define FLASH_LED_REG_VPH_DROOP_THRESHOLD(base) (base + 0x61)
+#define FLASH_LED_REG_VPH_DROOP_DEBOUNCE(base) (base + 0x62)
+#define FLASH_LED_REG_ILED_GRT_THRSH(base) (base + 0x67)
+#define FLASH_LED_REG_LED1N2_ICLAMP_LOW(base) (base + 0x68)
+#define FLASH_LED_REG_LED1N2_ICLAMP_MID(base) (base + 0x69)
+#define FLASH_LED_REG_LED3_ICLAMP_LOW(base) (base + 0x6A)
+#define FLASH_LED_REG_LED3_ICLAMP_MID(base) (base + 0x6B)
+#define FLASH_LED_REG_MITIGATION_SEL(base) (base + 0x6E)
+#define FLASH_LED_REG_MITIGATION_SW(base) (base + 0x6F)
+#define FLASH_LED_REG_LMH_LEVEL(base) (base + 0x70)
+#define FLASH_LED_REG_CURRENT_DERATE_EN(base) (base + 0x76)
+
+#define FLASH_LED_HDRM_VOL_MASK GENMASK(7, 4)
+#define FLASH_LED_CURRENT_MASK GENMASK(6, 0)
+#define FLASH_LED_ENABLE_MASK GENMASK(2, 0)
+#define FLASH_HW_STROBE_MASK GENMASK(2, 0)
+#define FLASH_LED_ISC_WARMUP_DELAY_MASK GENMASK(1, 0)
+#define FLASH_LED_CURRENT_DERATE_EN_MASK GENMASK(2, 0)
+#define FLASH_LED_VPH_DROOP_DEBOUNCE_MASK GENMASK(1, 0)
+#define FLASH_LED_CHGR_MITIGATION_SEL_MASK GENMASK(5, 4)
+#define FLASH_LED_LMH_MITIGATION_SEL_MASK GENMASK(1, 0)
+#define FLASH_LED_ILED_GRT_THRSH_MASK GENMASK(5, 0)
+#define FLASH_LED_LMH_LEVEL_MASK GENMASK(1, 0)
+#define FLASH_LED_VPH_DROOP_HYSTERESIS_MASK GENMASK(5, 4)
+#define FLASH_LED_VPH_DROOP_THRESHOLD_MASK GENMASK(2, 0)
+#define FLASH_LED_THERMAL_HYSTERESIS_MASK GENMASK(1, 0)
+#define FLASH_LED_THERMAL_DEBOUNCE_MASK GENMASK(1, 0)
+#define FLASH_LED_THERMAL_THRSH_MASK GENMASK(2, 0)
+#define FLASH_LED_MOD_CTRL_MASK BIT(7)
+#define FLASH_LED_HW_SW_STROBE_SEL_BIT BIT(2)
+#define FLASH_LED_VPH_DROOP_FAULT_MASK BIT(4)
+#define FLASH_LED_LMH_MITIGATION_EN_MASK BIT(0)
+#define FLASH_LED_CHGR_MITIGATION_EN_MASK BIT(4)
+#define THERMAL_OTST1_RAMP_CTRL_MASK BIT(7)
+#define THERMAL_OTST1_RAMP_CTRL_SHIFT 7
+#define THERMAL_DERATE_SLOW_SHIFT 4
+#define THERMAL_DERATE_SLOW_MASK GENMASK(6, 4)
+#define THERMAL_DERATE_FAST_MASK GENMASK(2, 0)
+
+#define VPH_DROOP_DEBOUNCE_US_TO_VAL(val_us) (val_us / 8)
+#define VPH_DROOP_HYST_MV_TO_VAL(val_mv) (val_mv / 25)
+#define VPH_DROOP_THRESH_MV_TO_VAL(val_mv) ((val_mv / 100) - 25)
+#define VPH_DROOP_THRESH_VAL_TO_UV(val) ((val + 25) * 100000)
+#define MITIGATION_THRSH_MA_TO_VAL(val_ma) (val_ma / 100)
+#define CURRENT_MA_TO_REG_VAL(curr_ma, ires_ua) ((curr_ma * 1000) / ires_ua - 1)
+#define SAFETY_TMR_TO_REG_VAL(duration_ms) ((duration_ms / 10) - 1)
+#define THERMAL_HYST_TEMP_TO_VAL(val, divisor) (val / divisor)
+
+#define FLASH_LED_ISC_WARMUP_DELAY_SHIFT 6
+#define FLASH_LED_WARMUP_DELAY_DEFAULT 2
+#define FLASH_LED_ISC_DELAY_DEFAULT 3
+#define FLASH_LED_VPH_DROOP_DEBOUNCE_DEFAULT 2
+#define FLASH_LED_VPH_DROOP_HYST_SHIFT 4
+#define FLASH_LED_VPH_DROOP_HYST_DEFAULT 2
+#define FLASH_LED_VPH_DROOP_THRESH_DEFAULT 5
+#define FLASH_LED_DEBOUNCE_MAX 3
+#define FLASH_LED_HYSTERESIS_MAX 3
+#define FLASH_LED_VPH_DROOP_THRESH_MAX 7
+#define THERMAL_DERATE_SLOW_MAX 314592
+#define THERMAL_DERATE_FAST_MAX 512
+#define THERMAL_DEBOUNCE_TIME_MAX 64
+#define THERMAL_DERATE_HYSTERESIS_MAX 3
+#define FLASH_LED_THERMAL_THRSH_MIN 3
+#define FLASH_LED_THERMAL_THRSH_MAX 7
+#define FLASH_LED_THERMAL_OTST_LEVELS 3
+#define FLASH_LED_VLED_MAX_DEFAULT_UV 3500000
+#define FLASH_LED_IBATT_OCP_THRESH_DEFAULT_UA 4500000
+#define FLASH_LED_RPARA_DEFAULT_UOHM 0
+#define FLASH_LED_SAFETY_TMR_ENABLE BIT(7)
+#define FLASH_LED_LMH_LEVEL_DEFAULT 0
+#define FLASH_LED_LMH_MITIGATION_ENABLE 1
+#define FLASH_LED_LMH_MITIGATION_DISABLE 0
+#define FLASH_LED_CHGR_MITIGATION_ENABLE BIT(4)
+#define FLASH_LED_CHGR_MITIGATION_DISABLE 0
+#define FLASH_LED_MITIGATION_SEL_DEFAULT 2
+#define FLASH_LED_MITIGATION_SEL_MAX 2
+#define FLASH_LED_CHGR_MITIGATION_SEL_SHIFT 4
+#define FLASH_LED_MITIGATION_THRSH_DEFAULT 0xA
+#define FLASH_LED_MITIGATION_THRSH_MAX 0x1F
+#define FLASH_LED_LMH_OCV_THRESH_DEFAULT_UV 3700000
+#define FLASH_LED_LMH_RBATT_THRESH_DEFAULT_UOHM 400000
+#define FLASH_LED_IRES_BASE 3
+#define FLASH_LED_IRES_DIVISOR 2500
+#define FLASH_LED_IRES_MIN_UA 5000
+#define FLASH_LED_IRES_DEFAULT_UA 12500
+#define FLASH_LED_IRES_DEFAULT_VAL 0x00
+#define FLASH_LED_HDRM_VOL_SHIFT 4
+#define FLASH_LED_HDRM_VOL_DEFAULT_MV 0x80
+#define FLASH_LED_HDRM_VOL_HI_LO_WIN_DEFAULT_MV 0x04
+#define FLASH_LED_HDRM_VOL_BASE_MV 125
+#define FLASH_LED_HDRM_VOL_STEP_MV 25
+#define FLASH_LED_STROBE_CFG_DEFAULT 0x00
+#define FLASH_LED_HW_STROBE_OPTION_1 0x00
+#define FLASH_LED_HW_STROBE_OPTION_2 0x01
+#define FLASH_LED_HW_STROBE_OPTION_3 0x02
+#define FLASH_LED_ENABLE BIT(0)
+#define FLASH_LED_MOD_ENABLE BIT(7)
+#define FLASH_LED_DISABLE 0x00
+#define FLASH_LED_SAFETY_TMR_DISABLED 0x13
+#define FLASH_LED_MIN_CURRENT_MA 25
+#define FLASH_LED_MAX_TOTAL_CURRENT_MA 3750
+
+/* notifier call chain for flash-led irqs */
+static ATOMIC_NOTIFIER_HEAD(irq_notifier_list);
+
+enum flash_led_type {
+ FLASH_LED_TYPE_FLASH,
+ FLASH_LED_TYPE_TORCH,
+};
+
+enum {
+ LED1 = 0,
+ LED2,
+ LED3,
+};
+
+/*
+ * Configurations for each individual LED
+ */
+struct flash_node_data {
+ struct platform_device *pdev;
+ struct led_classdev cdev;
+ struct pinctrl *pinctrl;
+ struct pinctrl_state *gpio_state_active;
+ struct pinctrl_state *gpio_state_suspend;
+ struct pinctrl_state *hw_strobe_state_active;
+ struct pinctrl_state *hw_strobe_state_suspend;
+ int hw_strobe_gpio;
+ int ires_ua;
+ int max_current;
+ int current_ma;
+ u8 duration;
+ u8 id;
+ u8 type;
+ u8 ires;
+ u8 hdrm_val;
+ u8 current_reg_val;
+ u8 trigger;
+ bool led_on;
+};
+
+
+struct flash_switch_data {
+ struct platform_device *pdev;
+ struct regulator *vreg;
+ struct led_classdev cdev;
+ int led_mask;
+ bool regulator_on;
+ bool enabled;
+};
+
+/*
+ * Flash LED configuration read from device tree
+ */
+struct flash_led_platform_data {
+ struct pmic_revid_data *pmic_rev_id;
+ int *thermal_derate_current;
+ int all_ramp_up_done_irq;
+ int all_ramp_down_done_irq;
+ int led_fault_irq;
+ int ibatt_ocp_threshold_ua;
+ int vled_max_uv;
+ int rpara_uohm;
+ int lmh_rbatt_threshold_uohm;
+ int lmh_ocv_threshold_uv;
+ int thermal_derate_slow;
+ int thermal_derate_fast;
+ int thermal_hysteresis;
+ int thermal_debounce;
+ int thermal_thrsh1;
+ int thermal_thrsh2;
+ int thermal_thrsh3;
+ u32 led1n2_iclamp_low_ma;
+ u32 led1n2_iclamp_mid_ma;
+ u32 led3_iclamp_low_ma;
+ u32 led3_iclamp_mid_ma;
+ u8 isc_delay;
+ u8 warmup_delay;
+ u8 current_derate_en_cfg;
+ u8 vph_droop_threshold;
+ u8 vph_droop_hysteresis;
+ u8 vph_droop_debounce;
+ u8 lmh_mitigation_sel;
+ u8 chgr_mitigation_sel;
+ u8 lmh_level;
+ u8 iled_thrsh_val;
+ u8 hw_strobe_option;
+ bool hdrm_auto_mode_en;
+ bool thermal_derate_en;
+ bool otst_ramp_bkup_en;
+};
+
+/*
+ * Flash LED data structure containing flash LED attributes
+ */
+struct qpnp_flash_led {
+ struct flash_led_platform_data *pdata;
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ struct flash_node_data *fnode;
+ struct flash_switch_data *snode;
+ struct power_supply *bms_psy;
+ struct notifier_block nb;
+ spinlock_t lock;
+ int num_fnodes;
+ int num_snodes;
+ int enable;
+ u16 base;
+ bool trigger_lmh;
+ bool trigger_chgr;
+};
+
+static int thermal_derate_slow_table[] = {
+ 128, 256, 512, 1024, 2048, 4096, 8192, 314592,
+};
+
+static int thermal_derate_fast_table[] = {
+ 32, 64, 96, 128, 256, 384, 512,
+};
+
+static int otst1_threshold_table[] = {
+ 85, 79, 73, 67, 109, 103, 97, 91,
+};
+
+static int otst2_threshold_table[] = {
+ 110, 104, 98, 92, 134, 128, 122, 116,
+};
+
+static int otst3_threshold_table[] = {
+ 125, 119, 113, 107, 149, 143, 137, 131,
+};
+
+static int qpnp_flash_led_read(struct qpnp_flash_led *led, u16 addr, u8 *data)
+{
+ int rc;
+ uint val;
+
+ rc = regmap_read(led->regmap, addr, &val);
+ if (rc < 0) {
+ pr_err("Unable to read from 0x%04X rc = %d\n", addr, rc);
+ return rc;
+ }
+
+ pr_debug("Read 0x%02X from addr 0x%04X\n", val, addr);
+ *data = (u8)val;
+ return 0;
+}
+
+static int qpnp_flash_led_write(struct qpnp_flash_led *led, u16 addr, u8 data)
+{
+ int rc;
+
+ rc = regmap_write(led->regmap, addr, data);
+ if (rc < 0) {
+ pr_err("Unable to write to 0x%04X rc = %d\n", addr, rc);
+ return rc;
+ }
+
+ pr_debug("Wrote 0x%02X to addr 0x%04X\n", data, addr);
+ return 0;
+}
+
+static int
+qpnp_flash_led_masked_read(struct qpnp_flash_led *led, u16 addr, u8 mask,
+ u8 *val)
+{
+ int rc;
+
+ rc = qpnp_flash_led_read(led, addr, val);
+ if (rc < 0)
+ return rc;
+
+ *val &= mask;
+ return rc;
+}
+
+static int
+qpnp_flash_led_masked_write(struct qpnp_flash_led *led, u16 addr, u8 mask,
+ u8 val)
+{
+ int rc;
+
+ rc = regmap_update_bits(led->regmap, addr, mask, val);
+ if (rc < 0)
+ pr_err("Unable to update bits from 0x%04X, rc = %d\n", addr,
+ rc);
+ else
+ pr_debug("Wrote 0x%02X to addr 0x%04X\n", val, addr);
+
+ return rc;
+}
+
+static enum
+led_brightness qpnp_flash_led_brightness_get(struct led_classdev *led_cdev)
+{
+ return led_cdev->brightness;
+}
+
+static int qpnp_flash_led_init_settings(struct qpnp_flash_led *led)
+{
+ int rc, i, addr_offset;
+ u8 val = 0, mask;
+
+ for (i = 0; i < led->num_fnodes; i++) {
+ addr_offset = led->fnode[i].id;
+ rc = qpnp_flash_led_write(led,
+ FLASH_LED_REG_HDRM_PRGM(led->base + addr_offset),
+ led->fnode[i].hdrm_val);
+ if (rc < 0)
+ return rc;
+
+ val |= 0x1 << led->fnode[i].id;
+ }
+
+ rc = qpnp_flash_led_write(led,
+ FLASH_LED_REG_HDRM_AUTO_MODE_CTRL(led->base),
+ val);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_ISC_DELAY(led->base),
+ FLASH_LED_ISC_WARMUP_DELAY_MASK,
+ led->pdata->isc_delay);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_WARMUP_DELAY(led->base),
+ FLASH_LED_ISC_WARMUP_DELAY_MASK,
+ led->pdata->warmup_delay);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_CURRENT_DERATE_EN(led->base),
+ FLASH_LED_CURRENT_DERATE_EN_MASK,
+ led->pdata->current_derate_en_cfg);
+ if (rc < 0)
+ return rc;
+
+ val = (led->pdata->otst_ramp_bkup_en << THERMAL_OTST1_RAMP_CTRL_SHIFT);
+ mask = THERMAL_OTST1_RAMP_CTRL_MASK;
+ if (led->pdata->thermal_derate_slow >= 0) {
+ val |= (led->pdata->thermal_derate_slow <<
+ THERMAL_DERATE_SLOW_SHIFT);
+ mask |= THERMAL_DERATE_SLOW_MASK;
+ }
+
+ if (led->pdata->thermal_derate_fast >= 0) {
+ val |= led->pdata->thermal_derate_fast;
+ mask |= THERMAL_DERATE_FAST_MASK;
+ }
+
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_THERMAL_RMP_DN_RATE(led->base),
+ mask, val);
+ if (rc < 0)
+ return rc;
+
+ if (led->pdata->thermal_debounce >= 0) {
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_THERMAL_DEBOUNCE(led->base),
+ FLASH_LED_THERMAL_DEBOUNCE_MASK,
+ led->pdata->thermal_debounce);
+ if (rc < 0)
+ return rc;
+ }
+
+ if (led->pdata->thermal_hysteresis >= 0) {
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_THERMAL_HYSTERESIS(led->base),
+ FLASH_LED_THERMAL_HYSTERESIS_MASK,
+ led->pdata->thermal_hysteresis);
+ if (rc < 0)
+ return rc;
+ }
+
+ if (led->pdata->thermal_thrsh1 >= 0) {
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_THERMAL_THRSH1(led->base),
+ FLASH_LED_THERMAL_THRSH_MASK,
+ led->pdata->thermal_thrsh1);
+ if (rc < 0)
+ return rc;
+ }
+
+ if (led->pdata->thermal_thrsh2 >= 0) {
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_THERMAL_THRSH2(led->base),
+ FLASH_LED_THERMAL_THRSH_MASK,
+ led->pdata->thermal_thrsh2);
+ if (rc < 0)
+ return rc;
+ }
+
+ if (led->pdata->thermal_thrsh3 >= 0) {
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_THERMAL_THRSH3(led->base),
+ FLASH_LED_THERMAL_THRSH_MASK,
+ led->pdata->thermal_thrsh3);
+ if (rc < 0)
+ return rc;
+ }
+
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_VPH_DROOP_DEBOUNCE(led->base),
+ FLASH_LED_VPH_DROOP_DEBOUNCE_MASK,
+ led->pdata->vph_droop_debounce);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_VPH_DROOP_THRESHOLD(led->base),
+ FLASH_LED_VPH_DROOP_THRESHOLD_MASK,
+ led->pdata->vph_droop_threshold);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_VPH_DROOP_THRESHOLD(led->base),
+ FLASH_LED_VPH_DROOP_HYSTERESIS_MASK,
+ led->pdata->vph_droop_hysteresis);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_MITIGATION_SEL(led->base),
+ FLASH_LED_LMH_MITIGATION_SEL_MASK,
+ led->pdata->lmh_mitigation_sel);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_MITIGATION_SEL(led->base),
+ FLASH_LED_CHGR_MITIGATION_SEL_MASK,
+ led->pdata->chgr_mitigation_sel);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_LMH_LEVEL(led->base),
+ FLASH_LED_LMH_LEVEL_MASK,
+ led->pdata->lmh_level);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_ILED_GRT_THRSH(led->base),
+ FLASH_LED_ILED_GRT_THRSH_MASK,
+ led->pdata->iled_thrsh_val);
+ if (rc < 0)
+ return rc;
+
+ if (led->pdata->led1n2_iclamp_low_ma) {
+ val = CURRENT_MA_TO_REG_VAL(led->pdata->led1n2_iclamp_low_ma,
+ led->fnode[0].ires_ua);
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_LED1N2_ICLAMP_LOW(led->base),
+ FLASH_LED_CURRENT_MASK, val);
+ if (rc < 0)
+ return rc;
+ }
+
+ if (led->pdata->led1n2_iclamp_mid_ma) {
+ val = CURRENT_MA_TO_REG_VAL(led->pdata->led1n2_iclamp_mid_ma,
+ led->fnode[0].ires_ua);
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_LED1N2_ICLAMP_MID(led->base),
+ FLASH_LED_CURRENT_MASK, val);
+ if (rc < 0)
+ return rc;
+ }
+
+ if (led->pdata->led3_iclamp_low_ma) {
+ val = CURRENT_MA_TO_REG_VAL(led->pdata->led3_iclamp_low_ma,
+ led->fnode[3].ires_ua);
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_LED3_ICLAMP_LOW(led->base),
+ FLASH_LED_CURRENT_MASK, val);
+ if (rc < 0)
+ return rc;
+ }
+
+ if (led->pdata->led3_iclamp_mid_ma) {
+ val = CURRENT_MA_TO_REG_VAL(led->pdata->led3_iclamp_mid_ma,
+ led->fnode[3].ires_ua);
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_LED3_ICLAMP_MID(led->base),
+ FLASH_LED_CURRENT_MASK, val);
+ if (rc < 0)
+ return rc;
+ }
+
+ return 0;
+}
+
+static int qpnp_flash_led_hw_strobe_enable(struct flash_node_data *fnode,
+ int hw_strobe_option, bool on)
+{
+ int rc = 0;
+
+ /*
+ * If the LED controlled by this fnode is not GPIO controlled
+ * for the given strobe_option, return.
+ */
+ if (hw_strobe_option == FLASH_LED_HW_STROBE_OPTION_1)
+ return 0;
+ else if (hw_strobe_option == FLASH_LED_HW_STROBE_OPTION_2
+ && fnode->id != LED3)
+ return 0;
+ else if (hw_strobe_option == FLASH_LED_HW_STROBE_OPTION_3
+ && fnode->id == LED1)
+ return 0;
+
+ if (gpio_is_valid(fnode->hw_strobe_gpio)) {
+ gpio_set_value(fnode->hw_strobe_gpio, on ? 1 : 0);
+ } else if (fnode->hw_strobe_state_active &&
+ fnode->hw_strobe_state_suspend) {
+ rc = pinctrl_select_state(fnode->pinctrl,
+ on ? fnode->hw_strobe_state_active :
+ fnode->hw_strobe_state_suspend);
+ if (rc < 0) {
+ pr_err("failed to change hw strobe pin state\n");
+ return rc;
+ }
+ }
+
+ return rc;
+}
+
+static int qpnp_flash_led_regulator_enable(struct qpnp_flash_led *led,
+ struct flash_switch_data *snode, bool on)
+{
+ int rc = 0;
+
+ if (!snode || !snode->vreg)
+ return 0;
+
+ if (snode->regulator_on == on)
+ return 0;
+
+ if (on)
+ rc = regulator_enable(snode->vreg);
+ else
+ rc = regulator_disable(snode->vreg);
+
+ if (rc < 0) {
+ pr_err("regulator_%s failed, rc=%d\n",
+ on ? "enable" : "disable", rc);
+ return rc;
+ }
+
+ snode->regulator_on = on ? true : false;
+ return 0;
+}
+
+static int get_property_from_fg(struct qpnp_flash_led *led,
+ enum power_supply_property prop, int *val)
+{
+ int rc;
+ union power_supply_propval pval = {0, };
+
+ if (!led->bms_psy) {
+ pr_err("no bms psy found\n");
+ return -EINVAL;
+ }
+
+ rc = power_supply_get_property(led->bms_psy, prop, &pval);
+ if (rc) {
+ pr_err("bms psy doesn't support reading prop %d rc = %d\n",
+ prop, rc);
+ return rc;
+ }
+
+ *val = pval.intval;
+ return rc;
+}
+
+#define VOLTAGE_HDRM_DEFAULT_MV 350
+static int qpnp_flash_led_get_voltage_headroom(struct qpnp_flash_led *led)
+{
+ int i, voltage_hdrm_mv = 0, voltage_hdrm_max = 0;
+
+ for (i = 0; i < led->num_fnodes; i++) {
+ if (led->fnode[i].led_on) {
+ if (led->fnode[i].id < 2) {
+ if (led->fnode[i].current_ma < 750)
+ voltage_hdrm_mv = 125;
+ else if (led->fnode[i].current_ma < 1000)
+ voltage_hdrm_mv = 175;
+ else if (led->fnode[i].current_ma < 1250)
+ voltage_hdrm_mv = 250;
+ else
+ voltage_hdrm_mv = 350;
+ } else {
+ if (led->fnode[i].current_ma < 375)
+ voltage_hdrm_mv = 125;
+ else if (led->fnode[i].current_ma < 500)
+ voltage_hdrm_mv = 175;
+ else if (led->fnode[i].current_ma < 625)
+ voltage_hdrm_mv = 250;
+ else
+ voltage_hdrm_mv = 350;
+ }
+
+ voltage_hdrm_max = max(voltage_hdrm_max,
+ voltage_hdrm_mv);
+ }
+ }
+
+ if (!voltage_hdrm_max)
+ return VOLTAGE_HDRM_DEFAULT_MV;
+
+ return voltage_hdrm_max;
+}
+
+#define UCONV 1000000LL
+#define MCONV 1000LL
+#define FLASH_VDIP_MARGIN 50000
+#define BOB_EFFICIENCY 900LL
+#define VIN_FLASH_MIN_UV 3300000LL
+static int qpnp_flash_led_calc_max_current(struct qpnp_flash_led *led)
+{
+ int ocv_uv, rbatt_uohm, ibat_now, voltage_hdrm_mv, rc;
+ int64_t ibat_flash_ua, avail_flash_ua, avail_flash_power_fw;
+ int64_t ibat_safe_ua, vin_flash_uv, vph_flash_uv, vph_flash_vdip;
+
+ /* RESISTANCE = esr_uohm + rslow_uohm */
+ rc = get_property_from_fg(led, POWER_SUPPLY_PROP_RESISTANCE,
+ &rbatt_uohm);
+ if (rc < 0) {
+ pr_err("bms psy does not support resistance, rc=%d\n", rc);
+ return rc;
+ }
+
+ /* If no battery is connected, return max possible flash current */
+ if (!rbatt_uohm)
+ return FLASH_LED_MAX_TOTAL_CURRENT_MA;
+
+ rc = get_property_from_fg(led, POWER_SUPPLY_PROP_VOLTAGE_OCV, &ocv_uv);
+ if (rc < 0) {
+ pr_err("bms psy does not support OCV, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = get_property_from_fg(led, POWER_SUPPLY_PROP_CURRENT_NOW,
+ &ibat_now);
+ if (rc < 0) {
+ pr_err("bms psy does not support current, rc=%d\n", rc);
+ return rc;
+ }
+
+ rbatt_uohm += led->pdata->rpara_uohm;
+ voltage_hdrm_mv = qpnp_flash_led_get_voltage_headroom(led);
+ vph_flash_vdip =
+ VPH_DROOP_THRESH_VAL_TO_UV(led->pdata->vph_droop_threshold)
+ + FLASH_VDIP_MARGIN;
+
+ /* Check if LMH_MITIGATION needs to be triggered */
+ if (!led->trigger_lmh && (ocv_uv < led->pdata->lmh_ocv_threshold_uv ||
+ rbatt_uohm > led->pdata->lmh_rbatt_threshold_uohm)) {
+ led->trigger_lmh = true;
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_MITIGATION_SW(led->base),
+ FLASH_LED_LMH_MITIGATION_EN_MASK,
+ FLASH_LED_LMH_MITIGATION_ENABLE);
+ if (rc < 0) {
+ pr_err("trigger lmh mitigation failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ /* Wait for LMH mitigation to take effect */
+ udelay(100);
+
+ return qpnp_flash_led_calc_max_current(led);
+ }
+
+ /*
+ * Calculate the maximum current that can pulled out of the battery
+ * before the battery voltage dips below a safe threshold.
+ */
+ ibat_safe_ua = div_s64((ocv_uv - vph_flash_vdip) * UCONV,
+ rbatt_uohm);
+
+ if (ibat_safe_ua <= led->pdata->ibatt_ocp_threshold_ua) {
+ /*
+ * If the calculated current is below the OCP threshold, then
+ * use it as the possible flash current.
+ */
+ ibat_flash_ua = ibat_safe_ua - ibat_now;
+ vph_flash_uv = vph_flash_vdip;
+ } else {
+ /*
+ * If the calculated current is above the OCP threshold, then
+ * use the ocp threshold instead.
+ *
+ * Any higher current will be tripping the battery OCP.
+ */
+ ibat_flash_ua = led->pdata->ibatt_ocp_threshold_ua - ibat_now;
+ vph_flash_uv = ocv_uv - div64_s64((int64_t)rbatt_uohm
+ * led->pdata->ibatt_ocp_threshold_ua, UCONV);
+ }
+ /* Calculate the input voltage of the flash module. */
+ vin_flash_uv = max((led->pdata->vled_max_uv +
+ (voltage_hdrm_mv * MCONV)), VIN_FLASH_MIN_UV);
+ /* Calculate the available power for the flash module. */
+ avail_flash_power_fw = BOB_EFFICIENCY * vph_flash_uv * ibat_flash_ua;
+ /*
+ * Calculate the available amount of current the flash module can draw
+ * before collapsing the battery. (available power/ flash input voltage)
+ */
+ avail_flash_ua = div64_s64(avail_flash_power_fw, vin_flash_uv * MCONV);
+ pr_debug("avail_iflash=%lld, ocv=%d, ibat=%d, rbatt=%d, trigger_lmh=%d\n",
+ avail_flash_ua, ocv_uv, ibat_now, rbatt_uohm, led->trigger_lmh);
+ return min(FLASH_LED_MAX_TOTAL_CURRENT_MA,
+ (int)(div64_s64(avail_flash_ua, MCONV)));
+}
+
+static int qpnp_flash_led_calc_thermal_current_lim(struct qpnp_flash_led *led)
+{
+ int thermal_current_lim = 0;
+ int rc;
+ u8 thermal_thrsh1, thermal_thrsh2, thermal_thrsh3, otst_status;
+
+ /* Store THERMAL_THRSHx register values */
+ rc = qpnp_flash_led_masked_read(led,
+ FLASH_LED_REG_THERMAL_THRSH1(led->base),
+ FLASH_LED_THERMAL_THRSH_MASK,
+ &thermal_thrsh1);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_flash_led_masked_read(led,
+ FLASH_LED_REG_THERMAL_THRSH2(led->base),
+ FLASH_LED_THERMAL_THRSH_MASK,
+ &thermal_thrsh2);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_flash_led_masked_read(led,
+ FLASH_LED_REG_THERMAL_THRSH3(led->base),
+ FLASH_LED_THERMAL_THRSH_MASK,
+ &thermal_thrsh3);
+ if (rc < 0)
+ return rc;
+
+ /* Lower THERMAL_THRSHx thresholds to minimum */
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_THERMAL_THRSH1(led->base),
+ FLASH_LED_THERMAL_THRSH_MASK,
+ FLASH_LED_THERMAL_THRSH_MIN);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_THERMAL_THRSH2(led->base),
+ FLASH_LED_THERMAL_THRSH_MASK,
+ FLASH_LED_THERMAL_THRSH_MIN);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_THERMAL_THRSH3(led->base),
+ FLASH_LED_THERMAL_THRSH_MASK,
+ FLASH_LED_THERMAL_THRSH_MIN);
+ if (rc < 0)
+ return rc;
+
+ /* Check THERMAL_OTST status */
+ rc = qpnp_flash_led_read(led,
+ FLASH_LED_REG_LED_STATUS2(led->base),
+ &otst_status);
+ if (rc < 0)
+ return rc;
+
+ /* Look up current limit based on THERMAL_OTST status */
+ if (otst_status)
+ thermal_current_lim =
+ led->pdata->thermal_derate_current[otst_status >> 1];
+
+ /* Restore THERMAL_THRESHx registers to original values */
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_THERMAL_THRSH1(led->base),
+ FLASH_LED_THERMAL_THRSH_MASK,
+ thermal_thrsh1);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_THERMAL_THRSH2(led->base),
+ FLASH_LED_THERMAL_THRSH_MASK,
+ thermal_thrsh2);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_THERMAL_THRSH3(led->base),
+ FLASH_LED_THERMAL_THRSH_MASK,
+ thermal_thrsh3);
+ if (rc < 0)
+ return rc;
+
+ return thermal_current_lim;
+}
+
+static int qpnp_flash_led_get_max_avail_current(struct qpnp_flash_led *led)
+{
+ int max_avail_current, thermal_current_lim = 0;
+
+ led->trigger_lmh = false;
+ max_avail_current = qpnp_flash_led_calc_max_current(led);
+ if (led->pdata->thermal_derate_en)
+ thermal_current_lim =
+ qpnp_flash_led_calc_thermal_current_lim(led);
+
+ if (thermal_current_lim)
+ max_avail_current = min(max_avail_current, thermal_current_lim);
+
+ return max_avail_current;
+}
+
+static void qpnp_flash_led_node_set(struct flash_node_data *fnode, int value)
+{
+ int prgm_current_ma = value;
+
+ if (value <= 0)
+ prgm_current_ma = 0;
+ else if (value < FLASH_LED_MIN_CURRENT_MA)
+ prgm_current_ma = FLASH_LED_MIN_CURRENT_MA;
+
+ prgm_current_ma = min(prgm_current_ma, fnode->max_current);
+ fnode->current_ma = prgm_current_ma;
+ fnode->cdev.brightness = prgm_current_ma;
+ fnode->current_reg_val = CURRENT_MA_TO_REG_VAL(prgm_current_ma,
+ fnode->ires_ua);
+ fnode->led_on = prgm_current_ma != 0;
+}
+
+static int qpnp_flash_led_switch_disable(struct flash_switch_data *snode)
+{
+ struct qpnp_flash_led *led = dev_get_drvdata(&snode->pdev->dev);
+ int i, rc, addr_offset;
+
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_EN_LED_CTRL(led->base),
+ snode->led_mask, FLASH_LED_DISABLE);
+ if (rc < 0)
+ return rc;
+
+ if (led->trigger_lmh) {
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_MITIGATION_SW(led->base),
+ FLASH_LED_LMH_MITIGATION_EN_MASK,
+ FLASH_LED_LMH_MITIGATION_DISABLE);
+ if (rc < 0) {
+ pr_err("disable lmh mitigation failed, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ if (!led->trigger_chgr) {
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_MITIGATION_SW(led->base),
+ FLASH_LED_CHGR_MITIGATION_EN_MASK,
+ FLASH_LED_CHGR_MITIGATION_DISABLE);
+ if (rc < 0) {
+ pr_err("disable chgr mitigation failed, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ led->enable--;
+ if (led->enable == 0) {
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_MOD_CTRL(led->base),
+ FLASH_LED_MOD_CTRL_MASK, FLASH_LED_DISABLE);
+ if (rc < 0)
+ return rc;
+ }
+
+ for (i = 0; i < led->num_fnodes; i++) {
+ if (!led->fnode[i].led_on ||
+ !(snode->led_mask & BIT(led->fnode[i].id)))
+ continue;
+
+ addr_offset = led->fnode[i].id;
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_TGR_CURRENT(led->base + addr_offset),
+ FLASH_LED_CURRENT_MASK, 0);
+ if (rc < 0)
+ return rc;
+
+ led->fnode[i].led_on = false;
+
+ if (led->fnode[i].pinctrl) {
+ rc = pinctrl_select_state(led->fnode[i].pinctrl,
+ led->fnode[i].gpio_state_suspend);
+ if (rc < 0) {
+ pr_err("failed to disable GPIO, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ if (led->fnode[i].trigger & FLASH_LED_HW_SW_STROBE_SEL_BIT) {
+ rc = qpnp_flash_led_hw_strobe_enable(&led->fnode[i],
+ led->pdata->hw_strobe_option, false);
+ if (rc < 0) {
+ pr_err("Unable to disable hw strobe, rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+ }
+
+ snode->enabled = false;
+ return 0;
+}
+
+static int qpnp_flash_led_switch_set(struct flash_switch_data *snode, bool on)
+{
+ struct qpnp_flash_led *led = dev_get_drvdata(&snode->pdev->dev);
+ int rc, i, addr_offset;
+ u8 val, mask;
+
+ if (snode->enabled == on) {
+ pr_debug("Switch node is already %s!\n",
+ on ? "enabled" : "disabled");
+ return 0;
+ }
+
+ if (!on) {
+ rc = qpnp_flash_led_switch_disable(snode);
+ return rc;
+ }
+
+ /* Iterate over all leds for this switch node */
+ val = 0;
+ for (i = 0; i < led->num_fnodes; i++)
+ if (snode->led_mask & BIT(led->fnode[i].id))
+ val |= led->fnode[i].ires << (led->fnode[i].id * 2);
+
+ rc = qpnp_flash_led_masked_write(led, FLASH_LED_REG_IRES(led->base),
+ FLASH_LED_CURRENT_MASK, val);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_STROBE_CFG(led->base),
+ FLASH_LED_ENABLE_MASK,
+ led->pdata->hw_strobe_option);
+ if (rc < 0)
+ return rc;
+
+ val = 0;
+ for (i = 0; i < led->num_fnodes; i++) {
+ if (!led->fnode[i].led_on ||
+ !(snode->led_mask & BIT(led->fnode[i].id)))
+ continue;
+
+ addr_offset = led->fnode[i].id;
+ if (led->fnode[i].trigger & FLASH_LED_HW_SW_STROBE_SEL_BIT)
+ mask = FLASH_HW_STROBE_MASK;
+ else
+ mask = FLASH_LED_HW_SW_STROBE_SEL_BIT;
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_STROBE_CTRL(led->base + addr_offset),
+ mask, led->fnode[i].trigger);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_TGR_CURRENT(led->base + addr_offset),
+ FLASH_LED_CURRENT_MASK, led->fnode[i].current_reg_val);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_flash_led_write(led,
+ FLASH_LED_REG_SAFETY_TMR(led->base + addr_offset),
+ led->fnode[i].duration);
+ if (rc < 0)
+ return rc;
+
+ val |= FLASH_LED_ENABLE << led->fnode[i].id;
+
+ if (led->fnode[i].pinctrl) {
+ rc = pinctrl_select_state(led->fnode[i].pinctrl,
+ led->fnode[i].gpio_state_active);
+ if (rc < 0) {
+ pr_err("failed to enable GPIO rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ if (led->fnode[i].trigger & FLASH_LED_HW_SW_STROBE_SEL_BIT) {
+ rc = qpnp_flash_led_hw_strobe_enable(&led->fnode[i],
+ led->pdata->hw_strobe_option, true);
+ if (rc < 0) {
+ pr_err("Unable to enable hw strobe rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+ }
+
+ if (led->enable == 0) {
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_MOD_CTRL(led->base),
+ FLASH_LED_MOD_CTRL_MASK, FLASH_LED_MOD_ENABLE);
+ if (rc < 0)
+ return rc;
+ }
+ led->enable++;
+
+ if (led->trigger_lmh) {
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_MITIGATION_SW(led->base),
+ FLASH_LED_LMH_MITIGATION_EN_MASK,
+ FLASH_LED_LMH_MITIGATION_ENABLE);
+ if (rc < 0) {
+ pr_err("trigger lmh mitigation failed, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ if (led->trigger_chgr) {
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_REG_MITIGATION_SW(led->base),
+ FLASH_LED_CHGR_MITIGATION_EN_MASK,
+ FLASH_LED_CHGR_MITIGATION_ENABLE);
+ if (rc < 0) {
+ pr_err("trigger chgr mitigation failed, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ rc = qpnp_flash_led_masked_write(led,
+ FLASH_LED_EN_LED_CTRL(led->base),
+ snode->led_mask, val);
+ if (rc < 0)
+ return rc;
+
+ snode->enabled = true;
+ return 0;
+}
+
+int qpnp_flash_led_prepare(struct led_trigger *trig, int options,
+ int *max_current)
+{
+ struct led_classdev *led_cdev;
+ struct flash_switch_data *snode;
+ struct qpnp_flash_led *led;
+ int rc;
+
+ if (!trig) {
+ pr_err("Invalid led_trigger provided\n");
+ return -EINVAL;
+ }
+
+ led_cdev = trigger_to_lcdev(trig);
+ if (!led_cdev) {
+ pr_err("Invalid led_cdev in trigger %s\n", trig->name);
+ return -EINVAL;
+ }
+
+ snode = container_of(led_cdev, struct flash_switch_data, cdev);
+ led = dev_get_drvdata(&snode->pdev->dev);
+
+ if (!(options & FLASH_LED_PREPARE_OPTIONS_MASK)) {
+ pr_err("Invalid options %d\n", options);
+ return -EINVAL;
+ }
+
+ if (options & ENABLE_REGULATOR) {
+ rc = qpnp_flash_led_regulator_enable(led, snode, true);
+ if (rc < 0) {
+ pr_err("enable regulator failed, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ if (options & DISABLE_REGULATOR) {
+ rc = qpnp_flash_led_regulator_enable(led, snode, false);
+ if (rc < 0) {
+ pr_err("disable regulator failed, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ if (options & QUERY_MAX_CURRENT) {
+ rc = qpnp_flash_led_get_max_avail_current(led);
+ if (rc < 0) {
+ pr_err("query max current failed, rc=%d\n", rc);
+ return rc;
+ }
+ *max_current = rc;
+ }
+
+ led->trigger_chgr = false;
+ if (options & PRE_FLASH)
+ led->trigger_chgr = true;
+
+ return 0;
+}
+
+static void qpnp_flash_led_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct flash_node_data *fnode = NULL;
+ struct flash_switch_data *snode = NULL;
+ struct qpnp_flash_led *led = NULL;
+ int rc;
+
+ /*
+ * strncmp() must be used here since a prefix comparison is required
+ * in order to support names like led:switch_0 and led:flash_1.
+ */
+ if (!strncmp(led_cdev->name, "led:switch", strlen("led:switch"))) {
+ snode = container_of(led_cdev, struct flash_switch_data, cdev);
+ led = dev_get_drvdata(&snode->pdev->dev);
+ } else if (!strncmp(led_cdev->name, "led:flash", strlen("led:flash")) ||
+ !strncmp(led_cdev->name, "led:torch",
+ strlen("led:torch"))) {
+ fnode = container_of(led_cdev, struct flash_node_data, cdev);
+ led = dev_get_drvdata(&fnode->pdev->dev);
+ }
+
+ if (!led) {
+ pr_err("Failed to get flash driver data\n");
+ return;
+ }
+
+ spin_lock(&led->lock);
+ if (snode) {
+ rc = qpnp_flash_led_switch_set(snode, value > 0);
+ if (rc < 0)
+ pr_err("Failed to set flash LED switch rc=%d\n", rc);
+ } else if (fnode) {
+ qpnp_flash_led_node_set(fnode, value);
+ }
+
+ spin_unlock(&led->lock);
+}
+
+/* sysfs show function for flash_max_current */
+static ssize_t qpnp_flash_led_max_current_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int rc;
+ struct flash_switch_data *snode;
+ struct qpnp_flash_led *led;
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+
+ snode = container_of(led_cdev, struct flash_switch_data, cdev);
+ led = dev_get_drvdata(&snode->pdev->dev);
+
+ rc = qpnp_flash_led_get_max_avail_current(led);
+ if (rc < 0)
+ pr_err("query max current failed, rc=%d\n", rc);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", rc);
+}
+
+/* sysfs attributes exported by flash_led */
+static struct device_attribute qpnp_flash_led_attrs[] = {
+ __ATTR(max_current, 0664, qpnp_flash_led_max_current_show, NULL),
+};
+
+static int flash_led_psy_notifier_call(struct notifier_block *nb,
+ unsigned long ev, void *v)
+{
+ struct power_supply *psy = v;
+ struct qpnp_flash_led *led =
+ container_of(nb, struct qpnp_flash_led, nb);
+
+ if (ev != PSY_EVENT_PROP_CHANGED)
+ return NOTIFY_OK;
+
+ if (!strcmp(psy->desc->name, "bms")) {
+ led->bms_psy = power_supply_get_by_name("bms");
+ if (!led->bms_psy)
+ pr_err("Failed to get bms power_supply\n");
+ else
+ power_supply_unreg_notifier(&led->nb);
+ }
+
+ return NOTIFY_OK;
+}
+
+static int flash_led_psy_register_notifier(struct qpnp_flash_led *led)
+{
+ int rc;
+
+ led->nb.notifier_call = flash_led_psy_notifier_call;
+ rc = power_supply_reg_notifier(&led->nb);
+ if (rc < 0) {
+ pr_err("Couldn't register psy notifier, rc = %d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+/* irq handler */
+static irqreturn_t qpnp_flash_led_irq_handler(int irq, void *_led)
+{
+ struct qpnp_flash_led *led = _led;
+ enum flash_led_irq_type irq_type = INVALID_IRQ;
+ int rc;
+ u8 irq_status, led_status1, led_status2;
+
+ pr_debug("irq received, irq=%d\n", irq);
+
+ rc = qpnp_flash_led_read(led,
+ FLASH_LED_REG_INT_RT_STS(led->base), &irq_status);
+ if (rc < 0) {
+ pr_err("Failed to read interrupt status reg, rc=%d\n", rc);
+ goto exit;
+ }
+
+ if (irq == led->pdata->all_ramp_up_done_irq)
+ irq_type = ALL_RAMP_UP_DONE_IRQ;
+ else if (irq == led->pdata->all_ramp_down_done_irq)
+ irq_type = ALL_RAMP_DOWN_DONE_IRQ;
+ else if (irq == led->pdata->led_fault_irq)
+ irq_type = LED_FAULT_IRQ;
+
+ if (irq_type == ALL_RAMP_UP_DONE_IRQ)
+ atomic_notifier_call_chain(&irq_notifier_list,
+ irq_type, NULL);
+
+ if (irq_type == LED_FAULT_IRQ) {
+ rc = qpnp_flash_led_read(led,
+ FLASH_LED_REG_LED_STATUS1(led->base), &led_status1);
+ if (rc < 0) {
+ pr_err("Failed to read led_status1 reg, rc=%d\n", rc);
+ goto exit;
+ }
+
+ rc = qpnp_flash_led_read(led,
+ FLASH_LED_REG_LED_STATUS2(led->base), &led_status2);
+ if (rc < 0) {
+ pr_err("Failed to read led_status2 reg, rc=%d\n", rc);
+ goto exit;
+ }
+
+ if (led_status1)
+ pr_emerg("led short/open fault detected! led_status1=%x\n",
+ led_status1);
+
+ if (led_status2 & FLASH_LED_VPH_DROOP_FAULT_MASK)
+ pr_emerg("led vph_droop fault detected!\n");
+ }
+
+ pr_debug("irq handled, irq_type=%x, irq_status=%x\n", irq_type,
+ irq_status);
+
+exit:
+ return IRQ_HANDLED;
+}
+
+int qpnp_flash_led_register_irq_notifier(struct notifier_block *nb)
+{
+ return atomic_notifier_chain_register(&irq_notifier_list, nb);
+}
+
+int qpnp_flash_led_unregister_irq_notifier(struct notifier_block *nb)
+{
+ return atomic_notifier_chain_unregister(&irq_notifier_list, nb);
+}
+
+static int qpnp_flash_led_parse_each_led_dt(struct qpnp_flash_led *led,
+ struct flash_node_data *fnode, struct device_node *node)
+{
+ const char *temp_string;
+ int rc;
+ u32 val;
+ bool strobe_sel = 0, edge_trigger = 0, active_high = 0;
+
+ fnode->pdev = led->pdev;
+ fnode->cdev.brightness_set = qpnp_flash_led_brightness_set;
+ fnode->cdev.brightness_get = qpnp_flash_led_brightness_get;
+
+ rc = of_property_read_string(node, "qcom,led-name", &fnode->cdev.name);
+ if (rc < 0) {
+ pr_err("Unable to read flash LED names\n");
+ return rc;
+ }
+
+ rc = of_property_read_string(node, "label", &temp_string);
+ if (!rc) {
+ if (!strcmp(temp_string, "flash")) {
+ fnode->type = FLASH_LED_TYPE_FLASH;
+ } else if (!strcmp(temp_string, "torch")) {
+ fnode->type = FLASH_LED_TYPE_TORCH;
+ } else {
+ pr_err("Wrong flash LED type\n");
+ return rc;
+ }
+ } else {
+ pr_err("Unable to read flash LED label\n");
+ return rc;
+ }
+
+ rc = of_property_read_u32(node, "qcom,id", &val);
+ if (!rc) {
+ fnode->id = (u8)val;
+ } else {
+ pr_err("Unable to read flash LED ID\n");
+ return rc;
+ }
+
+ rc = of_property_read_string(node, "qcom,default-led-trigger",
+ &fnode->cdev.default_trigger);
+ if (rc < 0) {
+ pr_err("Unable to read trigger name\n");
+ return rc;
+ }
+
+ fnode->ires_ua = FLASH_LED_IRES_DEFAULT_UA;
+ fnode->ires = FLASH_LED_IRES_DEFAULT_VAL;
+ rc = of_property_read_u32(node, "qcom,ires-ua", &val);
+ if (!rc) {
+ fnode->ires_ua = val;
+ fnode->ires = FLASH_LED_IRES_BASE -
+ (val - FLASH_LED_IRES_MIN_UA) / FLASH_LED_IRES_DIVISOR;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read current resolution rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(node, "qcom,max-current", &val);
+ if (!rc) {
+ if (val < FLASH_LED_MIN_CURRENT_MA)
+ val = FLASH_LED_MIN_CURRENT_MA;
+ fnode->max_current = val;
+ fnode->cdev.max_brightness = val;
+ } else {
+ pr_err("Unable to read max current, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(node, "qcom,current-ma", &val);
+ if (!rc) {
+ if (val < FLASH_LED_MIN_CURRENT_MA ||
+ val > fnode->max_current)
+ pr_warn("Invalid operational current specified, capping it\n");
+ if (val < FLASH_LED_MIN_CURRENT_MA)
+ val = FLASH_LED_MIN_CURRENT_MA;
+ if (val > fnode->max_current)
+ val = fnode->max_current;
+ fnode->current_ma = val;
+ fnode->cdev.brightness = val;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read operational current, rc=%d\n", rc);
+ return rc;
+ }
+
+ fnode->duration = FLASH_LED_SAFETY_TMR_DISABLED;
+ rc = of_property_read_u32(node, "qcom,duration-ms", &val);
+ if (!rc) {
+ fnode->duration = (u8)(SAFETY_TMR_TO_REG_VAL(val) |
+ FLASH_LED_SAFETY_TMR_ENABLE);
+ } else if (rc == -EINVAL) {
+ if (fnode->type == FLASH_LED_TYPE_FLASH) {
+ pr_err("Timer duration is required for flash LED\n");
+ return rc;
+ }
+ } else {
+ pr_err("Unable to read timer duration\n");
+ return rc;
+ }
+
+ fnode->hdrm_val = FLASH_LED_HDRM_VOL_DEFAULT_MV;
+ rc = of_property_read_u32(node, "qcom,hdrm-voltage-mv", &val);
+ if (!rc) {
+ val = (val - FLASH_LED_HDRM_VOL_BASE_MV) /
+ FLASH_LED_HDRM_VOL_STEP_MV;
+ fnode->hdrm_val = (val << FLASH_LED_HDRM_VOL_SHIFT) &
+ FLASH_LED_HDRM_VOL_MASK;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read headroom voltage\n");
+ return rc;
+ }
+
+ rc = of_property_read_u32(node, "qcom,hdrm-vol-hi-lo-win-mv", &val);
+ if (!rc) {
+ fnode->hdrm_val |= (val / FLASH_LED_HDRM_VOL_STEP_MV) &
+ ~FLASH_LED_HDRM_VOL_MASK;
+ } else if (rc == -EINVAL) {
+ fnode->hdrm_val |= FLASH_LED_HDRM_VOL_HI_LO_WIN_DEFAULT_MV;
+ } else {
+ pr_err("Unable to read hdrm hi-lo window voltage\n");
+ return rc;
+ }
+
+ strobe_sel = of_property_read_bool(node, "qcom,hw-strobe-sel");
+ if (strobe_sel) {
+ edge_trigger = of_property_read_bool(node,
+ "qcom,hw-strobe-edge-trigger");
+ active_high = !of_property_read_bool(node,
+ "qcom,hw-strobe-active-low");
+ }
+ fnode->trigger = (strobe_sel << 2) | (edge_trigger << 1) | active_high;
+
+ if (fnode->trigger & FLASH_LED_HW_SW_STROBE_SEL_BIT) {
+ if (of_find_property(node, "qcom,hw-strobe-gpio", NULL)) {
+ fnode->hw_strobe_gpio = of_get_named_gpio(node,
+ "qcom,hw-strobe-gpio", 0);
+ if (fnode->hw_strobe_gpio < 0) {
+ pr_err("Invalid gpio specified\n");
+ return fnode->hw_strobe_gpio;
+ }
+ gpio_direction_output(fnode->hw_strobe_gpio, 0);
+ } else {
+ fnode->hw_strobe_gpio = -1;
+ fnode->hw_strobe_state_active =
+ pinctrl_lookup_state(fnode->pinctrl,
+ "strobe_enable");
+ if (IS_ERR_OR_NULL(fnode->hw_strobe_state_active)) {
+ pr_err("No active pin for hardware strobe, rc=%ld\n",
+ PTR_ERR(fnode->hw_strobe_state_active));
+ fnode->hw_strobe_state_active = NULL;
+ }
+
+ fnode->hw_strobe_state_suspend =
+ pinctrl_lookup_state(fnode->pinctrl,
+ "strobe_disable");
+ if (IS_ERR_OR_NULL(fnode->hw_strobe_state_suspend)) {
+ pr_err("No suspend pin for hardware strobe, rc=%ld\n",
+ PTR_ERR(fnode->hw_strobe_state_suspend)
+ );
+ fnode->hw_strobe_state_suspend = NULL;
+ }
+ }
+ }
+
+ rc = led_classdev_register(&led->pdev->dev, &fnode->cdev);
+ if (rc < 0) {
+ pr_err("Unable to register led node %d\n", fnode->id);
+ return rc;
+ }
+
+ fnode->cdev.dev->of_node = node;
+
+ fnode->pinctrl = devm_pinctrl_get(fnode->cdev.dev);
+ if (IS_ERR_OR_NULL(fnode->pinctrl)) {
+ pr_debug("No pinctrl defined\n");
+ fnode->pinctrl = NULL;
+ } else {
+ fnode->gpio_state_active =
+ pinctrl_lookup_state(fnode->pinctrl, "led_enable");
+ if (IS_ERR_OR_NULL(fnode->gpio_state_active)) {
+ pr_err("Cannot lookup LED active state\n");
+ devm_pinctrl_put(fnode->pinctrl);
+ fnode->pinctrl = NULL;
+ return PTR_ERR(fnode->gpio_state_active);
+ }
+
+ fnode->gpio_state_suspend =
+ pinctrl_lookup_state(fnode->pinctrl, "led_disable");
+ if (IS_ERR_OR_NULL(fnode->gpio_state_suspend)) {
+ pr_err("Cannot lookup LED disable state\n");
+ devm_pinctrl_put(fnode->pinctrl);
+ fnode->pinctrl = NULL;
+ return PTR_ERR(fnode->gpio_state_suspend);
+ }
+ }
+
+ return 0;
+}
+
+static int qpnp_flash_led_parse_and_register_switch(struct qpnp_flash_led *led,
+ struct flash_switch_data *snode,
+ struct device_node *node)
+{
+ int rc = 0, num;
+ char reg_name[16], reg_sup_name[16];
+
+ rc = of_property_read_string(node, "qcom,led-name", &snode->cdev.name);
+ if (rc < 0) {
+ pr_err("Failed to read switch node name, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = sscanf(snode->cdev.name, "led:switch_%d", &num);
+ if (!rc) {
+ pr_err("No number for switch device?\n");
+ return -EINVAL;
+ }
+
+ rc = of_property_read_string(node, "qcom,default-led-trigger",
+ &snode->cdev.default_trigger);
+ if (rc < 0) {
+ pr_err("Unable to read trigger name, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(node, "qcom,led-mask", &snode->led_mask);
+ if (rc < 0) {
+ pr_err("Unable to read led mask rc=%d\n", rc);
+ return rc;
+ }
+
+ if (snode->led_mask < 1 || snode->led_mask > 7) {
+ pr_err("Invalid value for led-mask\n");
+ return -EINVAL;
+ }
+
+ scnprintf(reg_name, sizeof(reg_name), "switch%d-supply", num);
+ if (of_find_property(led->pdev->dev.of_node, reg_name, NULL)) {
+ scnprintf(reg_sup_name, sizeof(reg_sup_name), "switch%d", num);
+ snode->vreg = devm_regulator_get(&led->pdev->dev, reg_sup_name);
+ if (IS_ERR_OR_NULL(snode->vreg)) {
+ rc = PTR_ERR(snode->vreg);
+ if (rc != -EPROBE_DEFER)
+ pr_err("Failed to get regulator, rc=%d\n", rc);
+ snode->vreg = NULL;
+ return rc;
+ }
+ }
+
+ snode->pdev = led->pdev;
+ snode->cdev.brightness_set = qpnp_flash_led_brightness_set;
+ snode->cdev.brightness_get = qpnp_flash_led_brightness_get;
+ snode->cdev.flags |= LED_KEEP_TRIGGER;
+ rc = led_classdev_register(&led->pdev->dev, &snode->cdev);
+ if (rc < 0) {
+ pr_err("Unable to register led switch node\n");
+ return rc;
+ }
+
+ snode->cdev.dev->of_node = node;
+ return 0;
+}
+
+static int get_code_from_table(int *table, int len, int value)
+{
+ int i;
+
+ for (i = 0; i < len; i++) {
+ if (value == table[i])
+ break;
+ }
+
+ if (i == len) {
+ pr_err("Couldn't find %d from table\n", value);
+ return -ENODATA;
+ }
+
+ return i;
+}
+
+static int qpnp_flash_led_parse_common_dt(struct qpnp_flash_led *led,
+ struct device_node *node)
+{
+ struct device_node *revid_node;
+ int rc;
+ u32 val;
+ bool short_circuit_det, open_circuit_det, vph_droop_det;
+
+ revid_node = of_parse_phandle(node, "qcom,pmic-revid", 0);
+ if (!revid_node) {
+ pr_err("Missing qcom,pmic-revid property - driver failed\n");
+ return -EINVAL;
+ }
+
+ led->pdata->pmic_rev_id = get_revid_data(revid_node);
+ if (IS_ERR_OR_NULL(led->pdata->pmic_rev_id)) {
+ pr_err("Unable to get pmic_revid rc=%ld\n",
+ PTR_ERR(led->pdata->pmic_rev_id));
+ /*
+ * the revid peripheral must be registered, any failure
+ * here only indicates that the rev-id module has not
+ * probed yet.
+ */
+ return -EPROBE_DEFER;
+ }
+
+ pr_debug("PMIC subtype %d Digital major %d\n",
+ led->pdata->pmic_rev_id->pmic_subtype,
+ led->pdata->pmic_rev_id->rev4);
+
+ led->pdata->hdrm_auto_mode_en = of_property_read_bool(node,
+ "qcom,hdrm-auto-mode");
+
+ led->pdata->isc_delay = FLASH_LED_ISC_DELAY_DEFAULT;
+ rc = of_property_read_u32(node, "qcom,isc-delay-us", &val);
+ if (!rc) {
+ led->pdata->isc_delay =
+ val >> FLASH_LED_ISC_WARMUP_DELAY_SHIFT;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read ISC delay, rc=%d\n", rc);
+ return rc;
+ }
+
+ led->pdata->warmup_delay = FLASH_LED_WARMUP_DELAY_DEFAULT;
+ rc = of_property_read_u32(node, "qcom,warmup-delay-us", &val);
+ if (!rc) {
+ led->pdata->warmup_delay =
+ val >> FLASH_LED_ISC_WARMUP_DELAY_SHIFT;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read WARMUP delay, rc=%d\n", rc);
+ return rc;
+ }
+
+ short_circuit_det =
+ of_property_read_bool(node, "qcom,short-circuit-det");
+ open_circuit_det = of_property_read_bool(node, "qcom,open-circuit-det");
+ vph_droop_det = of_property_read_bool(node, "qcom,vph-droop-det");
+ led->pdata->current_derate_en_cfg = (vph_droop_det << 2) |
+ (open_circuit_det << 1) | short_circuit_det;
+
+ led->pdata->thermal_derate_en =
+ of_property_read_bool(node, "qcom,thermal-derate-en");
+
+ if (led->pdata->thermal_derate_en) {
+ led->pdata->thermal_derate_current =
+ devm_kcalloc(&led->pdev->dev,
+ FLASH_LED_THERMAL_OTST_LEVELS,
+ sizeof(int), GFP_KERNEL);
+ if (!led->pdata->thermal_derate_current)
+ return -ENOMEM;
+
+ rc = of_property_read_u32_array(node,
+ "qcom,thermal-derate-current",
+ led->pdata->thermal_derate_current,
+ FLASH_LED_THERMAL_OTST_LEVELS);
+ if (rc < 0) {
+ pr_err("Unable to read thermal current limits, rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ led->pdata->otst_ramp_bkup_en =
+ !of_property_read_bool(node, "qcom,otst-ramp-back-up-dis");
+
+ led->pdata->thermal_derate_slow = -EINVAL;
+ rc = of_property_read_u32(node, "qcom,thermal-derate-slow", &val);
+ if (!rc) {
+ if (val < 0 || val > THERMAL_DERATE_SLOW_MAX) {
+ pr_err("Invalid thermal_derate_slow %d\n", val);
+ return -EINVAL;
+ }
+
+ led->pdata->thermal_derate_slow =
+ get_code_from_table(thermal_derate_slow_table,
+ ARRAY_SIZE(thermal_derate_slow_table), val);
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read thermal derate slow, rc=%d\n", rc);
+ return rc;
+ }
+
+ led->pdata->thermal_derate_fast = -EINVAL;
+ rc = of_property_read_u32(node, "qcom,thermal-derate-fast", &val);
+ if (!rc) {
+ if (val < 0 || val > THERMAL_DERATE_FAST_MAX) {
+ pr_err("Invalid thermal_derate_fast %d\n", val);
+ return -EINVAL;
+ }
+
+ led->pdata->thermal_derate_fast =
+ get_code_from_table(thermal_derate_fast_table,
+ ARRAY_SIZE(thermal_derate_fast_table), val);
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read thermal derate fast, rc=%d\n", rc);
+ return rc;
+ }
+
+ led->pdata->thermal_debounce = -EINVAL;
+ rc = of_property_read_u32(node, "qcom,thermal-debounce", &val);
+ if (!rc) {
+ if (val < 0 || val > THERMAL_DEBOUNCE_TIME_MAX) {
+ pr_err("Invalid thermal_debounce %d\n", val);
+ return -EINVAL;
+ }
+
+ if (val >= 0 && val < 16)
+ led->pdata->thermal_debounce = 0;
+ else
+ led->pdata->thermal_debounce = ilog2(val) - 3;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read thermal debounce, rc=%d\n", rc);
+ return rc;
+ }
+
+ led->pdata->thermal_hysteresis = -EINVAL;
+ rc = of_property_read_u32(node, "qcom,thermal-hysteresis", &val);
+ if (!rc) {
+ if (led->pdata->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE)
+ val = THERMAL_HYST_TEMP_TO_VAL(val, 20);
+ else
+ val = THERMAL_HYST_TEMP_TO_VAL(val, 15);
+
+ if (val < 0 || val > THERMAL_DERATE_HYSTERESIS_MAX) {
+ pr_err("Invalid thermal_derate_hysteresis %d\n", val);
+ return -EINVAL;
+ }
+
+ led->pdata->thermal_hysteresis = val;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read thermal hysteresis, rc=%d\n", rc);
+ return rc;
+ }
+
+ led->pdata->thermal_thrsh1 = -EINVAL;
+ rc = of_property_read_u32(node, "qcom,thermal-thrsh1", &val);
+ if (!rc) {
+ led->pdata->thermal_thrsh1 =
+ get_code_from_table(otst1_threshold_table,
+ ARRAY_SIZE(otst1_threshold_table), val);
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read thermal thrsh1, rc=%d\n", rc);
+ return rc;
+ }
+
+ led->pdata->thermal_thrsh2 = -EINVAL;
+ rc = of_property_read_u32(node, "qcom,thermal-thrsh2", &val);
+ if (!rc) {
+ led->pdata->thermal_thrsh2 =
+ get_code_from_table(otst2_threshold_table,
+ ARRAY_SIZE(otst2_threshold_table), val);
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read thermal thrsh2, rc=%d\n", rc);
+ return rc;
+ }
+
+ led->pdata->thermal_thrsh3 = -EINVAL;
+ rc = of_property_read_u32(node, "qcom,thermal-thrsh3", &val);
+ if (!rc) {
+ led->pdata->thermal_thrsh3 =
+ get_code_from_table(otst3_threshold_table,
+ ARRAY_SIZE(otst3_threshold_table), val);
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read thermal thrsh3, rc=%d\n", rc);
+ return rc;
+ }
+
+ led->pdata->vph_droop_debounce = FLASH_LED_VPH_DROOP_DEBOUNCE_DEFAULT;
+ rc = of_property_read_u32(node, "qcom,vph-droop-debounce-us", &val);
+ if (!rc) {
+ led->pdata->vph_droop_debounce =
+ VPH_DROOP_DEBOUNCE_US_TO_VAL(val);
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read VPH droop debounce, rc=%d\n", rc);
+ return rc;
+ }
+
+ if (led->pdata->vph_droop_debounce > FLASH_LED_DEBOUNCE_MAX) {
+ pr_err("Invalid VPH droop debounce specified\n");
+ return -EINVAL;
+ }
+
+ led->pdata->vph_droop_threshold = FLASH_LED_VPH_DROOP_THRESH_DEFAULT;
+ rc = of_property_read_u32(node, "qcom,vph-droop-threshold-mv", &val);
+ if (!rc) {
+ led->pdata->vph_droop_threshold =
+ VPH_DROOP_THRESH_MV_TO_VAL(val);
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read VPH droop threshold, rc=%d\n", rc);
+ return rc;
+ }
+
+ if (led->pdata->vph_droop_threshold > FLASH_LED_VPH_DROOP_THRESH_MAX) {
+ pr_err("Invalid VPH droop threshold specified\n");
+ return -EINVAL;
+ }
+
+ led->pdata->vph_droop_hysteresis =
+ FLASH_LED_VPH_DROOP_HYST_DEFAULT;
+ rc = of_property_read_u32(node, "qcom,vph-droop-hysteresis-mv", &val);
+ if (!rc) {
+ led->pdata->vph_droop_hysteresis =
+ VPH_DROOP_HYST_MV_TO_VAL(val);
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read VPH droop hysteresis, rc=%d\n", rc);
+ return rc;
+ }
+
+ if (led->pdata->vph_droop_hysteresis > FLASH_LED_HYSTERESIS_MAX) {
+ pr_err("Invalid VPH droop hysteresis specified\n");
+ return -EINVAL;
+ }
+
+ led->pdata->vph_droop_hysteresis <<= FLASH_LED_VPH_DROOP_HYST_SHIFT;
+
+ rc = of_property_read_u32(node, "qcom,hw-strobe-option", &val);
+ if (!rc) {
+ led->pdata->hw_strobe_option = (u8)val;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to parse hw strobe option, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(node, "qcom,led1n2-iclamp-low-ma", &val);
+ if (!rc) {
+ led->pdata->led1n2_iclamp_low_ma = val;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read led1n2_iclamp_low current, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(node, "qcom,led1n2-iclamp-mid-ma", &val);
+ if (!rc) {
+ led->pdata->led1n2_iclamp_mid_ma = val;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read led1n2_iclamp_mid current, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(node, "qcom,led3-iclamp-low-ma", &val);
+ if (!rc) {
+ led->pdata->led3_iclamp_low_ma = val;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read led3_iclamp_low current, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(node, "qcom,led3-iclamp-mid-ma", &val);
+ if (!rc) {
+ led->pdata->led3_iclamp_mid_ma = val;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to read led3_iclamp_mid current, rc=%d\n", rc);
+ return rc;
+ }
+
+ led->pdata->vled_max_uv = FLASH_LED_VLED_MAX_DEFAULT_UV;
+ rc = of_property_read_u32(node, "qcom,vled-max-uv", &val);
+ if (!rc) {
+ led->pdata->vled_max_uv = val;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to parse vled_max voltage, rc=%d\n", rc);
+ return rc;
+ }
+
+ led->pdata->ibatt_ocp_threshold_ua =
+ FLASH_LED_IBATT_OCP_THRESH_DEFAULT_UA;
+ rc = of_property_read_u32(node, "qcom,ibatt-ocp-threshold-ua", &val);
+ if (!rc) {
+ led->pdata->ibatt_ocp_threshold_ua = val;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to parse ibatt_ocp threshold, rc=%d\n", rc);
+ return rc;
+ }
+
+ led->pdata->rpara_uohm = FLASH_LED_RPARA_DEFAULT_UOHM;
+ rc = of_property_read_u32(node, "qcom,rparasitic-uohm", &val);
+ if (!rc) {
+ led->pdata->rpara_uohm = val;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to parse rparasitic, rc=%d\n", rc);
+ return rc;
+ }
+
+ led->pdata->lmh_ocv_threshold_uv =
+ FLASH_LED_LMH_OCV_THRESH_DEFAULT_UV;
+ rc = of_property_read_u32(node, "qcom,lmh-ocv-threshold-uv", &val);
+ if (!rc) {
+ led->pdata->lmh_ocv_threshold_uv = val;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to parse lmh ocv threshold, rc=%d\n", rc);
+ return rc;
+ }
+
+ led->pdata->lmh_rbatt_threshold_uohm =
+ FLASH_LED_LMH_RBATT_THRESH_DEFAULT_UOHM;
+ rc = of_property_read_u32(node, "qcom,lmh-rbatt-threshold-uohm", &val);
+ if (!rc) {
+ led->pdata->lmh_rbatt_threshold_uohm = val;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to parse lmh rbatt threshold, rc=%d\n", rc);
+ return rc;
+ }
+
+ led->pdata->lmh_level = FLASH_LED_LMH_LEVEL_DEFAULT;
+ rc = of_property_read_u32(node, "qcom,lmh-level", &val);
+ if (!rc) {
+ led->pdata->lmh_level = val;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to parse lmh_level, rc=%d\n", rc);
+ return rc;
+ }
+
+ led->pdata->lmh_mitigation_sel = FLASH_LED_MITIGATION_SEL_DEFAULT;
+ rc = of_property_read_u32(node, "qcom,lmh-mitigation-sel", &val);
+ if (!rc) {
+ led->pdata->lmh_mitigation_sel = val;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to parse lmh_mitigation_sel, rc=%d\n", rc);
+ return rc;
+ }
+
+ if (led->pdata->lmh_mitigation_sel > FLASH_LED_MITIGATION_SEL_MAX) {
+ pr_err("Invalid lmh_mitigation_sel specified\n");
+ return -EINVAL;
+ }
+
+ led->pdata->chgr_mitigation_sel = FLASH_LED_MITIGATION_SEL_DEFAULT;
+ rc = of_property_read_u32(node, "qcom,chgr-mitigation-sel", &val);
+ if (!rc) {
+ led->pdata->chgr_mitigation_sel = val;
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to parse chgr_mitigation_sel, rc=%d\n", rc);
+ return rc;
+ }
+
+ if (led->pdata->chgr_mitigation_sel > FLASH_LED_MITIGATION_SEL_MAX) {
+ pr_err("Invalid chgr_mitigation_sel specified\n");
+ return -EINVAL;
+ }
+
+ led->pdata->chgr_mitigation_sel <<= FLASH_LED_CHGR_MITIGATION_SEL_SHIFT;
+
+ led->pdata->iled_thrsh_val = FLASH_LED_MITIGATION_THRSH_DEFAULT;
+ rc = of_property_read_u32(node, "qcom,iled-thrsh-ma", &val);
+ if (!rc) {
+ led->pdata->iled_thrsh_val = MITIGATION_THRSH_MA_TO_VAL(val);
+ } else if (rc != -EINVAL) {
+ pr_err("Unable to parse iled_thrsh_val, rc=%d\n", rc);
+ return rc;
+ }
+
+ if (led->pdata->iled_thrsh_val > FLASH_LED_MITIGATION_THRSH_MAX) {
+ pr_err("Invalid iled_thrsh_val specified\n");
+ return -EINVAL;
+ }
+
+ led->pdata->all_ramp_up_done_irq =
+ of_irq_get_byname(node, "all-ramp-up-done-irq");
+ if (led->pdata->all_ramp_up_done_irq < 0)
+ pr_debug("all-ramp-up-done-irq not used\n");
+
+ led->pdata->all_ramp_down_done_irq =
+ of_irq_get_byname(node, "all-ramp-down-done-irq");
+ if (led->pdata->all_ramp_down_done_irq < 0)
+ pr_debug("all-ramp-down-done-irq not used\n");
+
+ led->pdata->led_fault_irq =
+ of_irq_get_byname(node, "led-fault-irq");
+ if (led->pdata->led_fault_irq < 0)
+ pr_debug("led-fault-irq not used\n");
+
+ return 0;
+}
+
+static int qpnp_flash_led_probe(struct platform_device *pdev)
+{
+ struct qpnp_flash_led *led;
+ struct device_node *node, *temp;
+ const char *temp_string;
+ unsigned int base;
+ int rc, i = 0, j = 0;
+
+ node = pdev->dev.of_node;
+ if (!node) {
+ pr_err("No flash LED nodes defined\n");
+ return -ENODEV;
+ }
+
+ rc = of_property_read_u32(node, "reg", &base);
+ if (rc < 0) {
+ pr_err("Couldn't find reg in node %s, rc = %d\n",
+ node->full_name, rc);
+ return rc;
+ }
+
+ led = devm_kzalloc(&pdev->dev, sizeof(struct qpnp_flash_led),
+ GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!led->regmap) {
+ pr_err("Couldn't get parent's regmap\n");
+ return -EINVAL;
+ }
+
+ led->base = base;
+ led->pdev = pdev;
+ led->pdata = devm_kzalloc(&pdev->dev,
+ sizeof(struct flash_led_platform_data), GFP_KERNEL);
+ if (!led->pdata)
+ return -ENOMEM;
+
+ rc = qpnp_flash_led_parse_common_dt(led, node);
+ if (rc < 0) {
+ pr_err("Failed to parse common flash LED device tree\n");
+ return rc;
+ }
+
+ for_each_available_child_of_node(node, temp) {
+ rc = of_property_read_string(temp, "label", &temp_string);
+ if (rc < 0) {
+ pr_err("Failed to parse label, rc=%d\n", rc);
+ return rc;
+ }
+
+ if (!strcmp("switch", temp_string)) {
+ led->num_snodes++;
+ } else if (!strcmp("flash", temp_string) ||
+ !strcmp("torch", temp_string)) {
+ led->num_fnodes++;
+ } else {
+ pr_err("Invalid label for led node\n");
+ return -EINVAL;
+ }
+ }
+
+ if (!led->num_fnodes) {
+ pr_err("No LED nodes defined\n");
+ return -ECHILD;
+ }
+
+ led->fnode = devm_kcalloc(&pdev->dev, led->num_fnodes,
+ sizeof(*led->fnode),
+ GFP_KERNEL);
+ if (!led->fnode)
+ return -ENOMEM;
+
+ led->snode = devm_kcalloc(&pdev->dev, led->num_snodes,
+ sizeof(*led->snode),
+ GFP_KERNEL);
+ if (!led->snode)
+ return -ENOMEM;
+
+ temp = NULL;
+ i = 0;
+ j = 0;
+ for_each_available_child_of_node(node, temp) {
+ rc = of_property_read_string(temp, "label", &temp_string);
+ if (rc < 0) {
+ pr_err("Failed to parse label, rc=%d\n", rc);
+ return rc;
+ }
+
+ if (!strcmp("flash", temp_string) ||
+ !strcmp("torch", temp_string)) {
+ rc = qpnp_flash_led_parse_each_led_dt(led,
+ &led->fnode[i++], temp);
+ if (rc < 0) {
+ pr_err("Unable to parse flash node %d rc=%d\n",
+ i, rc);
+ goto error_led_register;
+ }
+ }
+
+ if (!strcmp("switch", temp_string)) {
+ rc = qpnp_flash_led_parse_and_register_switch(led,
+ &led->snode[j++], temp);
+ if (rc < 0) {
+ pr_err("Unable to parse and register switch node, rc=%d\n",
+ rc);
+ goto error_switch_register;
+ }
+ }
+ }
+
+ /* setup irqs */
+ if (led->pdata->all_ramp_up_done_irq >= 0) {
+ rc = devm_request_threaded_irq(&led->pdev->dev,
+ led->pdata->all_ramp_up_done_irq,
+ NULL, qpnp_flash_led_irq_handler,
+ IRQF_ONESHOT,
+ "qpnp_flash_led_all_ramp_up_done_irq", led);
+ if (rc < 0) {
+ pr_err("Unable to request all_ramp_up_done(%d) IRQ(err:%d)\n",
+ led->pdata->all_ramp_up_done_irq, rc);
+ goto error_switch_register;
+ }
+ }
+
+ if (led->pdata->all_ramp_down_done_irq >= 0) {
+ rc = devm_request_threaded_irq(&led->pdev->dev,
+ led->pdata->all_ramp_down_done_irq,
+ NULL, qpnp_flash_led_irq_handler,
+ IRQF_ONESHOT,
+ "qpnp_flash_led_all_ramp_down_done_irq", led);
+ if (rc < 0) {
+ pr_err("Unable to request all_ramp_down_done(%d) IRQ(err:%d)\n",
+ led->pdata->all_ramp_down_done_irq, rc);
+ goto error_switch_register;
+ }
+ }
+
+ if (led->pdata->led_fault_irq >= 0) {
+ rc = devm_request_threaded_irq(&led->pdev->dev,
+ led->pdata->led_fault_irq,
+ NULL, qpnp_flash_led_irq_handler,
+ IRQF_ONESHOT,
+ "qpnp_flash_led_fault_irq", led);
+ if (rc < 0) {
+ pr_err("Unable to request led_fault(%d) IRQ(err:%d)\n",
+ led->pdata->led_fault_irq, rc);
+ goto error_switch_register;
+ }
+ }
+
+ led->bms_psy = power_supply_get_by_name("bms");
+ if (!led->bms_psy) {
+ rc = flash_led_psy_register_notifier(led);
+ if (rc < 0) {
+ pr_err("Couldn't register psy notifier, rc = %d\n", rc);
+ goto error_switch_register;
+ }
+ }
+
+ rc = qpnp_flash_led_init_settings(led);
+ if (rc < 0) {
+ pr_err("Failed to initialize flash LED, rc=%d\n", rc);
+ goto unreg_notifier;
+ }
+
+ for (i = 0; i < led->num_snodes; i++) {
+ for (j = 0; j < ARRAY_SIZE(qpnp_flash_led_attrs); j++) {
+ rc = sysfs_create_file(&led->snode[i].cdev.dev->kobj,
+ &qpnp_flash_led_attrs[j].attr);
+ if (rc < 0) {
+ pr_err("sysfs creation failed, rc=%d\n", rc);
+ goto sysfs_fail;
+ }
+ }
+ }
+
+ spin_lock_init(&led->lock);
+
+ dev_set_drvdata(&pdev->dev, led);
+
+ return 0;
+
+sysfs_fail:
+ for (--j; j >= 0; j--)
+ sysfs_remove_file(&led->snode[i].cdev.dev->kobj,
+ &qpnp_flash_led_attrs[j].attr);
+
+ for (--i; i >= 0; i--) {
+ for (j = 0; j < ARRAY_SIZE(qpnp_flash_led_attrs); j++)
+ sysfs_remove_file(&led->snode[i].cdev.dev->kobj,
+ &qpnp_flash_led_attrs[j].attr);
+ }
+
+ i = led->num_snodes;
+unreg_notifier:
+ power_supply_unreg_notifier(&led->nb);
+error_switch_register:
+ while (i > 0)
+ led_classdev_unregister(&led->snode[--i].cdev);
+ i = led->num_fnodes;
+error_led_register:
+ while (i > 0)
+ led_classdev_unregister(&led->fnode[--i].cdev);
+
+ return rc;
+}
+
+static int qpnp_flash_led_remove(struct platform_device *pdev)
+{
+ struct qpnp_flash_led *led = dev_get_drvdata(&pdev->dev);
+ int i, j;
+
+ for (i = 0; i < led->num_snodes; i++) {
+ for (j = 0; j < ARRAY_SIZE(qpnp_flash_led_attrs); j++)
+ sysfs_remove_file(&led->snode[i].cdev.dev->kobj,
+ &qpnp_flash_led_attrs[j].attr);
+
+ if (led->snode[i].regulator_on)
+ qpnp_flash_led_regulator_enable(led,
+ &led->snode[i], false);
+ }
+
+ while (i > 0)
+ led_classdev_unregister(&led->snode[--i].cdev);
+
+ i = led->num_fnodes;
+ while (i > 0)
+ led_classdev_unregister(&led->fnode[--i].cdev);
+
+ power_supply_unreg_notifier(&led->nb);
+ return 0;
+}
+
+const struct of_device_id qpnp_flash_led_match_table[] = {
+ { .compatible = "qcom,qpnp-flash-led-v2",},
+ { },
+};
+
+static struct platform_driver qpnp_flash_led_driver = {
+ .driver = {
+ .name = "qcom,qpnp-flash-led-v2",
+ .of_match_table = qpnp_flash_led_match_table,
+ },
+ .probe = qpnp_flash_led_probe,
+ .remove = qpnp_flash_led_remove,
+};
+
+static int __init qpnp_flash_led_init(void)
+{
+ return platform_driver_register(&qpnp_flash_led_driver);
+}
+late_initcall(qpnp_flash_led_init);
+
+static void __exit qpnp_flash_led_exit(void)
+{
+ platform_driver_unregister(&qpnp_flash_led_driver);
+}
+module_exit(qpnp_flash_led_exit);
+
+MODULE_DESCRIPTION("QPNP Flash LED driver v2");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("leds:leds-qpnp-flash-v2");
diff --git a/drivers/leds/leds-qpnp-flash.c b/drivers/leds/leds-qpnp-flash.c
new file mode 100644
index 0000000..c27c059
--- /dev/null
+++ b/drivers/leds/leds-qpnp-flash.c
@@ -0,0 +1,2649 @@
+/* Copyright (c) 2014-2017, The Linux Foundation. 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/regmap.h>
+#include <linux/errno.h>
+#include <linux/leds.h>
+#include <linux/slab.h>
+#include <linux/of_device.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
+#include <linux/workqueue.h>
+#include <linux/power_supply.h>
+#include <linux/leds-qpnp-flash.h>
+#include <linux/qpnp/qpnp-adc.h>
+#include <linux/qpnp/qpnp-revid.h>
+#include <linux/debugfs.h>
+#include <linux/uaccess.h>
+#include "leds.h"
+
+#define FLASH_LED_PERIPHERAL_SUBTYPE(base) (base + 0x05)
+#define FLASH_SAFETY_TIMER(base) (base + 0x40)
+#define FLASH_MAX_CURRENT(base) (base + 0x41)
+#define FLASH_LED0_CURRENT(base) (base + 0x42)
+#define FLASH_LED1_CURRENT(base) (base + 0x43)
+#define FLASH_CLAMP_CURRENT(base) (base + 0x44)
+#define FLASH_MODULE_ENABLE_CTRL(base) (base + 0x46)
+#define FLASH_LED_STROBE_CTRL(base) (base + 0x47)
+#define FLASH_LED_TMR_CTRL(base) (base + 0x48)
+#define FLASH_HEADROOM(base) (base + 0x4A)
+#define FLASH_STARTUP_DELAY(base) (base + 0x4B)
+#define FLASH_MASK_ENABLE(base) (base + 0x4C)
+#define FLASH_VREG_OK_FORCE(base) (base + 0x4F)
+#define FLASH_FAULT_DETECT(base) (base + 0x51)
+#define FLASH_THERMAL_DRATE(base) (base + 0x52)
+#define FLASH_CURRENT_RAMP(base) (base + 0x54)
+#define FLASH_VPH_PWR_DROOP(base) (base + 0x5A)
+#define FLASH_HDRM_SNS_ENABLE_CTRL0(base) (base + 0x5C)
+#define FLASH_HDRM_SNS_ENABLE_CTRL1(base) (base + 0x5D)
+#define FLASH_LED_UNLOCK_SECURE(base) (base + 0xD0)
+#define FLASH_PERPH_RESET_CTRL(base) (base + 0xDA)
+#define FLASH_TORCH(base) (base + 0xE4)
+
+#define FLASH_STATUS_REG_MASK 0xFF
+#define FLASH_LED_FAULT_STATUS(base) (base + 0x08)
+#define INT_LATCHED_STS(base) (base + 0x18)
+#define IN_POLARITY_HIGH(base) (base + 0x12)
+#define INT_SET_TYPE(base) (base + 0x11)
+#define INT_EN_SET(base) (base + 0x15)
+#define INT_LATCHED_CLR(base) (base + 0x14)
+
+#define FLASH_HEADROOM_MASK 0x03
+#define FLASH_STARTUP_DLY_MASK 0x03
+#define FLASH_VREG_OK_FORCE_MASK 0xC0
+#define FLASH_FAULT_DETECT_MASK 0x80
+#define FLASH_THERMAL_DERATE_MASK 0xBF
+#define FLASH_SECURE_MASK 0xFF
+#define FLASH_TORCH_MASK 0x03
+#define FLASH_CURRENT_MASK 0x7F
+#define FLASH_TMR_MASK 0x03
+#define FLASH_TMR_SAFETY 0x00
+#define FLASH_SAFETY_TIMER_MASK 0x7F
+#define FLASH_MODULE_ENABLE_MASK 0xE0
+#define FLASH_STROBE_MASK 0xC0
+#define FLASH_CURRENT_RAMP_MASK 0xBF
+#define FLASH_VPH_PWR_DROOP_MASK 0xF3
+#define FLASH_LED_HDRM_SNS_ENABLE_MASK 0x81
+#define FLASH_MASK_MODULE_CONTRL_MASK 0xE0
+#define FLASH_FOLLOW_OTST2_RB_MASK 0x08
+
+#define FLASH_LED_TRIGGER_DEFAULT "none"
+#define FLASH_LED_HEADROOM_DEFAULT_MV 500
+#define FLASH_LED_STARTUP_DELAY_DEFAULT_US 128
+#define FLASH_LED_CLAMP_CURRENT_DEFAULT_MA 200
+#define FLASH_LED_THERMAL_DERATE_THRESHOLD_DEFAULT_C 80
+#define FLASH_LED_RAMP_UP_STEP_DEFAULT_US 3
+#define FLASH_LED_RAMP_DN_STEP_DEFAULT_US 3
+#define FLASH_LED_VPH_PWR_DROOP_THRESHOLD_DEFAULT_MV 3200
+#define FLASH_LED_VPH_PWR_DROOP_DEBOUNCE_TIME_DEFAULT_US 10
+#define FLASH_LED_THERMAL_DERATE_RATE_DEFAULT_PERCENT 2
+#define FLASH_RAMP_UP_DELAY_US_MIN 1000
+#define FLASH_RAMP_UP_DELAY_US_MAX 1001
+#define FLASH_RAMP_DN_DELAY_US_MIN 2160
+#define FLASH_RAMP_DN_DELAY_US_MAX 2161
+#define FLASH_BOOST_REGULATOR_PROBE_DELAY_MS 2000
+#define FLASH_TORCH_MAX_LEVEL 0x0F
+#define FLASH_MAX_LEVEL 0x4F
+#define FLASH_LED_FLASH_HW_VREG_OK 0x40
+#define FLASH_LED_FLASH_SW_VREG_OK 0x80
+#define FLASH_LED_STROBE_TYPE_HW 0x04
+#define FLASH_DURATION_DIVIDER 10
+#define FLASH_LED_HEADROOM_DIVIDER 100
+#define FLASH_LED_HEADROOM_OFFSET 2
+#define FLASH_LED_MAX_CURRENT_MA 1000
+#define FLASH_LED_THERMAL_THRESHOLD_MIN 95
+#define FLASH_LED_THERMAL_DEVIDER 10
+#define FLASH_LED_VPH_DROOP_THRESHOLD_MIN_MV 2500
+#define FLASH_LED_VPH_DROOP_THRESHOLD_DIVIDER 100
+#define FLASH_LED_HDRM_SNS_ENABLE 0x81
+#define FLASH_LED_HDRM_SNS_DISABLE 0x01
+#define FLASH_LED_UA_PER_MA 1000
+#define FLASH_LED_MASK_MODULE_MASK2_ENABLE 0x20
+#define FLASH_LED_MASK3_ENABLE_SHIFT 7
+#define FLASH_LED_MODULE_CTRL_DEFAULT 0x60
+#define FLASH_LED_CURRENT_READING_DELAY_MIN 5000
+#define FLASH_LED_CURRENT_READING_DELAY_MAX 5001
+#define FLASH_LED_OPEN_FAULT_DETECTED 0xC
+
+#define FLASH_UNLOCK_SECURE 0xA5
+#define FLASH_LED_TORCH_ENABLE 0x00
+#define FLASH_LED_TORCH_DISABLE 0x03
+#define FLASH_MODULE_ENABLE 0x80
+#define FLASH_LED0_TRIGGER 0x80
+#define FLASH_LED1_TRIGGER 0x40
+#define FLASH_LED0_ENABLEMENT 0x40
+#define FLASH_LED1_ENABLEMENT 0x20
+#define FLASH_LED_DISABLE 0x00
+#define FLASH_LED_MIN_CURRENT_MA 13
+#define FLASH_SUBTYPE_DUAL 0x01
+#define FLASH_SUBTYPE_SINGLE 0x02
+
+/*
+ * ID represents physical LEDs for individual control purpose.
+ */
+enum flash_led_id {
+ FLASH_LED_0 = 0,
+ FLASH_LED_1,
+ FLASH_LED_SWITCH,
+};
+
+enum flash_led_type {
+ FLASH = 0,
+ TORCH,
+ SWITCH,
+};
+
+enum thermal_derate_rate {
+ RATE_1_PERCENT = 0,
+ RATE_1P25_PERCENT,
+ RATE_2_PERCENT,
+ RATE_2P5_PERCENT,
+ RATE_5_PERCENT,
+};
+
+enum current_ramp_steps {
+ RAMP_STEP_0P2_US = 0,
+ RAMP_STEP_0P4_US,
+ RAMP_STEP_0P8_US,
+ RAMP_STEP_1P6_US,
+ RAMP_STEP_3P3_US,
+ RAMP_STEP_6P7_US,
+ RAMP_STEP_13P5_US,
+ RAMP_STEP_27US,
+};
+
+struct flash_regulator_data {
+ struct regulator *regs;
+ const char *reg_name;
+ u32 max_volt_uv;
+};
+
+/*
+ * Configurations for each individual LED
+ */
+struct flash_node_data {
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ struct led_classdev cdev;
+ struct work_struct work;
+ struct flash_regulator_data *reg_data;
+ u16 max_current;
+ u16 prgm_current;
+ u16 prgm_current2;
+ u16 duration;
+ u8 id;
+ u8 type;
+ u8 trigger;
+ u8 enable;
+ u8 num_regulators;
+ bool flash_on;
+};
+
+/*
+ * Flash LED configuration read from device tree
+ */
+struct flash_led_platform_data {
+ unsigned int temp_threshold_num;
+ unsigned int temp_derate_curr_num;
+ unsigned int *die_temp_derate_curr_ma;
+ unsigned int *die_temp_threshold_degc;
+ u16 ramp_up_step;
+ u16 ramp_dn_step;
+ u16 vph_pwr_droop_threshold;
+ u16 headroom;
+ u16 clamp_current;
+ u8 thermal_derate_threshold;
+ u8 vph_pwr_droop_debounce_time;
+ u8 startup_dly;
+ u8 thermal_derate_rate;
+ bool pmic_charger_support;
+ bool self_check_en;
+ bool thermal_derate_en;
+ bool current_ramp_en;
+ bool vph_pwr_droop_en;
+ bool hdrm_sns_ch0_en;
+ bool hdrm_sns_ch1_en;
+ bool power_detect_en;
+ bool mask3_en;
+ bool follow_rb_disable;
+ bool die_current_derate_en;
+};
+
+struct qpnp_flash_led_buffer {
+ size_t rpos;
+ size_t wpos;
+ size_t len;
+ char data[0];
+};
+
+/*
+ * Flash LED data structure containing flash LED attributes
+ */
+struct qpnp_flash_led {
+ struct pmic_revid_data *revid_data;
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ struct flash_led_platform_data *pdata;
+ struct pinctrl *pinctrl;
+ struct pinctrl_state *gpio_state_active;
+ struct pinctrl_state *gpio_state_suspend;
+ struct flash_node_data *flash_node;
+ struct power_supply *battery_psy;
+ struct workqueue_struct *ordered_workq;
+ struct qpnp_vadc_chip *vadc_dev;
+ struct mutex flash_led_lock;
+ struct qpnp_flash_led_buffer *log;
+ struct dentry *dbgfs_root;
+ int num_leds;
+ u32 buffer_cnt;
+ u16 base;
+ u16 current_addr;
+ u16 current2_addr;
+ u8 peripheral_type;
+ u8 fault_reg;
+ bool gpio_enabled;
+ bool charging_enabled;
+ bool strobe_debug;
+ bool dbg_feature_en;
+ bool open_fault;
+};
+
+static u8 qpnp_flash_led_ctrl_dbg_regs[] = {
+ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
+ 0x4A, 0x4B, 0x4C, 0x4F, 0x51, 0x52, 0x54, 0x55, 0x5A, 0x5C, 0x5D,
+};
+
+static int flash_led_dbgfs_file_open(struct qpnp_flash_led *led,
+ struct file *file)
+{
+ struct qpnp_flash_led_buffer *log;
+ size_t logbufsize = SZ_4K;
+
+ log = kzalloc(logbufsize, GFP_KERNEL);
+ if (!log)
+ return -ENOMEM;
+
+ log->rpos = 0;
+ log->wpos = 0;
+ log->len = logbufsize - sizeof(*log);
+ led->log = log;
+
+ led->buffer_cnt = 1;
+ file->private_data = led;
+
+ return 0;
+}
+
+static int flash_led_dfs_open(struct inode *inode, struct file *file)
+{
+ struct qpnp_flash_led *led = inode->i_private;
+
+ return flash_led_dbgfs_file_open(led, file);
+}
+
+static int flash_led_dfs_close(struct inode *inode, struct file *file)
+{
+ struct qpnp_flash_led *led = file->private_data;
+
+ if (led && led->log) {
+ file->private_data = NULL;
+ kfree(led->log);
+ }
+
+ return 0;
+}
+
+static int print_to_log(struct qpnp_flash_led_buffer *log,
+ const char *fmt, ...)
+{
+ va_list args;
+ int cnt;
+ char *log_buf = &log->data[log->wpos];
+ size_t size = log->len - log->wpos;
+
+ va_start(args, fmt);
+ cnt = vscnprintf(log_buf, size, fmt, args);
+ va_end(args);
+
+ log->wpos += cnt;
+ return cnt;
+}
+
+static ssize_t flash_led_dfs_latched_reg_read(struct file *fp, char __user *buf,
+ size_t count, loff_t *ppos) {
+ struct qpnp_flash_led *led = fp->private_data;
+ struct qpnp_flash_led_buffer *log = led->log;
+ uint val;
+ int rc;
+ size_t len;
+ size_t ret;
+
+ if (log->rpos >= log->wpos && led->buffer_cnt == 0)
+ return 0;
+
+ rc = regmap_read(led->regmap, INT_LATCHED_STS(led->base), &val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Unable to read from address %x, rc(%d)\n",
+ INT_LATCHED_STS(led->base), rc);
+ return -EINVAL;
+ }
+ led->buffer_cnt--;
+
+ rc = print_to_log(log, "0x%05X ", INT_LATCHED_STS(led->base));
+ if (rc == 0)
+ return rc;
+
+ rc = print_to_log(log, "0x%02X ", val);
+ if (rc == 0)
+ return rc;
+
+ if (log->wpos > 0 && log->data[log->wpos - 1] == ' ')
+ log->data[log->wpos - 1] = '\n';
+
+ len = min(count, log->wpos - log->rpos);
+
+ ret = copy_to_user(buf, &log->data[log->rpos], len);
+ if (ret) {
+ pr_err("error copy register value to user\n");
+ return -EFAULT;
+ }
+
+ len -= ret;
+ *ppos += len;
+ log->rpos += len;
+
+ return len;
+}
+
+static ssize_t flash_led_dfs_fault_reg_read(struct file *fp, char __user *buf,
+ size_t count, loff_t *ppos) {
+ struct qpnp_flash_led *led = fp->private_data;
+ struct qpnp_flash_led_buffer *log = led->log;
+ int rc;
+ size_t len;
+ size_t ret;
+
+ if (log->rpos >= log->wpos && led->buffer_cnt == 0)
+ return 0;
+
+ led->buffer_cnt--;
+
+ rc = print_to_log(log, "0x%05X ", FLASH_LED_FAULT_STATUS(led->base));
+ if (rc == 0)
+ return rc;
+
+ rc = print_to_log(log, "0x%02X ", led->fault_reg);
+ if (rc == 0)
+ return rc;
+
+ if (log->wpos > 0 && log->data[log->wpos - 1] == ' ')
+ log->data[log->wpos - 1] = '\n';
+
+ len = min(count, log->wpos - log->rpos);
+
+ ret = copy_to_user(buf, &log->data[log->rpos], len);
+ if (ret) {
+ pr_err("error copy register value to user\n");
+ return -EFAULT;
+ }
+
+ len -= ret;
+ *ppos += len;
+ log->rpos += len;
+
+ return len;
+}
+
+static ssize_t flash_led_dfs_fault_reg_enable(struct file *file,
+ const char __user *buf, size_t count, loff_t *ppos) {
+
+ u8 *val;
+ int pos = 0;
+ int cnt = 0;
+ int data;
+ size_t ret = 0;
+
+ struct qpnp_flash_led *led = file->private_data;
+ char *kbuf = kmalloc(count + 1, GFP_KERNEL);
+
+ if (!kbuf)
+ return -ENOMEM;
+
+ ret = copy_from_user(kbuf, buf, count);
+ if (!ret) {
+ pr_err("failed to copy data from user\n");
+ ret = -EFAULT;
+ goto free_buf;
+ }
+
+ count -= ret;
+ *ppos += count;
+ kbuf[count] = '\0';
+ val = kbuf;
+ while (sscanf(kbuf + pos, "%i", &data) == 1) {
+ pos++;
+ val[cnt++] = data & 0xff;
+ }
+
+ if (!cnt)
+ goto free_buf;
+
+ ret = count;
+ if (*val == 1)
+ led->strobe_debug = true;
+ else
+ led->strobe_debug = false;
+
+free_buf:
+ kfree(kbuf);
+ return ret;
+}
+
+static ssize_t flash_led_dfs_dbg_enable(struct file *file,
+ const char __user *buf, size_t count, loff_t *ppos) {
+
+ u8 *val;
+ int pos = 0;
+ int cnt = 0;
+ int data;
+ size_t ret = 0;
+ struct qpnp_flash_led *led = file->private_data;
+ char *kbuf = kmalloc(count + 1, GFP_KERNEL);
+
+ if (!kbuf)
+ return -ENOMEM;
+
+ ret = copy_from_user(kbuf, buf, count);
+ if (ret == count) {
+ pr_err("failed to copy data from user\n");
+ ret = -EFAULT;
+ goto free_buf;
+ }
+ count -= ret;
+ *ppos += count;
+ kbuf[count] = '\0';
+ val = kbuf;
+ while (sscanf(kbuf + pos, "%i", &data) == 1) {
+ pos++;
+ val[cnt++] = data & 0xff;
+ }
+
+ if (!cnt)
+ goto free_buf;
+
+ ret = count;
+ if (*val == 1)
+ led->dbg_feature_en = true;
+ else
+ led->dbg_feature_en = false;
+
+free_buf:
+ kfree(kbuf);
+ return ret;
+}
+
+static const struct file_operations flash_led_dfs_latched_reg_fops = {
+ .open = flash_led_dfs_open,
+ .release = flash_led_dfs_close,
+ .read = flash_led_dfs_latched_reg_read,
+};
+
+static const struct file_operations flash_led_dfs_strobe_reg_fops = {
+ .open = flash_led_dfs_open,
+ .release = flash_led_dfs_close,
+ .read = flash_led_dfs_fault_reg_read,
+ .write = flash_led_dfs_fault_reg_enable,
+};
+
+static const struct file_operations flash_led_dfs_dbg_feature_fops = {
+ .open = flash_led_dfs_open,
+ .release = flash_led_dfs_close,
+ .write = flash_led_dfs_dbg_enable,
+};
+
+static int
+qpnp_led_masked_write(struct qpnp_flash_led *led, u16 addr, u8 mask, u8 val)
+{
+ int rc;
+
+ rc = regmap_update_bits(led->regmap, addr, mask, val);
+ if (rc)
+ dev_err(&led->pdev->dev,
+ "Unable to update_bits to addr=%x, rc(%d)\n", addr, rc);
+
+ dev_dbg(&led->pdev->dev, "Write 0x%02X to addr 0x%02X\n", val, addr);
+
+ return rc;
+}
+
+static int qpnp_flash_led_get_allowed_die_temp_curr(struct qpnp_flash_led *led,
+ int64_t die_temp_degc)
+{
+ int die_temp_curr_ma;
+
+ if (die_temp_degc >= led->pdata->die_temp_threshold_degc[0])
+ die_temp_curr_ma = 0;
+ else if (die_temp_degc >= led->pdata->die_temp_threshold_degc[1])
+ die_temp_curr_ma = led->pdata->die_temp_derate_curr_ma[0];
+ else if (die_temp_degc >= led->pdata->die_temp_threshold_degc[2])
+ die_temp_curr_ma = led->pdata->die_temp_derate_curr_ma[1];
+ else if (die_temp_degc >= led->pdata->die_temp_threshold_degc[3])
+ die_temp_curr_ma = led->pdata->die_temp_derate_curr_ma[2];
+ else if (die_temp_degc >= led->pdata->die_temp_threshold_degc[4])
+ die_temp_curr_ma = led->pdata->die_temp_derate_curr_ma[3];
+ else
+ die_temp_curr_ma = led->pdata->die_temp_derate_curr_ma[4];
+
+ return die_temp_curr_ma;
+}
+
+static int64_t qpnp_flash_led_get_die_temp(struct qpnp_flash_led *led)
+{
+ struct qpnp_vadc_result die_temp_result;
+ int rc;
+
+ rc = qpnp_vadc_read(led->vadc_dev, SPARE2, &die_temp_result);
+ if (rc) {
+ pr_err("failed to read the die temp\n");
+ return -EINVAL;
+ }
+
+ return die_temp_result.physical;
+}
+
+static int qpnp_get_pmic_revid(struct qpnp_flash_led *led)
+{
+ struct device_node *revid_dev_node;
+
+ revid_dev_node = of_parse_phandle(led->pdev->dev.of_node,
+ "qcom,pmic-revid", 0);
+ if (!revid_dev_node) {
+ dev_err(&led->pdev->dev,
+ "qcom,pmic-revid property missing\n");
+ return -EINVAL;
+ }
+
+ led->revid_data = get_revid_data(revid_dev_node);
+ if (IS_ERR(led->revid_data)) {
+ pr_err("Couldn't get revid data rc = %ld\n",
+ PTR_ERR(led->revid_data));
+ return PTR_ERR(led->revid_data);
+ }
+
+ return 0;
+}
+
+static int
+qpnp_flash_led_get_max_avail_current(struct flash_node_data *flash_node,
+ struct qpnp_flash_led *led)
+{
+ union power_supply_propval prop;
+ int64_t chg_temp_milidegc, die_temp_degc;
+ int max_curr_avail_ma = 2000;
+ int allowed_die_temp_curr_ma = 2000;
+ int rc;
+
+ if (led->pdata->power_detect_en) {
+ if (!led->battery_psy) {
+ dev_err(&led->pdev->dev,
+ "Failed to query power supply\n");
+ return -EINVAL;
+ }
+
+ /*
+ * When charging is enabled, enforce this new enablement
+ * sequence to reduce fuel gauge reading resolution.
+ */
+ if (led->charging_enabled) {
+ rc = qpnp_led_masked_write(led,
+ FLASH_MODULE_ENABLE_CTRL(led->base),
+ FLASH_MODULE_ENABLE, FLASH_MODULE_ENABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Module enable reg write failed\n");
+ return -EINVAL;
+ }
+
+ usleep_range(FLASH_LED_CURRENT_READING_DELAY_MIN,
+ FLASH_LED_CURRENT_READING_DELAY_MAX);
+ }
+
+ power_supply_get_property(led->battery_psy,
+ POWER_SUPPLY_PROP_FLASH_CURRENT_MAX, &prop);
+ if (!prop.intval) {
+ dev_err(&led->pdev->dev,
+ "battery too low for flash\n");
+ return -EINVAL;
+ }
+
+ max_curr_avail_ma = (prop.intval / FLASH_LED_UA_PER_MA);
+ }
+
+ /*
+ * When thermal mitigation is available, this logic will execute to
+ * derate current based upon the PMIC die temperature.
+ */
+ if (led->pdata->die_current_derate_en) {
+ chg_temp_milidegc = qpnp_flash_led_get_die_temp(led);
+ if (chg_temp_milidegc < 0)
+ return -EINVAL;
+
+ die_temp_degc = div_s64(chg_temp_milidegc, 1000);
+ allowed_die_temp_curr_ma =
+ qpnp_flash_led_get_allowed_die_temp_curr(led,
+ die_temp_degc);
+ if (allowed_die_temp_curr_ma < 0)
+ return -EINVAL;
+ }
+
+ max_curr_avail_ma = (max_curr_avail_ma >= allowed_die_temp_curr_ma)
+ ? allowed_die_temp_curr_ma : max_curr_avail_ma;
+
+ return max_curr_avail_ma;
+}
+
+static ssize_t qpnp_flash_led_die_temp_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct qpnp_flash_led *led;
+ struct flash_node_data *flash_node;
+ unsigned long val;
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ ssize_t ret;
+
+ ret = kstrtoul(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ flash_node = container_of(led_cdev, struct flash_node_data, cdev);
+ led = dev_get_drvdata(&flash_node->pdev->dev);
+
+ /*'0' for disable die_temp feature; non-zero to enable feature*/
+ if (val == 0)
+ led->pdata->die_current_derate_en = false;
+ else
+ led->pdata->die_current_derate_en = true;
+
+ return count;
+}
+
+static ssize_t qpnp_led_strobe_type_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct flash_node_data *flash_node;
+ unsigned long state;
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ ssize_t ret = -EINVAL;
+
+ ret = kstrtoul(buf, 10, &state);
+ if (ret)
+ return ret;
+
+ flash_node = container_of(led_cdev, struct flash_node_data, cdev);
+
+ /* '0' for sw strobe; '1' for hw strobe */
+ if (state == 1)
+ flash_node->trigger |= FLASH_LED_STROBE_TYPE_HW;
+ else
+ flash_node->trigger &= ~FLASH_LED_STROBE_TYPE_HW;
+
+ return count;
+}
+
+static ssize_t qpnp_flash_led_dump_regs_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct qpnp_flash_led *led;
+ struct flash_node_data *flash_node;
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ int rc, i, count = 0;
+ u16 addr;
+ uint val;
+
+ flash_node = container_of(led_cdev, struct flash_node_data, cdev);
+ led = dev_get_drvdata(&flash_node->pdev->dev);
+ for (i = 0; i < ARRAY_SIZE(qpnp_flash_led_ctrl_dbg_regs); i++) {
+ addr = led->base + qpnp_flash_led_ctrl_dbg_regs[i];
+ rc = regmap_read(led->regmap, addr, &val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Unable to read from addr=%x, rc(%d)\n",
+ addr, rc);
+ return -EINVAL;
+ }
+
+ count += snprintf(buf + count, PAGE_SIZE - count,
+ "REG_0x%x = 0x%02x\n", addr, val);
+
+ if (count >= PAGE_SIZE)
+ return PAGE_SIZE - 1;
+ }
+
+ return count;
+}
+
+static ssize_t qpnp_flash_led_current_derate_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct qpnp_flash_led *led;
+ struct flash_node_data *flash_node;
+ unsigned long val;
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ ssize_t ret;
+
+ ret = kstrtoul(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ flash_node = container_of(led_cdev, struct flash_node_data, cdev);
+ led = dev_get_drvdata(&flash_node->pdev->dev);
+
+ /*'0' for disable derate feature; non-zero to enable derate feature */
+ if (val == 0)
+ led->pdata->power_detect_en = false;
+ else
+ led->pdata->power_detect_en = true;
+
+ return count;
+}
+
+static ssize_t qpnp_flash_led_max_current_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct qpnp_flash_led *led;
+ struct flash_node_data *flash_node;
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ int max_curr_avail_ma = 0;
+
+ flash_node = container_of(led_cdev, struct flash_node_data, cdev);
+ led = dev_get_drvdata(&flash_node->pdev->dev);
+
+ if (led->flash_node[0].flash_on)
+ max_curr_avail_ma += led->flash_node[0].max_current;
+ if (led->flash_node[1].flash_on)
+ max_curr_avail_ma += led->flash_node[1].max_current;
+
+ if (led->pdata->power_detect_en ||
+ led->pdata->die_current_derate_en) {
+ max_curr_avail_ma =
+ qpnp_flash_led_get_max_avail_current(flash_node, led);
+
+ if (max_curr_avail_ma < 0)
+ return -EINVAL;
+ }
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", max_curr_avail_ma);
+}
+
+static struct device_attribute qpnp_flash_led_attrs[] = {
+ __ATTR(strobe, 0664, NULL, qpnp_led_strobe_type_store),
+ __ATTR(reg_dump, 0664, qpnp_flash_led_dump_regs_show, NULL),
+ __ATTR(enable_current_derate, 0664, NULL,
+ qpnp_flash_led_current_derate_store),
+ __ATTR(max_allowed_current, 0664, qpnp_flash_led_max_current_show,
+ NULL),
+ __ATTR(enable_die_temp_current_derate, 0664, NULL,
+ qpnp_flash_led_die_temp_store),
+};
+
+static int qpnp_flash_led_get_thermal_derate_rate(const char *rate)
+{
+ /*
+ * return 5% derate as default value if user specifies
+ * a value un-supported
+ */
+ if (strcmp(rate, "1_PERCENT") == 0)
+ return RATE_1_PERCENT;
+ else if (strcmp(rate, "1P25_PERCENT") == 0)
+ return RATE_1P25_PERCENT;
+ else if (strcmp(rate, "2_PERCENT") == 0)
+ return RATE_2_PERCENT;
+ else if (strcmp(rate, "2P5_PERCENT") == 0)
+ return RATE_2P5_PERCENT;
+ else if (strcmp(rate, "5_PERCENT") == 0)
+ return RATE_5_PERCENT;
+ else
+ return RATE_5_PERCENT;
+}
+
+static int qpnp_flash_led_get_ramp_step(const char *step)
+{
+ /*
+ * return 27 us as default value if user specifies
+ * a value un-supported
+ */
+ if (strcmp(step, "0P2_US") == 0)
+ return RAMP_STEP_0P2_US;
+ else if (strcmp(step, "0P4_US") == 0)
+ return RAMP_STEP_0P4_US;
+ else if (strcmp(step, "0P8_US") == 0)
+ return RAMP_STEP_0P8_US;
+ else if (strcmp(step, "1P6_US") == 0)
+ return RAMP_STEP_1P6_US;
+ else if (strcmp(step, "3P3_US") == 0)
+ return RAMP_STEP_3P3_US;
+ else if (strcmp(step, "6P7_US") == 0)
+ return RAMP_STEP_6P7_US;
+ else if (strcmp(step, "13P5_US") == 0)
+ return RAMP_STEP_13P5_US;
+ else
+ return RAMP_STEP_27US;
+}
+
+static u8 qpnp_flash_led_get_droop_debounce_time(u8 val)
+{
+ /*
+ * return 10 us as default value if user specifies
+ * a value un-supported
+ */
+ switch (val) {
+ case 0:
+ return 0;
+ case 10:
+ return 1;
+ case 32:
+ return 2;
+ case 64:
+ return 3;
+ default:
+ return 1;
+ }
+}
+
+static u8 qpnp_flash_led_get_startup_dly(u8 val)
+{
+ /*
+ * return 128 us as default value if user specifies
+ * a value un-supported
+ */
+ switch (val) {
+ case 10:
+ return 0;
+ case 32:
+ return 1;
+ case 64:
+ return 2;
+ case 128:
+ return 3;
+ default:
+ return 3;
+ }
+}
+
+static int
+qpnp_flash_led_get_peripheral_type(struct qpnp_flash_led *led)
+{
+ int rc;
+ uint val;
+
+ rc = regmap_read(led->regmap,
+ FLASH_LED_PERIPHERAL_SUBTYPE(led->base), &val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Unable to read peripheral subtype\n");
+ return -EINVAL;
+ }
+
+ return val;
+}
+
+static int qpnp_flash_led_module_disable(struct qpnp_flash_led *led,
+ struct flash_node_data *flash_node)
+{
+ union power_supply_propval psy_prop;
+ int rc;
+ uint val, tmp;
+
+ rc = regmap_read(led->regmap, FLASH_LED_STROBE_CTRL(led->base), &val);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Unable to read strobe reg\n");
+ return -EINVAL;
+ }
+
+ tmp = (~flash_node->trigger) & val;
+ if (!tmp) {
+ if (flash_node->type == TORCH) {
+ rc = qpnp_led_masked_write(led,
+ FLASH_LED_UNLOCK_SECURE(led->base),
+ FLASH_SECURE_MASK, FLASH_UNLOCK_SECURE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Secure reg write failed\n");
+ return -EINVAL;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_TORCH(led->base),
+ FLASH_TORCH_MASK, FLASH_LED_TORCH_DISABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Torch reg write failed\n");
+ return -EINVAL;
+ }
+ }
+
+ if (led->battery_psy &&
+ led->revid_data->pmic_subtype == PMI8996_SUBTYPE &&
+ !led->revid_data->rev3) {
+ psy_prop.intval = false;
+ rc = power_supply_set_property(led->battery_psy,
+ POWER_SUPPLY_PROP_FLASH_TRIGGER,
+ &psy_prop);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Failed to enble charger i/p current limit\n");
+ return -EINVAL;
+ }
+ }
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_MODULE_ENABLE_CTRL(led->base),
+ FLASH_MODULE_ENABLE_MASK,
+ FLASH_LED_MODULE_CTRL_DEFAULT);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Module disable failed\n");
+ return -EINVAL;
+ }
+
+ if (led->pinctrl) {
+ rc = pinctrl_select_state(led->pinctrl,
+ led->gpio_state_suspend);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "failed to disable GPIO\n");
+ return -EINVAL;
+ }
+ led->gpio_enabled = false;
+ }
+
+ if (led->battery_psy) {
+ psy_prop.intval = false;
+ rc = power_supply_set_property(led->battery_psy,
+ POWER_SUPPLY_PROP_FLASH_ACTIVE,
+ &psy_prop);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Failed to setup OTG pulse skip enable\n");
+ return -EINVAL;
+ }
+ }
+ }
+
+ if (flash_node->trigger & FLASH_LED0_TRIGGER) {
+ rc = qpnp_led_masked_write(led,
+ led->current_addr,
+ FLASH_CURRENT_MASK, 0x00);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "current register write failed\n");
+ return -EINVAL;
+ }
+ }
+
+ if (flash_node->trigger & FLASH_LED1_TRIGGER) {
+ rc = qpnp_led_masked_write(led,
+ led->current2_addr,
+ FLASH_CURRENT_MASK, 0x00);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "current register write failed\n");
+ return -EINVAL;
+ }
+ }
+
+ if (flash_node->id == FLASH_LED_SWITCH)
+ flash_node->trigger &= FLASH_LED_STROBE_TYPE_HW;
+
+ return 0;
+}
+
+static enum
+led_brightness qpnp_flash_led_brightness_get(struct led_classdev *led_cdev)
+{
+ return led_cdev->brightness;
+}
+
+static int flash_regulator_parse_dt(struct qpnp_flash_led *led,
+ struct flash_node_data *flash_node) {
+
+ int i = 0, rc;
+ struct device_node *node = flash_node->cdev.dev->of_node;
+ struct device_node *temp = NULL;
+ const char *temp_string;
+ u32 val;
+
+ flash_node->reg_data = devm_kzalloc(&led->pdev->dev,
+ sizeof(struct flash_regulator_data *) *
+ flash_node->num_regulators,
+ GFP_KERNEL);
+ if (!flash_node->reg_data) {
+ dev_err(&led->pdev->dev,
+ "Unable to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ for_each_child_of_node(node, temp) {
+ rc = of_property_read_string(temp, "regulator-name",
+ &temp_string);
+ if (!rc)
+ flash_node->reg_data[i].reg_name = temp_string;
+ else {
+ dev_err(&led->pdev->dev,
+ "Unable to read regulator name\n");
+ return rc;
+ }
+
+ rc = of_property_read_u32(temp, "max-voltage", &val);
+ if (!rc) {
+ flash_node->reg_data[i].max_volt_uv = val;
+ } else if (rc != -EINVAL) {
+ dev_err(&led->pdev->dev,
+ "Unable to read max voltage\n");
+ return rc;
+ }
+
+ i++;
+ }
+
+ return 0;
+}
+
+static int flash_regulator_setup(struct qpnp_flash_led *led,
+ struct flash_node_data *flash_node, bool on)
+{
+ int i, rc = 0;
+
+ if (on == false) {
+ i = flash_node->num_regulators;
+ goto error_regulator_setup;
+ }
+
+ for (i = 0; i < flash_node->num_regulators; i++) {
+ flash_node->reg_data[i].regs =
+ regulator_get(flash_node->cdev.dev,
+ flash_node->reg_data[i].reg_name);
+ if (IS_ERR(flash_node->reg_data[i].regs)) {
+ rc = PTR_ERR(flash_node->reg_data[i].regs);
+ dev_err(&led->pdev->dev,
+ "Failed to get regulator\n");
+ goto error_regulator_setup;
+ }
+
+ if (regulator_count_voltages(flash_node->reg_data[i].regs)
+ > 0) {
+ rc = regulator_set_voltage(flash_node->reg_data[i].regs,
+ flash_node->reg_data[i].max_volt_uv,
+ flash_node->reg_data[i].max_volt_uv);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "regulator set voltage failed\n");
+ regulator_put(flash_node->reg_data[i].regs);
+ goto error_regulator_setup;
+ }
+ }
+ }
+
+ return rc;
+
+error_regulator_setup:
+ while (i--) {
+ if (regulator_count_voltages(flash_node->reg_data[i].regs)
+ > 0) {
+ regulator_set_voltage(flash_node->reg_data[i].regs,
+ 0, flash_node->reg_data[i].max_volt_uv);
+ }
+
+ regulator_put(flash_node->reg_data[i].regs);
+ }
+
+ return rc;
+}
+
+static int flash_regulator_enable(struct qpnp_flash_led *led,
+ struct flash_node_data *flash_node, bool on)
+{
+ int i, rc = 0;
+
+ if (on == false) {
+ i = flash_node->num_regulators;
+ goto error_regulator_enable;
+ }
+
+ for (i = 0; i < flash_node->num_regulators; i++) {
+ rc = regulator_enable(flash_node->reg_data[i].regs);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "regulator enable failed\n");
+ goto error_regulator_enable;
+ }
+ }
+
+ return rc;
+
+error_regulator_enable:
+ while (i--)
+ regulator_disable(flash_node->reg_data[i].regs);
+
+ return rc;
+}
+
+int qpnp_flash_led_prepare(struct led_trigger *trig, int options,
+ int *max_current)
+{
+ struct led_classdev *led_cdev = trigger_to_lcdev(trig);
+ struct flash_node_data *flash_node;
+ struct qpnp_flash_led *led;
+ int rc;
+
+ if (!led_cdev) {
+ pr_err("Invalid led_trigger provided\n");
+ return -EINVAL;
+ }
+
+ flash_node = container_of(led_cdev, struct flash_node_data, cdev);
+ led = dev_get_drvdata(&flash_node->pdev->dev);
+
+ if (!(options & FLASH_LED_PREPARE_OPTIONS_MASK)) {
+ dev_err(&led->pdev->dev, "Invalid options %d\n", options);
+ return -EINVAL;
+ }
+
+ if (options & ENABLE_REGULATOR) {
+ rc = flash_regulator_enable(led, flash_node, true);
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "enable regulator failed, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ if (options & DISABLE_REGULATOR) {
+ rc = flash_regulator_enable(led, flash_node, false);
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "disable regulator failed, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ if (options & QUERY_MAX_CURRENT) {
+ rc = qpnp_flash_led_get_max_avail_current(flash_node, led);
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "query max current failed, rc=%d\n", rc);
+ return rc;
+ }
+ *max_current = rc;
+ }
+
+ return 0;
+}
+
+static void qpnp_flash_led_work(struct work_struct *work)
+{
+ struct flash_node_data *flash_node = container_of(work,
+ struct flash_node_data, work);
+ struct qpnp_flash_led *led = dev_get_drvdata(&flash_node->pdev->dev);
+ union power_supply_propval psy_prop;
+ int rc, brightness = flash_node->cdev.brightness;
+ int max_curr_avail_ma = 0;
+ int total_curr_ma = 0;
+ int i;
+ u8 val;
+ uint temp;
+
+ mutex_lock(&led->flash_led_lock);
+
+ if (!brightness)
+ goto turn_off;
+
+ if (led->open_fault) {
+ dev_err(&led->pdev->dev, "Open fault detected\n");
+ mutex_unlock(&led->flash_led_lock);
+ return;
+ }
+
+ if (!flash_node->flash_on && flash_node->num_regulators > 0) {
+ rc = flash_regulator_enable(led, flash_node, true);
+ if (rc) {
+ mutex_unlock(&led->flash_led_lock);
+ return;
+ }
+ }
+
+ if (!led->gpio_enabled && led->pinctrl) {
+ rc = pinctrl_select_state(led->pinctrl,
+ led->gpio_state_active);
+ if (rc) {
+ dev_err(&led->pdev->dev, "failed to enable GPIO\n");
+ goto error_enable_gpio;
+ }
+ led->gpio_enabled = true;
+ }
+
+ if (led->dbg_feature_en) {
+ rc = qpnp_led_masked_write(led,
+ INT_SET_TYPE(led->base),
+ FLASH_STATUS_REG_MASK, 0x1F);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "INT_SET_TYPE write failed\n");
+ goto exit_flash_led_work;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ IN_POLARITY_HIGH(led->base),
+ FLASH_STATUS_REG_MASK, 0x1F);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "IN_POLARITY_HIGH write failed\n");
+ goto exit_flash_led_work;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ INT_EN_SET(led->base),
+ FLASH_STATUS_REG_MASK, 0x1F);
+ if (rc) {
+ dev_err(&led->pdev->dev, "INT_EN_SET write failed\n");
+ goto exit_flash_led_work;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ INT_LATCHED_CLR(led->base),
+ FLASH_STATUS_REG_MASK, 0x1F);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "INT_LATCHED_CLR write failed\n");
+ goto exit_flash_led_work;
+ }
+ }
+
+ if (led->flash_node[led->num_leds - 1].id == FLASH_LED_SWITCH &&
+ flash_node->id != FLASH_LED_SWITCH) {
+ led->flash_node[led->num_leds - 1].trigger |=
+ (0x80 >> flash_node->id);
+ if (flash_node->id == FLASH_LED_0)
+ led->flash_node[led->num_leds - 1].prgm_current =
+ flash_node->prgm_current;
+ else if (flash_node->id == FLASH_LED_1)
+ led->flash_node[led->num_leds - 1].prgm_current2 =
+ flash_node->prgm_current;
+ }
+
+ if (flash_node->type == TORCH) {
+ rc = qpnp_led_masked_write(led,
+ FLASH_LED_UNLOCK_SECURE(led->base),
+ FLASH_SECURE_MASK, FLASH_UNLOCK_SECURE);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Secure reg write failed\n");
+ goto exit_flash_led_work;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_TORCH(led->base),
+ FLASH_TORCH_MASK, FLASH_LED_TORCH_ENABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Torch reg write failed\n");
+ goto exit_flash_led_work;
+ }
+
+ if (flash_node->id == FLASH_LED_SWITCH) {
+ val = (u8)(flash_node->prgm_current *
+ FLASH_TORCH_MAX_LEVEL
+ / flash_node->max_current);
+ rc = qpnp_led_masked_write(led,
+ led->current_addr,
+ FLASH_CURRENT_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Torch reg write failed\n");
+ goto exit_flash_led_work;
+ }
+
+ val = (u8)(flash_node->prgm_current2 *
+ FLASH_TORCH_MAX_LEVEL
+ / flash_node->max_current);
+ rc = qpnp_led_masked_write(led,
+ led->current2_addr,
+ FLASH_CURRENT_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Torch reg write failed\n");
+ goto exit_flash_led_work;
+ }
+ } else {
+ val = (u8)(flash_node->prgm_current *
+ FLASH_TORCH_MAX_LEVEL /
+ flash_node->max_current);
+ if (flash_node->id == FLASH_LED_0) {
+ rc = qpnp_led_masked_write(led,
+ led->current_addr,
+ FLASH_CURRENT_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "current reg write failed\n");
+ goto exit_flash_led_work;
+ }
+ } else {
+ rc = qpnp_led_masked_write(led,
+ led->current2_addr,
+ FLASH_CURRENT_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "current reg write failed\n");
+ goto exit_flash_led_work;
+ }
+ }
+ }
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_MAX_CURRENT(led->base),
+ FLASH_CURRENT_MASK, FLASH_TORCH_MAX_LEVEL);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Max current reg write failed\n");
+ goto exit_flash_led_work;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_MODULE_ENABLE_CTRL(led->base),
+ FLASH_MODULE_ENABLE_MASK, FLASH_MODULE_ENABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Module enable reg write failed\n");
+ goto exit_flash_led_work;
+ }
+
+ if (led->pdata->hdrm_sns_ch0_en ||
+ led->pdata->hdrm_sns_ch1_en) {
+ if (flash_node->id == FLASH_LED_SWITCH) {
+ rc = qpnp_led_masked_write(led,
+ FLASH_HDRM_SNS_ENABLE_CTRL0(led->base),
+ FLASH_LED_HDRM_SNS_ENABLE_MASK,
+ flash_node->trigger &
+ FLASH_LED0_TRIGGER ?
+ FLASH_LED_HDRM_SNS_ENABLE :
+ FLASH_LED_HDRM_SNS_DISABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Headroom sense enable failed\n");
+ goto exit_flash_led_work;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_HDRM_SNS_ENABLE_CTRL1(led->base),
+ FLASH_LED_HDRM_SNS_ENABLE_MASK,
+ flash_node->trigger &
+ FLASH_LED1_TRIGGER ?
+ FLASH_LED_HDRM_SNS_ENABLE :
+ FLASH_LED_HDRM_SNS_DISABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Headroom sense enable failed\n");
+ goto exit_flash_led_work;
+ }
+ } else if (flash_node->id == FLASH_LED_0) {
+ rc = qpnp_led_masked_write(led,
+ FLASH_HDRM_SNS_ENABLE_CTRL0(led->base),
+ FLASH_LED_HDRM_SNS_ENABLE_MASK,
+ FLASH_LED_HDRM_SNS_ENABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Headroom sense disable failed\n");
+ goto exit_flash_led_work;
+ }
+ } else if (flash_node->id == FLASH_LED_1) {
+ rc = qpnp_led_masked_write(led,
+ FLASH_HDRM_SNS_ENABLE_CTRL1(led->base),
+ FLASH_LED_HDRM_SNS_ENABLE_MASK,
+ FLASH_LED_HDRM_SNS_ENABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Headroom sense disable failed\n");
+ goto exit_flash_led_work;
+ }
+ }
+ }
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_LED_STROBE_CTRL(led->base),
+ (flash_node->id == FLASH_LED_SWITCH ? FLASH_STROBE_MASK
+ | FLASH_LED_STROBE_TYPE_HW
+ : flash_node->trigger |
+ FLASH_LED_STROBE_TYPE_HW),
+ flash_node->trigger);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Strobe reg write failed\n");
+ goto exit_flash_led_work;
+ }
+ } else if (flash_node->type == FLASH) {
+ if (flash_node->trigger & FLASH_LED0_TRIGGER)
+ max_curr_avail_ma += flash_node->max_current;
+ if (flash_node->trigger & FLASH_LED1_TRIGGER)
+ max_curr_avail_ma += flash_node->max_current;
+
+ psy_prop.intval = true;
+ rc = power_supply_set_property(led->battery_psy,
+ POWER_SUPPLY_PROP_FLASH_ACTIVE,
+ &psy_prop);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Failed to setup OTG pulse skip enable\n");
+ goto exit_flash_led_work;
+ }
+
+ if (led->pdata->power_detect_en ||
+ led->pdata->die_current_derate_en) {
+ if (led->battery_psy) {
+ power_supply_get_property(led->battery_psy,
+ POWER_SUPPLY_PROP_STATUS,
+ &psy_prop);
+ if (psy_prop.intval < 0) {
+ dev_err(&led->pdev->dev,
+ "Invalid battery status\n");
+ goto exit_flash_led_work;
+ }
+
+ if (psy_prop.intval ==
+ POWER_SUPPLY_STATUS_CHARGING)
+ led->charging_enabled = true;
+ else if (psy_prop.intval ==
+ POWER_SUPPLY_STATUS_DISCHARGING
+ || psy_prop.intval ==
+ POWER_SUPPLY_STATUS_NOT_CHARGING)
+ led->charging_enabled = false;
+ }
+ max_curr_avail_ma =
+ qpnp_flash_led_get_max_avail_current
+ (flash_node, led);
+ if (max_curr_avail_ma < 0) {
+ dev_err(&led->pdev->dev,
+ "Failed to get max avail curr\n");
+ goto exit_flash_led_work;
+ }
+ }
+
+ if (flash_node->id == FLASH_LED_SWITCH) {
+ if (flash_node->trigger & FLASH_LED0_TRIGGER)
+ total_curr_ma += flash_node->prgm_current;
+ if (flash_node->trigger & FLASH_LED1_TRIGGER)
+ total_curr_ma += flash_node->prgm_current2;
+
+ if (max_curr_avail_ma < total_curr_ma) {
+ flash_node->prgm_current =
+ (flash_node->prgm_current *
+ max_curr_avail_ma) / total_curr_ma;
+ flash_node->prgm_current2 =
+ (flash_node->prgm_current2 *
+ max_curr_avail_ma) / total_curr_ma;
+ }
+
+ val = (u8)(flash_node->prgm_current *
+ FLASH_MAX_LEVEL / flash_node->max_current);
+ rc = qpnp_led_masked_write(led,
+ led->current_addr, FLASH_CURRENT_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Current register write failed\n");
+ goto exit_flash_led_work;
+ }
+
+ val = (u8)(flash_node->prgm_current2 *
+ FLASH_MAX_LEVEL / flash_node->max_current);
+ rc = qpnp_led_masked_write(led,
+ led->current2_addr, FLASH_CURRENT_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Current register write failed\n");
+ goto exit_flash_led_work;
+ }
+ } else {
+ if (max_curr_avail_ma < flash_node->prgm_current) {
+ dev_err(&led->pdev->dev,
+ "battery only supprots %d mA\n",
+ max_curr_avail_ma);
+ flash_node->prgm_current =
+ (u16)max_curr_avail_ma;
+ }
+
+ val = (u8)(flash_node->prgm_current *
+ FLASH_MAX_LEVEL
+ / flash_node->max_current);
+ if (flash_node->id == FLASH_LED_0) {
+ rc = qpnp_led_masked_write(
+ led,
+ led->current_addr,
+ FLASH_CURRENT_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "current reg write failed\n");
+ goto exit_flash_led_work;
+ }
+ } else if (flash_node->id == FLASH_LED_1) {
+ rc = qpnp_led_masked_write(
+ led,
+ led->current2_addr,
+ FLASH_CURRENT_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "current reg write failed\n");
+ goto exit_flash_led_work;
+ }
+ }
+ }
+
+ val = (u8)((flash_node->duration - FLASH_DURATION_DIVIDER)
+ / FLASH_DURATION_DIVIDER);
+ rc = qpnp_led_masked_write(led,
+ FLASH_SAFETY_TIMER(led->base),
+ FLASH_SAFETY_TIMER_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Safety timer reg write failed\n");
+ goto exit_flash_led_work;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_MAX_CURRENT(led->base),
+ FLASH_CURRENT_MASK, FLASH_MAX_LEVEL);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Max current reg write failed\n");
+ goto exit_flash_led_work;
+ }
+
+ if (!led->charging_enabled) {
+ rc = qpnp_led_masked_write(led,
+ FLASH_MODULE_ENABLE_CTRL(led->base),
+ FLASH_MODULE_ENABLE, FLASH_MODULE_ENABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Module enable reg write failed\n");
+ goto exit_flash_led_work;
+ }
+
+ usleep_range(FLASH_RAMP_UP_DELAY_US_MIN,
+ FLASH_RAMP_UP_DELAY_US_MAX);
+ }
+
+ if (led->revid_data->pmic_subtype == PMI8996_SUBTYPE &&
+ !led->revid_data->rev3) {
+ rc = power_supply_set_property(led->battery_psy,
+ POWER_SUPPLY_PROP_FLASH_TRIGGER,
+ &psy_prop);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Failed to disable charger i/p curr limit\n");
+ goto exit_flash_led_work;
+ }
+ }
+
+ if (led->pdata->hdrm_sns_ch0_en ||
+ led->pdata->hdrm_sns_ch1_en) {
+ if (flash_node->id == FLASH_LED_SWITCH) {
+ rc = qpnp_led_masked_write(led,
+ FLASH_HDRM_SNS_ENABLE_CTRL0(led->base),
+ FLASH_LED_HDRM_SNS_ENABLE_MASK,
+ (flash_node->trigger &
+ FLASH_LED0_TRIGGER ?
+ FLASH_LED_HDRM_SNS_ENABLE :
+ FLASH_LED_HDRM_SNS_DISABLE));
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Headroom sense enable failed\n");
+ goto exit_flash_led_work;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_HDRM_SNS_ENABLE_CTRL1(led->base),
+ FLASH_LED_HDRM_SNS_ENABLE_MASK,
+ (flash_node->trigger &
+ FLASH_LED1_TRIGGER ?
+ FLASH_LED_HDRM_SNS_ENABLE :
+ FLASH_LED_HDRM_SNS_DISABLE));
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Headroom sense enable failed\n");
+ goto exit_flash_led_work;
+ }
+ } else if (flash_node->id == FLASH_LED_0) {
+ rc = qpnp_led_masked_write(led,
+ FLASH_HDRM_SNS_ENABLE_CTRL0(led->base),
+ FLASH_LED_HDRM_SNS_ENABLE_MASK,
+ FLASH_LED_HDRM_SNS_ENABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Headroom sense disable failed\n");
+ goto exit_flash_led_work;
+ }
+ } else if (flash_node->id == FLASH_LED_1) {
+ rc = qpnp_led_masked_write(led,
+ FLASH_HDRM_SNS_ENABLE_CTRL1(led->base),
+ FLASH_LED_HDRM_SNS_ENABLE_MASK,
+ FLASH_LED_HDRM_SNS_ENABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Headroom sense disable failed\n");
+ goto exit_flash_led_work;
+ }
+ }
+ }
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_LED_STROBE_CTRL(led->base),
+ (flash_node->id == FLASH_LED_SWITCH ? FLASH_STROBE_MASK
+ | FLASH_LED_STROBE_TYPE_HW
+ : flash_node->trigger |
+ FLASH_LED_STROBE_TYPE_HW),
+ flash_node->trigger);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Strobe reg write failed\n");
+ goto exit_flash_led_work;
+ }
+
+ if (led->strobe_debug && led->dbg_feature_en) {
+ udelay(2000);
+ rc = regmap_read(led->regmap,
+ FLASH_LED_FAULT_STATUS(led->base),
+ &temp);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Unable to read from addr= %x, rc(%d)\n",
+ FLASH_LED_FAULT_STATUS(led->base), rc);
+ goto exit_flash_led_work;
+ }
+ led->fault_reg = temp;
+ }
+ } else {
+ pr_err("Both Torch and Flash cannot be select at same time\n");
+ for (i = 0; i < led->num_leds; i++)
+ led->flash_node[i].flash_on = false;
+ goto turn_off;
+ }
+
+ flash_node->flash_on = true;
+ mutex_unlock(&led->flash_led_lock);
+
+ return;
+
+turn_off:
+ if (led->flash_node[led->num_leds - 1].id == FLASH_LED_SWITCH &&
+ flash_node->id != FLASH_LED_SWITCH)
+ led->flash_node[led->num_leds - 1].trigger &=
+ ~(0x80 >> flash_node->id);
+ if (flash_node->type == TORCH) {
+ /*
+ * Checking LED fault status detects hardware open fault.
+ * If fault occurs, all subsequent LED enablement requests
+ * will be rejected to protect hardware.
+ */
+ rc = regmap_read(led->regmap,
+ FLASH_LED_FAULT_STATUS(led->base), &temp);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Failed to read out fault status register\n");
+ goto exit_flash_led_work;
+ }
+
+ led->open_fault |= (val & FLASH_LED_OPEN_FAULT_DETECTED);
+ }
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_LED_STROBE_CTRL(led->base),
+ (flash_node->id == FLASH_LED_SWITCH ? FLASH_STROBE_MASK
+ | FLASH_LED_STROBE_TYPE_HW
+ : flash_node->trigger
+ | FLASH_LED_STROBE_TYPE_HW),
+ FLASH_LED_DISABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Strobe disable failed\n");
+ goto exit_flash_led_work;
+ }
+
+ usleep_range(FLASH_RAMP_DN_DELAY_US_MIN, FLASH_RAMP_DN_DELAY_US_MAX);
+exit_flash_hdrm_sns:
+ if (led->pdata->hdrm_sns_ch0_en) {
+ if (flash_node->id == FLASH_LED_0 ||
+ flash_node->id == FLASH_LED_SWITCH) {
+ rc = qpnp_led_masked_write(led,
+ FLASH_HDRM_SNS_ENABLE_CTRL0(led->base),
+ FLASH_LED_HDRM_SNS_ENABLE_MASK,
+ FLASH_LED_HDRM_SNS_DISABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Headroom sense disable failed\n");
+ goto exit_flash_hdrm_sns;
+ }
+ }
+ }
+
+ if (led->pdata->hdrm_sns_ch1_en) {
+ if (flash_node->id == FLASH_LED_1 ||
+ flash_node->id == FLASH_LED_SWITCH) {
+ rc = qpnp_led_masked_write(led,
+ FLASH_HDRM_SNS_ENABLE_CTRL1(led->base),
+ FLASH_LED_HDRM_SNS_ENABLE_MASK,
+ FLASH_LED_HDRM_SNS_DISABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Headroom sense disable failed\n");
+ goto exit_flash_hdrm_sns;
+ }
+ }
+ }
+exit_flash_led_work:
+ rc = qpnp_flash_led_module_disable(led, flash_node);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Module disable failed\n");
+ goto exit_flash_led_work;
+ }
+error_enable_gpio:
+ if (flash_node->flash_on && flash_node->num_regulators > 0)
+ flash_regulator_enable(led, flash_node, false);
+
+ flash_node->flash_on = false;
+ mutex_unlock(&led->flash_led_lock);
+}
+
+static void qpnp_flash_led_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct flash_node_data *flash_node;
+ struct qpnp_flash_led *led;
+
+ flash_node = container_of(led_cdev, struct flash_node_data, cdev);
+ led = dev_get_drvdata(&flash_node->pdev->dev);
+
+ if (value < LED_OFF) {
+ pr_err("Invalid brightness value\n");
+ return;
+ }
+
+ if (value > flash_node->cdev.max_brightness)
+ value = flash_node->cdev.max_brightness;
+
+ flash_node->cdev.brightness = value;
+ if (led->flash_node[led->num_leds - 1].id ==
+ FLASH_LED_SWITCH) {
+ if (flash_node->type == TORCH)
+ led->flash_node[led->num_leds - 1].type = TORCH;
+ else if (flash_node->type == FLASH)
+ led->flash_node[led->num_leds - 1].type = FLASH;
+
+ led->flash_node[led->num_leds - 1].max_current
+ = flash_node->max_current;
+
+ if (flash_node->id == FLASH_LED_0 ||
+ flash_node->id == FLASH_LED_1) {
+ if (value < FLASH_LED_MIN_CURRENT_MA && value != 0)
+ value = FLASH_LED_MIN_CURRENT_MA;
+
+ flash_node->prgm_current = value;
+ flash_node->flash_on = value ? true : false;
+ } else if (flash_node->id == FLASH_LED_SWITCH) {
+ if (!value) {
+ flash_node->prgm_current = 0;
+ flash_node->prgm_current2 = 0;
+ }
+ }
+ } else {
+ if (value < FLASH_LED_MIN_CURRENT_MA && value != 0)
+ value = FLASH_LED_MIN_CURRENT_MA;
+ flash_node->prgm_current = value;
+ }
+
+ queue_work(led->ordered_workq, &flash_node->work);
+}
+
+static int qpnp_flash_led_init_settings(struct qpnp_flash_led *led)
+{
+ int rc;
+ u8 val, temp_val;
+ uint val_int;
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_MODULE_ENABLE_CTRL(led->base),
+ FLASH_MODULE_ENABLE_MASK,
+ FLASH_LED_MODULE_CTRL_DEFAULT);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Module disable failed\n");
+ return rc;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_LED_STROBE_CTRL(led->base),
+ FLASH_STROBE_MASK, FLASH_LED_DISABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Strobe disable failed\n");
+ return rc;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_LED_TMR_CTRL(led->base),
+ FLASH_TMR_MASK, FLASH_TMR_SAFETY);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "LED timer ctrl reg write failed(%d)\n", rc);
+ return rc;
+ }
+
+ val = (u8)(led->pdata->headroom / FLASH_LED_HEADROOM_DIVIDER -
+ FLASH_LED_HEADROOM_OFFSET);
+ rc = qpnp_led_masked_write(led,
+ FLASH_HEADROOM(led->base),
+ FLASH_HEADROOM_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Headroom reg write failed\n");
+ return rc;
+ }
+
+ val = qpnp_flash_led_get_startup_dly(led->pdata->startup_dly);
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_STARTUP_DELAY(led->base),
+ FLASH_STARTUP_DLY_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Startup delay reg write failed\n");
+ return rc;
+ }
+
+ val = (u8)(led->pdata->clamp_current * FLASH_MAX_LEVEL /
+ FLASH_LED_MAX_CURRENT_MA);
+ rc = qpnp_led_masked_write(led,
+ FLASH_CLAMP_CURRENT(led->base),
+ FLASH_CURRENT_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Clamp current reg write failed\n");
+ return rc;
+ }
+
+ if (led->pdata->pmic_charger_support)
+ val = FLASH_LED_FLASH_HW_VREG_OK;
+ else
+ val = FLASH_LED_FLASH_SW_VREG_OK;
+ rc = qpnp_led_masked_write(led,
+ FLASH_VREG_OK_FORCE(led->base),
+ FLASH_VREG_OK_FORCE_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev, "VREG OK force reg write failed\n");
+ return rc;
+ }
+
+ if (led->pdata->self_check_en)
+ val = FLASH_MODULE_ENABLE;
+ else
+ val = FLASH_LED_DISABLE;
+ rc = qpnp_led_masked_write(led,
+ FLASH_FAULT_DETECT(led->base),
+ FLASH_FAULT_DETECT_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Fault detect reg write failed\n");
+ return rc;
+ }
+
+ val = 0x0;
+ val |= led->pdata->mask3_en << FLASH_LED_MASK3_ENABLE_SHIFT;
+ val |= FLASH_LED_MASK_MODULE_MASK2_ENABLE;
+ rc = qpnp_led_masked_write(led, FLASH_MASK_ENABLE(led->base),
+ FLASH_MASK_MODULE_CONTRL_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Mask module enable failed\n");
+ return rc;
+ }
+
+ rc = regmap_read(led->regmap, FLASH_PERPH_RESET_CTRL(led->base),
+ &val_int);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Unable to read from address %x, rc(%d)\n",
+ FLASH_PERPH_RESET_CTRL(led->base), rc);
+ return -EINVAL;
+ }
+ val = (u8)val_int;
+
+ if (led->pdata->follow_rb_disable) {
+ rc = qpnp_led_masked_write(led,
+ FLASH_LED_UNLOCK_SECURE(led->base),
+ FLASH_SECURE_MASK, FLASH_UNLOCK_SECURE);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Secure reg write failed\n");
+ return -EINVAL;
+ }
+
+ val |= FLASH_FOLLOW_OTST2_RB_MASK;
+ rc = qpnp_led_masked_write(led,
+ FLASH_PERPH_RESET_CTRL(led->base),
+ FLASH_FOLLOW_OTST2_RB_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "failed to reset OTST2_RB bit\n");
+ return rc;
+ }
+ } else {
+ rc = qpnp_led_masked_write(led,
+ FLASH_LED_UNLOCK_SECURE(led->base),
+ FLASH_SECURE_MASK, FLASH_UNLOCK_SECURE);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Secure reg write failed\n");
+ return -EINVAL;
+ }
+
+ val &= ~FLASH_FOLLOW_OTST2_RB_MASK;
+ rc = qpnp_led_masked_write(led,
+ FLASH_PERPH_RESET_CTRL(led->base),
+ FLASH_FOLLOW_OTST2_RB_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "failed to reset OTST2_RB bit\n");
+ return rc;
+ }
+ }
+
+ if (!led->pdata->thermal_derate_en)
+ val = 0x0;
+ else {
+ val = led->pdata->thermal_derate_en << 7;
+ val |= led->pdata->thermal_derate_rate << 3;
+ val |= (led->pdata->thermal_derate_threshold -
+ FLASH_LED_THERMAL_THRESHOLD_MIN) /
+ FLASH_LED_THERMAL_DEVIDER;
+ }
+ rc = qpnp_led_masked_write(led,
+ FLASH_THERMAL_DRATE(led->base),
+ FLASH_THERMAL_DERATE_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Thermal derate reg write failed\n");
+ return rc;
+ }
+
+ if (!led->pdata->current_ramp_en)
+ val = 0x0;
+ else {
+ val = led->pdata->current_ramp_en << 7;
+ val |= led->pdata->ramp_up_step << 3;
+ val |= led->pdata->ramp_dn_step;
+ }
+ rc = qpnp_led_masked_write(led,
+ FLASH_CURRENT_RAMP(led->base),
+ FLASH_CURRENT_RAMP_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Current ramp reg write failed\n");
+ return rc;
+ }
+
+ if (!led->pdata->vph_pwr_droop_en)
+ val = 0x0;
+ else {
+ val = led->pdata->vph_pwr_droop_en << 7;
+ val |= ((led->pdata->vph_pwr_droop_threshold -
+ FLASH_LED_VPH_DROOP_THRESHOLD_MIN_MV) /
+ FLASH_LED_VPH_DROOP_THRESHOLD_DIVIDER) << 4;
+ temp_val =
+ qpnp_flash_led_get_droop_debounce_time(
+ led->pdata->vph_pwr_droop_debounce_time);
+ if (temp_val == 0xFF) {
+ dev_err(&led->pdev->dev, "Invalid debounce time\n");
+ return temp_val;
+ }
+
+ val |= temp_val;
+ }
+ rc = qpnp_led_masked_write(led,
+ FLASH_VPH_PWR_DROOP(led->base),
+ FLASH_VPH_PWR_DROOP_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev, "VPH PWR droop reg write failed\n");
+ return rc;
+ }
+
+ led->battery_psy = power_supply_get_by_name("battery");
+ if (!led->battery_psy) {
+ dev_err(&led->pdev->dev,
+ "Failed to get battery power supply\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int qpnp_flash_led_parse_each_led_dt(struct qpnp_flash_led *led,
+ struct flash_node_data *flash_node)
+{
+ const char *temp_string;
+ struct device_node *node = flash_node->cdev.dev->of_node;
+ struct device_node *temp = NULL;
+ int rc = 0, num_regs = 0;
+ u32 val;
+
+ rc = of_property_read_string(node, "label", &temp_string);
+ if (!rc) {
+ if (strcmp(temp_string, "flash") == 0)
+ flash_node->type = FLASH;
+ else if (strcmp(temp_string, "torch") == 0)
+ flash_node->type = TORCH;
+ else if (strcmp(temp_string, "switch") == 0)
+ flash_node->type = SWITCH;
+ else {
+ dev_err(&led->pdev->dev, "Wrong flash LED type\n");
+ return -EINVAL;
+ }
+ } else if (rc < 0) {
+ dev_err(&led->pdev->dev, "Unable to read flash type\n");
+ return rc;
+ }
+
+ rc = of_property_read_u32(node, "qcom,current", &val);
+ if (!rc) {
+ if (val < FLASH_LED_MIN_CURRENT_MA)
+ val = FLASH_LED_MIN_CURRENT_MA;
+ flash_node->prgm_current = val;
+ } else if (rc != -EINVAL) {
+ dev_err(&led->pdev->dev, "Unable to read current\n");
+ return rc;
+ }
+
+ rc = of_property_read_u32(node, "qcom,id", &val);
+ if (!rc)
+ flash_node->id = (u8)val;
+ else if (rc != -EINVAL) {
+ dev_err(&led->pdev->dev, "Unable to read led ID\n");
+ return rc;
+ }
+
+ if (flash_node->type == SWITCH || flash_node->type == FLASH) {
+ rc = of_property_read_u32(node, "qcom,duration", &val);
+ if (!rc)
+ flash_node->duration = (u16)val;
+ else if (rc != -EINVAL) {
+ dev_err(&led->pdev->dev, "Unable to read duration\n");
+ return rc;
+ }
+ }
+
+ switch (led->peripheral_type) {
+ case FLASH_SUBTYPE_SINGLE:
+ flash_node->trigger = FLASH_LED0_TRIGGER;
+ break;
+ case FLASH_SUBTYPE_DUAL:
+ if (flash_node->id == FLASH_LED_0)
+ flash_node->trigger = FLASH_LED0_TRIGGER;
+ else if (flash_node->id == FLASH_LED_1)
+ flash_node->trigger = FLASH_LED1_TRIGGER;
+ break;
+ default:
+ dev_err(&led->pdev->dev, "Invalid peripheral type\n");
+ }
+
+ while ((temp = of_get_next_child(node, temp))) {
+ if (of_find_property(temp, "regulator-name", NULL))
+ num_regs++;
+ }
+
+ if (num_regs)
+ flash_node->num_regulators = num_regs;
+
+ return rc;
+}
+
+static int qpnp_flash_led_parse_common_dt(
+ struct qpnp_flash_led *led,
+ struct device_node *node)
+{
+ int rc;
+ u32 val, temp_val;
+ const char *temp;
+
+ led->pdata->headroom = FLASH_LED_HEADROOM_DEFAULT_MV;
+ rc = of_property_read_u32(node, "qcom,headroom", &val);
+ if (!rc)
+ led->pdata->headroom = (u16)val;
+ else if (rc != -EINVAL) {
+ dev_err(&led->pdev->dev, "Unable to read headroom\n");
+ return rc;
+ }
+
+ led->pdata->startup_dly = FLASH_LED_STARTUP_DELAY_DEFAULT_US;
+ rc = of_property_read_u32(node, "qcom,startup-dly", &val);
+ if (!rc)
+ led->pdata->startup_dly = (u8)val;
+ else if (rc != -EINVAL) {
+ dev_err(&led->pdev->dev, "Unable to read startup delay\n");
+ return rc;
+ }
+
+ led->pdata->clamp_current = FLASH_LED_CLAMP_CURRENT_DEFAULT_MA;
+ rc = of_property_read_u32(node, "qcom,clamp-current", &val);
+ if (!rc) {
+ if (val < FLASH_LED_MIN_CURRENT_MA)
+ val = FLASH_LED_MIN_CURRENT_MA;
+ led->pdata->clamp_current = (u16)val;
+ } else if (rc != -EINVAL) {
+ dev_err(&led->pdev->dev, "Unable to read clamp current\n");
+ return rc;
+ }
+
+ led->pdata->pmic_charger_support =
+ of_property_read_bool(node,
+ "qcom,pmic-charger-support");
+
+ led->pdata->self_check_en =
+ of_property_read_bool(node, "qcom,self-check-enabled");
+
+ led->pdata->thermal_derate_en =
+ of_property_read_bool(node,
+ "qcom,thermal-derate-enabled");
+
+ if (led->pdata->thermal_derate_en) {
+ led->pdata->thermal_derate_rate =
+ FLASH_LED_THERMAL_DERATE_RATE_DEFAULT_PERCENT;
+ rc = of_property_read_string(node, "qcom,thermal-derate-rate",
+ &temp);
+ if (!rc) {
+ temp_val =
+ qpnp_flash_led_get_thermal_derate_rate(temp);
+ if (temp_val < 0) {
+ dev_err(&led->pdev->dev,
+ "Invalid thermal derate rate\n");
+ return -EINVAL;
+ }
+
+ led->pdata->thermal_derate_rate = (u8)temp_val;
+ } else {
+ dev_err(&led->pdev->dev,
+ "Unable to read thermal derate rate\n");
+ return -EINVAL;
+ }
+
+ led->pdata->thermal_derate_threshold =
+ FLASH_LED_THERMAL_DERATE_THRESHOLD_DEFAULT_C;
+ rc = of_property_read_u32(node, "qcom,thermal-derate-threshold",
+ &val);
+ if (!rc)
+ led->pdata->thermal_derate_threshold = (u8)val;
+ else if (rc != -EINVAL) {
+ dev_err(&led->pdev->dev,
+ "Unable to read thermal derate threshold\n");
+ return rc;
+ }
+ }
+
+ led->pdata->current_ramp_en =
+ of_property_read_bool(node,
+ "qcom,current-ramp-enabled");
+ if (led->pdata->current_ramp_en) {
+ led->pdata->ramp_up_step = FLASH_LED_RAMP_UP_STEP_DEFAULT_US;
+ rc = of_property_read_string(node, "qcom,ramp_up_step", &temp);
+ if (!rc) {
+ temp_val = qpnp_flash_led_get_ramp_step(temp);
+ if (temp_val < 0) {
+ dev_err(&led->pdev->dev,
+ "Invalid ramp up step values\n");
+ return -EINVAL;
+ }
+ led->pdata->ramp_up_step = (u8)temp_val;
+ } else if (rc != -EINVAL) {
+ dev_err(&led->pdev->dev,
+ "Unable to read ramp up steps\n");
+ return rc;
+ }
+
+ led->pdata->ramp_dn_step = FLASH_LED_RAMP_DN_STEP_DEFAULT_US;
+ rc = of_property_read_string(node, "qcom,ramp_dn_step", &temp);
+ if (!rc) {
+ temp_val = qpnp_flash_led_get_ramp_step(temp);
+ if (temp_val < 0) {
+ dev_err(&led->pdev->dev,
+ "Invalid ramp down step values\n");
+ return rc;
+ }
+ led->pdata->ramp_dn_step = (u8)temp_val;
+ } else if (rc != -EINVAL) {
+ dev_err(&led->pdev->dev,
+ "Unable to read ramp down steps\n");
+ return rc;
+ }
+ }
+
+ led->pdata->vph_pwr_droop_en = of_property_read_bool(node,
+ "qcom,vph-pwr-droop-enabled");
+ if (led->pdata->vph_pwr_droop_en) {
+ led->pdata->vph_pwr_droop_threshold =
+ FLASH_LED_VPH_PWR_DROOP_THRESHOLD_DEFAULT_MV;
+ rc = of_property_read_u32(node,
+ "qcom,vph-pwr-droop-threshold", &val);
+ if (!rc) {
+ led->pdata->vph_pwr_droop_threshold = (u16)val;
+ } else if (rc != -EINVAL) {
+ dev_err(&led->pdev->dev,
+ "Unable to read VPH PWR droop threshold\n");
+ return rc;
+ }
+
+ led->pdata->vph_pwr_droop_debounce_time =
+ FLASH_LED_VPH_PWR_DROOP_DEBOUNCE_TIME_DEFAULT_US;
+ rc = of_property_read_u32(node,
+ "qcom,vph-pwr-droop-debounce-time", &val);
+ if (!rc)
+ led->pdata->vph_pwr_droop_debounce_time = (u8)val;
+ else if (rc != -EINVAL) {
+ dev_err(&led->pdev->dev,
+ "Unable to read VPH PWR droop debounce time\n");
+ return rc;
+ }
+ }
+
+ led->pdata->hdrm_sns_ch0_en = of_property_read_bool(node,
+ "qcom,headroom-sense-ch0-enabled");
+
+ led->pdata->hdrm_sns_ch1_en = of_property_read_bool(node,
+ "qcom,headroom-sense-ch1-enabled");
+
+ led->pdata->power_detect_en = of_property_read_bool(node,
+ "qcom,power-detect-enabled");
+
+ led->pdata->mask3_en = of_property_read_bool(node,
+ "qcom,otst2-module-enabled");
+
+ led->pdata->follow_rb_disable = of_property_read_bool(node,
+ "qcom,follow-otst2-rb-disabled");
+
+ led->pdata->die_current_derate_en = of_property_read_bool(node,
+ "qcom,die-current-derate-enabled");
+
+ if (led->pdata->die_current_derate_en) {
+ led->vadc_dev = qpnp_get_vadc(&led->pdev->dev, "die-temp");
+ if (IS_ERR(led->vadc_dev)) {
+ pr_err("VADC channel property Missing\n");
+ return -EINVAL;
+ }
+
+ if (of_find_property(node, "qcom,die-temp-threshold",
+ &led->pdata->temp_threshold_num)) {
+ if (led->pdata->temp_threshold_num > 0) {
+ led->pdata->die_temp_threshold_degc =
+ devm_kzalloc(&led->pdev->dev,
+ led->pdata->temp_threshold_num,
+ GFP_KERNEL);
+
+ if (led->pdata->die_temp_threshold_degc
+ == NULL) {
+ dev_err(&led->pdev->dev,
+ "failed to allocate die temp array\n");
+ return -ENOMEM;
+ }
+ led->pdata->temp_threshold_num /=
+ sizeof(unsigned int);
+
+ rc = of_property_read_u32_array(node,
+ "qcom,die-temp-threshold",
+ led->pdata->die_temp_threshold_degc,
+ led->pdata->temp_threshold_num);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "couldn't read temp threshold rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+ }
+
+ if (of_find_property(node, "qcom,die-temp-derate-current",
+ &led->pdata->temp_derate_curr_num)) {
+ if (led->pdata->temp_derate_curr_num > 0) {
+ led->pdata->die_temp_derate_curr_ma =
+ devm_kzalloc(&led->pdev->dev,
+ led->pdata->temp_derate_curr_num,
+ GFP_KERNEL);
+ if (led->pdata->die_temp_derate_curr_ma
+ == NULL) {
+ dev_err(&led->pdev->dev,
+ "failed to allocate die derate current array\n");
+ return -ENOMEM;
+ }
+ led->pdata->temp_derate_curr_num /=
+ sizeof(unsigned int);
+
+ rc = of_property_read_u32_array(node,
+ "qcom,die-temp-derate-current",
+ led->pdata->die_temp_derate_curr_ma,
+ led->pdata->temp_derate_curr_num);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "couldn't read temp limits rc =%d\n",
+ rc);
+ return rc;
+ }
+ }
+ }
+ if (led->pdata->temp_threshold_num !=
+ led->pdata->temp_derate_curr_num) {
+ pr_err("Both array size are not same\n");
+ return -EINVAL;
+ }
+ }
+
+ led->pinctrl = devm_pinctrl_get(&led->pdev->dev);
+ if (IS_ERR_OR_NULL(led->pinctrl)) {
+ dev_err(&led->pdev->dev, "Unable to acquire pinctrl\n");
+ led->pinctrl = NULL;
+ return 0;
+ }
+
+ led->gpio_state_active = pinctrl_lookup_state(led->pinctrl,
+ "flash_led_enable");
+ if (IS_ERR_OR_NULL(led->gpio_state_active)) {
+ dev_err(&led->pdev->dev, "Cannot lookup LED active state\n");
+ devm_pinctrl_put(led->pinctrl);
+ led->pinctrl = NULL;
+ return PTR_ERR(led->gpio_state_active);
+ }
+
+ led->gpio_state_suspend = pinctrl_lookup_state(led->pinctrl,
+ "flash_led_disable");
+ if (IS_ERR_OR_NULL(led->gpio_state_suspend)) {
+ dev_err(&led->pdev->dev, "Cannot lookup LED disable state\n");
+ devm_pinctrl_put(led->pinctrl);
+ led->pinctrl = NULL;
+ return PTR_ERR(led->gpio_state_suspend);
+ }
+
+ return 0;
+}
+
+static int qpnp_flash_led_probe(struct platform_device *pdev)
+{
+ struct qpnp_flash_led *led;
+ unsigned int base;
+ struct device_node *node, *temp;
+ struct dentry *root, *file;
+ int rc, i = 0, j, num_leds = 0;
+ u32 val;
+
+ root = NULL;
+ node = pdev->dev.of_node;
+ if (node == NULL) {
+ dev_info(&pdev->dev, "No flash device defined\n");
+ return -ENODEV;
+ }
+
+ rc = of_property_read_u32(pdev->dev.of_node, "reg", &base);
+ if (rc < 0) {
+ dev_err(&pdev->dev,
+ "Couldn't find reg in node = %s rc = %d\n",
+ pdev->dev.of_node->full_name, rc);
+ return rc;
+ }
+
+ led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!led->regmap) {
+ dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+ return -EINVAL;
+ }
+
+ led->base = base;
+ led->pdev = pdev;
+ led->current_addr = FLASH_LED0_CURRENT(led->base);
+ led->current2_addr = FLASH_LED1_CURRENT(led->base);
+
+ led->pdata = devm_kzalloc(&pdev->dev, sizeof(*led->pdata), GFP_KERNEL);
+ if (!led->pdata)
+ return -ENOMEM;
+
+ led->peripheral_type = (u8)qpnp_flash_led_get_peripheral_type(led);
+ if (led->peripheral_type < 0) {
+ dev_err(&pdev->dev, "Failed to get peripheral type\n");
+ return rc;
+ }
+
+ rc = qpnp_flash_led_parse_common_dt(led, node);
+ if (rc) {
+ dev_err(&pdev->dev,
+ "Failed to get common config for flash LEDs\n");
+ return rc;
+ }
+
+ rc = qpnp_flash_led_init_settings(led);
+ if (rc) {
+ dev_err(&pdev->dev, "Failed to initialize flash LED\n");
+ return rc;
+ }
+
+ rc = qpnp_get_pmic_revid(led);
+ if (rc)
+ return rc;
+
+ temp = NULL;
+ while ((temp = of_get_next_child(node, temp)))
+ num_leds++;
+
+ if (!num_leds)
+ return -ECHILD;
+
+ led->flash_node = devm_kzalloc(&pdev->dev,
+ (sizeof(struct flash_node_data) * num_leds),
+ GFP_KERNEL);
+ if (!led->flash_node) {
+ dev_err(&pdev->dev, "Unable to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ mutex_init(&led->flash_led_lock);
+
+ led->ordered_workq = alloc_ordered_workqueue("flash_led_workqueue", 0);
+ if (!led->ordered_workq) {
+ dev_err(&pdev->dev, "Failed to allocate ordered workqueue\n");
+ return -ENOMEM;
+ }
+
+ for_each_child_of_node(node, temp) {
+ led->flash_node[i].cdev.brightness_set =
+ qpnp_flash_led_brightness_set;
+ led->flash_node[i].cdev.brightness_get =
+ qpnp_flash_led_brightness_get;
+ led->flash_node[i].pdev = pdev;
+
+ INIT_WORK(&led->flash_node[i].work, qpnp_flash_led_work);
+ rc = of_property_read_string(temp, "qcom,led-name",
+ &led->flash_node[i].cdev.name);
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "Unable to read flash name\n");
+ return rc;
+ }
+
+ rc = of_property_read_string(temp, "qcom,default-led-trigger",
+ &led->flash_node[i].cdev.default_trigger);
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "Unable to read trigger name\n");
+ return rc;
+ }
+
+ rc = of_property_read_u32(temp, "qcom,max-current", &val);
+ if (!rc) {
+ if (val < FLASH_LED_MIN_CURRENT_MA)
+ val = FLASH_LED_MIN_CURRENT_MA;
+ led->flash_node[i].max_current = (u16)val;
+ led->flash_node[i].cdev.max_brightness = val;
+ } else {
+ dev_err(&led->pdev->dev,
+ "Unable to read max current\n");
+ return rc;
+ }
+ rc = led_classdev_register(&pdev->dev,
+ &led->flash_node[i].cdev);
+ if (rc) {
+ dev_err(&pdev->dev, "Unable to register led\n");
+ goto error_led_register;
+ }
+
+ led->flash_node[i].cdev.dev->of_node = temp;
+
+ rc = qpnp_flash_led_parse_each_led_dt(led, &led->flash_node[i]);
+ if (rc) {
+ dev_err(&pdev->dev,
+ "Failed to parse config for each LED\n");
+ goto error_led_register;
+ }
+
+ if (led->flash_node[i].num_regulators) {
+ rc = flash_regulator_parse_dt(led, &led->flash_node[i]);
+ if (rc) {
+ dev_err(&pdev->dev,
+ "Unable to parse regulator data\n");
+ goto error_led_register;
+ }
+
+ rc = flash_regulator_setup(led, &led->flash_node[i],
+ true);
+ if (rc) {
+ dev_err(&pdev->dev,
+ "Unable to set up regulator\n");
+ goto error_led_register;
+ }
+ }
+
+ for (j = 0; j < ARRAY_SIZE(qpnp_flash_led_attrs); j++) {
+ rc =
+ sysfs_create_file(&led->flash_node[i].cdev.dev->kobj,
+ &qpnp_flash_led_attrs[j].attr);
+ if (rc)
+ goto error_led_register;
+ }
+
+ i++;
+ }
+
+ led->num_leds = i;
+
+ root = debugfs_create_dir("flashLED", NULL);
+ if (IS_ERR_OR_NULL(root)) {
+ pr_err("Error creating top level directory err%ld",
+ (long)root);
+ if (PTR_ERR(root) == -ENODEV)
+ pr_err("debugfs is not enabled in kernel");
+ goto error_led_debugfs;
+ }
+
+ led->dbgfs_root = root;
+ file = debugfs_create_file("enable_debug", 0600, root, led,
+ &flash_led_dfs_dbg_feature_fops);
+ if (!file) {
+ pr_err("error creating 'enable_debug' entry\n");
+ goto error_led_debugfs;
+ }
+
+ file = debugfs_create_file("latched", 0600, root, led,
+ &flash_led_dfs_latched_reg_fops);
+ if (!file) {
+ pr_err("error creating 'latched' entry\n");
+ goto error_led_debugfs;
+ }
+
+ file = debugfs_create_file("strobe", 0600, root, led,
+ &flash_led_dfs_strobe_reg_fops);
+ if (!file) {
+ pr_err("error creating 'strobe' entry\n");
+ goto error_led_debugfs;
+ }
+
+ dev_set_drvdata(&pdev->dev, led);
+
+ return 0;
+
+error_led_debugfs:
+ i = led->num_leds - 1;
+ j = ARRAY_SIZE(qpnp_flash_led_attrs) - 1;
+error_led_register:
+ for (; i >= 0; i--) {
+ for (; j >= 0; j--)
+ sysfs_remove_file(&led->flash_node[i].cdev.dev->kobj,
+ &qpnp_flash_led_attrs[j].attr);
+ j = ARRAY_SIZE(qpnp_flash_led_attrs) - 1;
+ led_classdev_unregister(&led->flash_node[i].cdev);
+ }
+ debugfs_remove_recursive(root);
+ mutex_destroy(&led->flash_led_lock);
+ destroy_workqueue(led->ordered_workq);
+
+ return rc;
+}
+
+static int qpnp_flash_led_remove(struct platform_device *pdev)
+{
+ struct qpnp_flash_led *led = dev_get_drvdata(&pdev->dev);
+ int i, j;
+
+ for (i = led->num_leds - 1; i >= 0; i--) {
+ if (led->flash_node[i].reg_data) {
+ if (led->flash_node[i].flash_on)
+ flash_regulator_enable(led,
+ &led->flash_node[i], false);
+ flash_regulator_setup(led, &led->flash_node[i],
+ false);
+ }
+ for (j = 0; j < ARRAY_SIZE(qpnp_flash_led_attrs); j++)
+ sysfs_remove_file(&led->flash_node[i].cdev.dev->kobj,
+ &qpnp_flash_led_attrs[j].attr);
+ led_classdev_unregister(&led->flash_node[i].cdev);
+ }
+ debugfs_remove_recursive(led->dbgfs_root);
+ mutex_destroy(&led->flash_led_lock);
+ destroy_workqueue(led->ordered_workq);
+
+ return 0;
+}
+
+static const struct of_device_id spmi_match_table[] = {
+ { .compatible = "qcom,qpnp-flash-led",},
+ { },
+};
+
+static struct platform_driver qpnp_flash_led_driver = {
+ .driver = {
+ .name = "qcom,qpnp-flash-led",
+ .of_match_table = spmi_match_table,
+ },
+ .probe = qpnp_flash_led_probe,
+ .remove = qpnp_flash_led_remove,
+};
+
+static int __init qpnp_flash_led_init(void)
+{
+ return platform_driver_register(&qpnp_flash_led_driver);
+}
+late_initcall(qpnp_flash_led_init);
+
+static void __exit qpnp_flash_led_exit(void)
+{
+ platform_driver_unregister(&qpnp_flash_led_driver);
+}
+module_exit(qpnp_flash_led_exit);
+
+MODULE_DESCRIPTION("QPNP Flash LED driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("leds:leds-qpnp-flash");
diff --git a/drivers/leds/leds-qpnp-wled.c b/drivers/leds/leds-qpnp-wled.c
new file mode 100644
index 0000000..1e24c79
--- /dev/null
+++ b/drivers/leds/leds-qpnp-wled.c
@@ -0,0 +1,2221 @@
+/* Copyright (c) 2014-2017, The Linux Foundation. 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/regmap.h>
+#include <linux/errno.h>
+#include <linux/leds.h>
+#include <linux/slab.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/leds-qpnp-wled.h>
+#include <linux/qpnp/qpnp-revid.h>
+
+/* base addresses */
+#define QPNP_WLED_CTRL_BASE "qpnp-wled-ctrl-base"
+#define QPNP_WLED_SINK_BASE "qpnp-wled-sink-base"
+
+/* ctrl registers */
+#define QPNP_WLED_FAULT_STATUS(b) (b + 0x08)
+#define QPNP_WLED_EN_REG(b) (b + 0x46)
+#define QPNP_WLED_FDBK_OP_REG(b) (b + 0x48)
+#define QPNP_WLED_VREF_REG(b) (b + 0x49)
+#define QPNP_WLED_BOOST_DUTY_REG(b) (b + 0x4B)
+#define QPNP_WLED_SWITCH_FREQ_REG(b) (b + 0x4C)
+#define QPNP_WLED_OVP_REG(b) (b + 0x4D)
+#define QPNP_WLED_ILIM_REG(b) (b + 0x4E)
+#define QPNP_WLED_AMOLED_VOUT_REG(b) (b + 0x4F)
+#define QPNP_WLED_SOFTSTART_RAMP_DLY(b) (b + 0x53)
+#define QPNP_WLED_VLOOP_COMP_RES_REG(b) (b + 0x55)
+#define QPNP_WLED_VLOOP_COMP_GM_REG(b) (b + 0x56)
+#define QPNP_WLED_PSM_CTRL_REG(b) (b + 0x5B)
+#define QPNP_WLED_LCD_AUTO_PFM_REG(b) (b + 0x5C)
+#define QPNP_WLED_SC_PRO_REG(b) (b + 0x5E)
+#define QPNP_WLED_SWIRE_AVDD_REG(b) (b + 0x5F)
+#define QPNP_WLED_CTRL_SPARE_REG(b) (b + 0xDF)
+#define QPNP_WLED_TEST1_REG(b) (b + 0xE2)
+#define QPNP_WLED_TEST4_REG(b) (b + 0xE5)
+#define QPNP_WLED_REF_7P7_TRIM_REG(b) (b + 0xF2)
+
+#define QPNP_WLED_7P7_TRIM_MASK GENMASK(3, 0)
+#define QPNP_WLED_EN_MASK 0x7F
+#define QPNP_WLED_EN_SHIFT 7
+#define QPNP_WLED_FDBK_OP_MASK 0xF8
+#define QPNP_WLED_VREF_MASK GENMASK(3, 0)
+
+#define QPNP_WLED_VLOOP_COMP_RES_MASK 0xF0
+#define QPNP_WLED_VLOOP_COMP_RES_OVERWRITE 0x80
+#define QPNP_WLED_LOOP_COMP_RES_DFLT_AMOLED_KOHM 320
+#define QPNP_WLED_LOOP_COMP_RES_STEP_KOHM 20
+#define QPNP_WLED_LOOP_COMP_RES_MIN_KOHM 20
+#define QPNP_WLED_LOOP_COMP_RES_MAX_KOHM 320
+#define QPNP_WLED_VLOOP_COMP_GM_MASK GENMASK(3, 0)
+#define QPNP_WLED_VLOOP_COMP_GM_OVERWRITE 0x80
+#define QPNP_WLED_VLOOP_COMP_AUTO_GM_EN BIT(6)
+#define QPNP_WLED_VLOOP_COMP_AUTO_GM_THRESH_MASK GENMASK(5, 4)
+#define QPNP_WLED_VLOOP_COMP_AUTO_GM_THRESH_SHIFT 4
+#define QPNP_WLED_LOOP_EA_GM_DFLT_AMOLED_PMI8994 0x03
+#define QPNP_WLED_LOOP_GM_DFLT_AMOLED_PMI8998 0x09
+#define QPNP_WLED_LOOP_GM_DFLT_WLED 0x09
+#define QPNP_WLED_LOOP_EA_GM_MIN 0x0
+#define QPNP_WLED_LOOP_EA_GM_MAX 0xF
+#define QPNP_WLED_LOOP_AUTO_GM_THRESH_MAX 3
+#define QPNP_WLED_LOOP_AUTO_GM_DFLT_THRESH 1
+#define QPNP_WLED_VREF_PSM_MASK 0xF8
+#define QPNP_WLED_VREF_PSM_STEP_MV 50
+#define QPNP_WLED_VREF_PSM_MIN_MV 400
+#define QPNP_WLED_VREF_PSM_MAX_MV 750
+#define QPNP_WLED_VREF_PSM_DFLT_AMOLED_MV 450
+#define QPNP_WLED_PSM_CTRL_OVERWRITE 0x80
+#define QPNP_WLED_LCD_AUTO_PFM_DFLT_THRESH 1
+#define QPNP_WLED_LCD_AUTO_PFM_THRESH_MAX 0xF
+#define QPNP_WLED_LCD_AUTO_PFM_EN_SHIFT 7
+#define QPNP_WLED_LCD_AUTO_PFM_EN_BIT BIT(7)
+#define QPNP_WLED_LCD_AUTO_PFM_THRESH_MASK GENMASK(3, 0)
+
+#define QPNP_WLED_ILIM_MASK GENMASK(2, 0)
+#define QPNP_WLED_ILIM_OVERWRITE BIT(7)
+#define PMI8994_WLED_ILIM_MIN_MA 105
+#define PMI8994_WLED_ILIM_MAX_MA 1980
+#define PMI8994_WLED_DFLT_ILIM_MA 980
+#define PMI8994_AMOLED_DFLT_ILIM_MA 385
+#define PMI8998_WLED_ILIM_MAX_MA 1500
+#define PMI8998_WLED_DFLT_ILIM_MA 970
+#define PMI8998_AMOLED_DFLT_ILIM_MA 620
+#define QPNP_WLED_BOOST_DUTY_MASK 0xFC
+#define QPNP_WLED_BOOST_DUTY_STEP_NS 52
+#define QPNP_WLED_BOOST_DUTY_MIN_NS 26
+#define QPNP_WLED_BOOST_DUTY_MAX_NS 156
+#define QPNP_WLED_DEF_BOOST_DUTY_NS 104
+#define QPNP_WLED_SWITCH_FREQ_MASK 0x70
+#define QPNP_WLED_SWITCH_FREQ_800_KHZ 800
+#define QPNP_WLED_SWITCH_FREQ_1600_KHZ 1600
+#define QPNP_WLED_SWITCH_FREQ_OVERWRITE 0x80
+#define QPNP_WLED_OVP_MASK GENMASK(1, 0)
+#define QPNP_WLED_TEST4_EN_DEB_BYPASS_ILIM_BIT BIT(6)
+#define QPNP_WLED_TEST4_EN_SH_FOR_SS_BIT BIT(5)
+#define QPNP_WLED_TEST4_EN_CLAMP_BIT BIT(4)
+#define QPNP_WLED_TEST4_EN_SOFT_START_BIT BIT(1)
+#define QPNP_WLED_TEST4_EN_VREF_UP \
+ (QPNP_WLED_TEST4_EN_SH_FOR_SS_BIT | \
+ QPNP_WLED_TEST4_EN_CLAMP_BIT | \
+ QPNP_WLED_TEST4_EN_SOFT_START_BIT)
+#define QPNP_WLED_TEST4_EN_IIND_UP 0x1
+
+/* sink registers */
+#define QPNP_WLED_CURR_SINK_REG(b) (b + 0x46)
+#define QPNP_WLED_SYNC_REG(b) (b + 0x47)
+#define QPNP_WLED_MOD_REG(b) (b + 0x4A)
+#define QPNP_WLED_HYB_THRES_REG(b) (b + 0x4B)
+#define QPNP_WLED_MOD_EN_REG(b, n) (b + 0x50 + (n * 0x10))
+#define QPNP_WLED_SYNC_DLY_REG(b, n) (QPNP_WLED_MOD_EN_REG(b, n) + 0x01)
+#define QPNP_WLED_FS_CURR_REG(b, n) (QPNP_WLED_MOD_EN_REG(b, n) + 0x02)
+#define QPNP_WLED_CABC_REG(b, n) (QPNP_WLED_MOD_EN_REG(b, n) + 0x06)
+#define QPNP_WLED_BRIGHT_LSB_REG(b, n) (QPNP_WLED_MOD_EN_REG(b, n) + 0x07)
+#define QPNP_WLED_BRIGHT_MSB_REG(b, n) (QPNP_WLED_MOD_EN_REG(b, n) + 0x08)
+#define QPNP_WLED_SINK_TEST5_REG(b) (b + 0xE6)
+
+#define QPNP_WLED_MOD_FREQ_1200_KHZ 1200
+#define QPNP_WLED_MOD_FREQ_2400_KHZ 2400
+#define QPNP_WLED_MOD_FREQ_9600_KHZ 9600
+#define QPNP_WLED_MOD_FREQ_19200_KHZ 19200
+#define QPNP_WLED_MOD_FREQ_MASK 0x3F
+#define QPNP_WLED_MOD_FREQ_SHIFT 6
+#define QPNP_WLED_ACC_CLK_FREQ_MASK 0xE7
+#define QPNP_WLED_ACC_CLK_FREQ_SHIFT 3
+#define QPNP_WLED_PHASE_STAG_MASK 0xDF
+#define QPNP_WLED_PHASE_STAG_SHIFT 5
+#define QPNP_WLED_DIM_RES_MASK 0xFD
+#define QPNP_WLED_DIM_RES_SHIFT 1
+#define QPNP_WLED_DIM_HYB_MASK 0xFB
+#define QPNP_WLED_DIM_HYB_SHIFT 2
+#define QPNP_WLED_DIM_ANA_MASK 0xFE
+#define QPNP_WLED_HYB_THRES_MASK 0xF8
+#define QPNP_WLED_HYB_THRES_MIN 78
+#define QPNP_WLED_DEF_HYB_THRES 625
+#define QPNP_WLED_HYB_THRES_MAX 10000
+#define QPNP_WLED_MOD_EN_MASK 0x7F
+#define QPNP_WLED_MOD_EN_SHFT 7
+#define QPNP_WLED_MOD_EN 1
+#define QPNP_WLED_GATE_DRV_MASK 0xFE
+#define QPNP_WLED_SYNC_DLY_MASK 0xF8
+#define QPNP_WLED_SYNC_DLY_MIN_US 0
+#define QPNP_WLED_SYNC_DLY_MAX_US 1400
+#define QPNP_WLED_SYNC_DLY_STEP_US 200
+#define QPNP_WLED_DEF_SYNC_DLY_US 400
+#define QPNP_WLED_FS_CURR_MASK 0xF0
+#define QPNP_WLED_FS_CURR_MIN_UA 0
+#define QPNP_WLED_FS_CURR_MAX_UA 30000
+#define QPNP_WLED_FS_CURR_STEP_UA 2500
+#define QPNP_WLED_CABC_MASK 0x7F
+#define QPNP_WLED_CABC_SHIFT 7
+#define QPNP_WLED_CURR_SINK_SHIFT 4
+#define QPNP_WLED_BRIGHT_LSB_MASK 0xFF
+#define QPNP_WLED_BRIGHT_MSB_SHIFT 8
+#define QPNP_WLED_BRIGHT_MSB_MASK 0x0F
+#define QPNP_WLED_SYNC 0x0F
+#define QPNP_WLED_SYNC_RESET 0x00
+
+#define QPNP_WLED_SINK_TEST5_HYB 0x14
+#define QPNP_WLED_SINK_TEST5_DIG 0x1E
+#define QPNP_WLED_SINK_TEST5_HVG_PULL_STR_BIT BIT(3)
+
+#define QPNP_WLED_SWITCH_FREQ_800_KHZ_CODE 0x0B
+#define QPNP_WLED_SWITCH_FREQ_1600_KHZ_CODE 0x05
+
+#define QPNP_WLED_DISP_SEL_REG(b) (b + 0x44)
+#define QPNP_WLED_MODULE_RDY_REG(b) (b + 0x45)
+#define QPNP_WLED_MODULE_EN_REG(b) (b + 0x46)
+#define QPNP_WLED_MODULE_RDY_MASK 0x7F
+#define QPNP_WLED_MODULE_RDY_SHIFT 7
+#define QPNP_WLED_MODULE_EN_MASK BIT(7)
+#define QPNP_WLED_MODULE_EN_SHIFT 7
+#define QPNP_WLED_DISP_SEL_MASK 0x7F
+#define QPNP_WLED_DISP_SEL_SHIFT 7
+#define QPNP_WLED_EN_SC_DEB_CYCLES_MASK 0x79
+#define QPNP_WLED_EN_DEB_CYCLES_MASK 0xF9
+#define QPNP_WLED_EN_SC_SHIFT 7
+#define QPNP_WLED_SC_PRO_EN_DSCHGR 0x8
+#define QPNP_WLED_SC_DEB_CYCLES_MIN 2
+#define QPNP_WLED_SC_DEB_CYCLES_MAX 16
+#define QPNP_WLED_SC_DEB_CYCLES_SUB 2
+#define QPNP_WLED_SC_DEB_CYCLES_DFLT 4
+#define QPNP_WLED_EXT_FET_DTEST2 0x09
+
+#define QPNP_WLED_SEC_ACCESS_REG(b) (b + 0xD0)
+#define QPNP_WLED_SEC_UNLOCK 0xA5
+
+#define QPNP_WLED_MAX_STRINGS 4
+#define WLED_MAX_LEVEL_4095 4095
+#define QPNP_WLED_RAMP_DLY_MS 20
+#define QPNP_WLED_TRIGGER_NONE "none"
+#define QPNP_WLED_STR_SIZE 20
+#define QPNP_WLED_MIN_MSLEEP 20
+#define QPNP_WLED_SC_DLY_MS 20
+
+#define NUM_SUPPORTED_AVDD_VOLTAGES 6
+#define QPNP_WLED_DFLT_AVDD_MV 7600
+#define QPNP_WLED_AVDD_MIN_MV 5650
+#define QPNP_WLED_AVDD_MAX_MV 7900
+#define QPNP_WLED_AVDD_STEP_MV 150
+#define QPNP_WLED_AVDD_MIN_TRIM_VAL 0x0
+#define QPNP_WLED_AVDD_MAX_TRIM_VAL 0xF
+#define QPNP_WLED_AVDD_SEL_SPMI_BIT BIT(7)
+#define QPNP_WLED_AVDD_SET_BIT BIT(4)
+
+#define NUM_SUPPORTED_OVP_THRESHOLDS 4
+#define NUM_SUPPORTED_ILIM_THRESHOLDS 8
+
+#define QPNP_WLED_AVDD_MV_TO_REG(val) \
+ ((val - QPNP_WLED_AVDD_MIN_MV) / QPNP_WLED_AVDD_STEP_MV)
+
+/* output feedback mode */
+enum qpnp_wled_fdbk_op {
+ QPNP_WLED_FDBK_AUTO,
+ QPNP_WLED_FDBK_WLED1,
+ QPNP_WLED_FDBK_WLED2,
+ QPNP_WLED_FDBK_WLED3,
+ QPNP_WLED_FDBK_WLED4,
+};
+
+/* dimming modes */
+enum qpnp_wled_dim_mode {
+ QPNP_WLED_DIM_ANALOG,
+ QPNP_WLED_DIM_DIGITAL,
+ QPNP_WLED_DIM_HYBRID,
+};
+
+/* wled ctrl debug registers */
+static u8 qpnp_wled_ctrl_dbg_regs[] = {
+ 0x44, 0x46, 0x48, 0x49, 0x4b, 0x4c, 0x4d, 0x4e, 0x50, 0x51, 0x52, 0x53,
+ 0x54, 0x55, 0x56, 0x57, 0x58, 0x5a, 0x5b, 0x5d, 0x5e, 0xe2
+};
+
+/* wled sink debug registers */
+static u8 qpnp_wled_sink_dbg_regs[] = {
+ 0x46, 0x47, 0x48, 0x4a, 0x4b,
+ 0x50, 0x51, 0x52, 0x53, 0x56, 0x57, 0x58,
+ 0x60, 0x61, 0x62, 0x63, 0x66, 0x67, 0x68,
+ 0x70, 0x71, 0x72, 0x73, 0x76, 0x77, 0x78,
+ 0x80, 0x81, 0x82, 0x83, 0x86, 0x87, 0x88,
+ 0xe6,
+};
+
+static int qpnp_wled_avdd_target_voltages[NUM_SUPPORTED_AVDD_VOLTAGES] = {
+ 7900, 7600, 7300, 6400, 6100, 5800,
+};
+
+static u8 qpnp_wled_ovp_reg_settings[NUM_SUPPORTED_AVDD_VOLTAGES] = {
+ 0x0, 0x0, 0x1, 0x2, 0x2, 0x3,
+};
+
+static int qpnp_wled_avdd_trim_adjustments[NUM_SUPPORTED_AVDD_VOLTAGES] = {
+ 3, 0, -2, 7, 3, 3,
+};
+
+static int qpnp_wled_ovp_thresholds_pmi8994[NUM_SUPPORTED_OVP_THRESHOLDS] = {
+ 31000, 29500, 19400, 17800,
+};
+
+static int qpnp_wled_ovp_thresholds_pmi8998[NUM_SUPPORTED_OVP_THRESHOLDS] = {
+ 31100, 29600, 19600, 18100,
+};
+
+static int qpnp_wled_ilim_settings_pmi8994[NUM_SUPPORTED_ILIM_THRESHOLDS] = {
+ 105, 385, 660, 980, 1150, 1420, 1700, 1980,
+};
+
+static int qpnp_wled_ilim_settings_pmi8998[NUM_SUPPORTED_ILIM_THRESHOLDS] = {
+ 105, 280, 450, 620, 970, 1150, 1300, 1500,
+};
+
+struct wled_vref_setting {
+ u32 min_uv;
+ u32 max_uv;
+ u32 step_uv;
+ u32 default_uv;
+};
+
+static struct wled_vref_setting vref_setting_pmi8994 = {
+ 300000, 675000, 25000, 350000,
+};
+static struct wled_vref_setting vref_setting_pmi8998 = {
+ 60000, 397500, 22500, 127500,
+};
+
+/**
+ * qpnp_wled - wed data structure
+ * @ cdev - led class device
+ * @ pdev - platform device
+ * @ work - worker for led operation
+ * @ lock - mutex lock for exclusive access
+ * @ fdbk_op - output feedback mode
+ * @ dim_mode - dimming mode
+ * @ ovp_irq - over voltage protection irq
+ * @ sc_irq - short circuit irq
+ * @ sc_cnt - short circuit irq count
+ * @ avdd_target_voltage_mv - target voltage for AVDD module in mV
+ * @ ctrl_base - base address for wled ctrl
+ * @ sink_base - base address for wled sink
+ * @ mod_freq_khz - modulator frequency in KHZ
+ * @ hyb_thres - threshold for hybrid dimming
+ * @ sync_dly_us - sync delay in us
+ * @ vref_uv - ref voltage in uv
+ * @ vref_psm_mv - ref psm voltage in mv
+ * @ loop_comp_res_kohm - control to select the compensation resistor
+ * @ loop_ea_gm - control to select the gm for the gm stage in control loop
+ * @ sc_deb_cycles - debounce time for short circuit detection
+ * @ switch_freq_khz - switching frequency in KHZ
+ * @ ovp_mv - over voltage protection in mv
+ * @ ilim_ma - current limiter in ma
+ * @ boost_duty_ns - boost duty cycle in ns
+ * @ fs_curr_ua - full scale current in ua
+ * @ ramp_ms - delay between ramp steps in ms
+ * @ ramp_step - ramp step size
+ * @ cons_sync_write_delay_us - delay between two consecutive writes to SYNC
+ * @ strings - supported list of strings
+ * @ num_strings - number of strings
+ * @ loop_auto_gm_thresh - the clamping level for auto gm
+ * @ lcd_auto_pfm_thresh - the threshold for lcd auto pfm mode
+ * @ loop_auto_gm_en - select if auto gm is enabled
+ * @ lcd_auto_pfm_en - select if auto pfm is enabled in lcd mode
+ * @ avdd_mode_spmi - enable avdd programming via spmi
+ * @ en_9b_dim_res - enable or disable 9bit dimming
+ * @ en_phase_stag - enable or disable phase staggering
+ * @ en_cabc - enable or disable cabc
+ * @ disp_type_amoled - type of display: LCD/AMOLED
+ * @ en_ext_pfet_sc_pro - enable sc protection on external pfet
+ */
+struct qpnp_wled {
+ struct led_classdev cdev;
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ struct pmic_revid_data *pmic_rev_id;
+ struct work_struct work;
+ struct mutex lock;
+ struct mutex bus_lock;
+ enum qpnp_wled_fdbk_op fdbk_op;
+ enum qpnp_wled_dim_mode dim_mode;
+ int ovp_irq;
+ int sc_irq;
+ u32 sc_cnt;
+ u32 avdd_target_voltage_mv;
+ u16 ctrl_base;
+ u16 sink_base;
+ u16 mod_freq_khz;
+ u16 hyb_thres;
+ u16 sync_dly_us;
+ u32 vref_uv;
+ u16 vref_psm_mv;
+ u16 loop_comp_res_kohm;
+ u16 loop_ea_gm;
+ u16 sc_deb_cycles;
+ u16 switch_freq_khz;
+ u16 ovp_mv;
+ u16 ilim_ma;
+ u16 boost_duty_ns;
+ u16 fs_curr_ua;
+ u16 ramp_ms;
+ u16 ramp_step;
+ u16 cons_sync_write_delay_us;
+ u8 strings[QPNP_WLED_MAX_STRINGS];
+ u8 num_strings;
+ u8 loop_auto_gm_thresh;
+ u8 lcd_auto_pfm_thresh;
+ bool loop_auto_gm_en;
+ bool lcd_auto_pfm_en;
+ bool avdd_mode_spmi;
+ bool en_9b_dim_res;
+ bool en_phase_stag;
+ bool en_cabc;
+ bool disp_type_amoled;
+ bool en_ext_pfet_sc_pro;
+ bool prev_state;
+ bool ovp_irq_disabled;
+};
+
+/* helper to read a pmic register */
+static int qpnp_wled_read_reg(struct qpnp_wled *wled, u16 addr, u8 *data)
+{
+ int rc;
+ uint val;
+
+ rc = regmap_read(wled->regmap, addr, &val);
+ if (rc < 0) {
+ dev_err(&wled->pdev->dev,
+ "Error reading address: %x(%d)\n", addr, rc);
+ return rc;
+ }
+
+ *data = (u8)val;
+ return 0;
+}
+
+/* helper to write a pmic register */
+static int qpnp_wled_write_reg(struct qpnp_wled *wled, u16 addr, u8 data)
+{
+ int rc;
+
+ mutex_lock(&wled->bus_lock);
+ rc = regmap_write(wled->regmap, addr, data);
+ if (rc < 0) {
+ dev_err(&wled->pdev->dev, "Error writing address: %x(%d)\n",
+ addr, rc);
+ goto out;
+ }
+
+ dev_dbg(&wled->pdev->dev, "wrote: WLED_0x%x = 0x%x\n", addr, data);
+out:
+ mutex_unlock(&wled->bus_lock);
+ return rc;
+}
+
+static int qpnp_wled_masked_write_reg(struct qpnp_wled *wled, u16 addr,
+ u8 mask, u8 data)
+{
+ int rc;
+
+ mutex_lock(&wled->bus_lock);
+ rc = regmap_update_bits(wled->regmap, addr, mask, data);
+ if (rc < 0) {
+ dev_err(&wled->pdev->dev, "Error writing address: %x(%d)\n",
+ addr, rc);
+ goto out;
+ }
+
+ dev_dbg(&wled->pdev->dev, "wrote: WLED_0x%x = 0x%x\n", addr, data);
+out:
+ mutex_unlock(&wled->bus_lock);
+ return rc;
+}
+
+static int qpnp_wled_sec_write_reg(struct qpnp_wled *wled, u16 addr, u8 data)
+{
+ int rc;
+ u8 reg = QPNP_WLED_SEC_UNLOCK;
+ u16 base_addr = addr & 0xFF00;
+
+ mutex_lock(&wled->bus_lock);
+ rc = regmap_write(wled->regmap, QPNP_WLED_SEC_ACCESS_REG(base_addr),
+ reg);
+ if (rc < 0) {
+ dev_err(&wled->pdev->dev, "Error writing address: %x(%d)\n",
+ QPNP_WLED_SEC_ACCESS_REG(base_addr), rc);
+ goto out;
+ }
+
+ rc = regmap_write(wled->regmap, addr, data);
+ if (rc < 0) {
+ dev_err(&wled->pdev->dev, "Error writing address: %x(%d)\n",
+ addr, rc);
+ goto out;
+ }
+
+ dev_dbg(&wled->pdev->dev, "wrote: WLED_0x%x = 0x%x\n", addr, data);
+out:
+ mutex_unlock(&wled->bus_lock);
+ return rc;
+}
+
+static int qpnp_wled_swire_avdd_config(struct qpnp_wled *wled)
+{
+ int rc;
+ u8 val;
+
+ if (wled->pmic_rev_id->pmic_subtype != PMI8998_SUBTYPE &&
+ wled->pmic_rev_id->pmic_subtype != PM660L_SUBTYPE)
+ return 0;
+
+ if (!wled->disp_type_amoled || wled->avdd_mode_spmi)
+ return 0;
+
+ val = QPNP_WLED_AVDD_MV_TO_REG(wled->avdd_target_voltage_mv);
+ rc = qpnp_wled_write_reg(wled,
+ QPNP_WLED_SWIRE_AVDD_REG(wled->ctrl_base), val);
+ return rc;
+}
+
+static int qpnp_wled_sync_reg_toggle(struct qpnp_wled *wled)
+{
+ int rc;
+ u8 reg;
+
+ /* sync */
+ reg = QPNP_WLED_SYNC;
+ rc = qpnp_wled_write_reg(wled, QPNP_WLED_SYNC_REG(wled->sink_base),
+ reg);
+ if (rc < 0)
+ return rc;
+
+ if (wled->cons_sync_write_delay_us)
+ usleep_range(wled->cons_sync_write_delay_us,
+ wled->cons_sync_write_delay_us + 1);
+
+ reg = QPNP_WLED_SYNC_RESET;
+ rc = qpnp_wled_write_reg(wled, QPNP_WLED_SYNC_REG(wled->sink_base),
+ reg);
+ if (rc < 0)
+ return rc;
+
+ return 0;
+}
+
+/* set wled to a level of brightness */
+static int qpnp_wled_set_level(struct qpnp_wled *wled, int level)
+{
+ int i, rc;
+ u8 reg;
+
+ /* set brightness registers */
+ for (i = 0; i < wled->num_strings; i++) {
+ reg = level & QPNP_WLED_BRIGHT_LSB_MASK;
+ rc = qpnp_wled_write_reg(wled,
+ QPNP_WLED_BRIGHT_LSB_REG(wled->sink_base,
+ wled->strings[i]), reg);
+ if (rc < 0)
+ return rc;
+
+ reg = level >> QPNP_WLED_BRIGHT_MSB_SHIFT;
+ reg = reg & QPNP_WLED_BRIGHT_MSB_MASK;
+ rc = qpnp_wled_write_reg(wled,
+ QPNP_WLED_BRIGHT_MSB_REG(wled->sink_base,
+ wled->strings[i]), reg);
+ if (rc < 0)
+ return rc;
+ }
+
+ rc = qpnp_wled_sync_reg_toggle(wled);
+ if (rc < 0) {
+ dev_err(&wled->pdev->dev, "Failed to toggle sync reg %d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int qpnp_wled_module_en(struct qpnp_wled *wled,
+ u16 base_addr, bool state)
+{
+ int rc;
+
+ rc = qpnp_wled_masked_write_reg(wled,
+ QPNP_WLED_MODULE_EN_REG(base_addr),
+ QPNP_WLED_MODULE_EN_MASK,
+ state << QPNP_WLED_MODULE_EN_SHIFT);
+ if (rc < 0)
+ return rc;
+
+ if (wled->ovp_irq > 0) {
+ if (state && wled->ovp_irq_disabled) {
+ /*
+ * Wait for at least 10ms before enabling OVP fault
+ * interrupt after enabling the module so that soft
+ * start is completed. Keep OVP interrupt disabled
+ * when the module is disabled.
+ */
+ usleep_range(10000, 11000);
+ enable_irq(wled->ovp_irq);
+ wled->ovp_irq_disabled = false;
+ } else if (!state && !wled->ovp_irq_disabled) {
+ disable_irq(wled->ovp_irq);
+ wled->ovp_irq_disabled = true;
+ }
+ }
+
+ return 0;
+}
+
+/* sysfs store function for ramp */
+static ssize_t qpnp_wled_ramp_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct qpnp_wled *wled = dev_get_drvdata(dev);
+ int i, rc;
+
+ mutex_lock(&wled->lock);
+
+ if (!wled->cdev.brightness) {
+ rc = qpnp_wled_module_en(wled, wled->ctrl_base, true);
+ if (rc) {
+ dev_err(&wled->pdev->dev, "wled enable failed\n");
+ goto unlock_mutex;
+ }
+ }
+
+ /* ramp up */
+ for (i = 0; i <= wled->cdev.max_brightness;) {
+ rc = qpnp_wled_set_level(wled, i);
+ if (rc) {
+ dev_err(&wled->pdev->dev, "wled set level failed\n");
+ goto restore_brightness;
+ }
+
+ if (wled->ramp_ms < QPNP_WLED_MIN_MSLEEP)
+ usleep_range(wled->ramp_ms * USEC_PER_MSEC,
+ wled->ramp_ms * USEC_PER_MSEC);
+ else
+ msleep(wled->ramp_ms);
+
+ if (i == wled->cdev.max_brightness)
+ break;
+
+ i += wled->ramp_step;
+ if (i > wled->cdev.max_brightness)
+ i = wled->cdev.max_brightness;
+ }
+
+ /* ramp down */
+ for (i = wled->cdev.max_brightness; i >= 0;) {
+ rc = qpnp_wled_set_level(wled, i);
+ if (rc) {
+ dev_err(&wled->pdev->dev, "wled set level failed\n");
+ goto restore_brightness;
+ }
+
+ if (wled->ramp_ms < QPNP_WLED_MIN_MSLEEP)
+ usleep_range(wled->ramp_ms * USEC_PER_MSEC,
+ wled->ramp_ms * USEC_PER_MSEC);
+ else
+ msleep(wled->ramp_ms);
+
+ if (i == 0)
+ break;
+
+ i -= wled->ramp_step;
+ if (i < 0)
+ i = 0;
+ }
+
+ dev_info(&wled->pdev->dev, "wled ramp complete\n");
+
+restore_brightness:
+ /* restore the old brightness */
+ qpnp_wled_set_level(wled, wled->cdev.brightness);
+ if (!wled->cdev.brightness) {
+ rc = qpnp_wled_module_en(wled, wled->ctrl_base, false);
+ if (rc)
+ dev_err(&wled->pdev->dev, "wled enable failed\n");
+ }
+unlock_mutex:
+ mutex_unlock(&wled->lock);
+
+ return count;
+}
+
+static int qpnp_wled_dump_regs(struct qpnp_wled *wled, u16 base_addr,
+ u8 dbg_regs[], u8 size, char *label,
+ int count, char *buf)
+{
+ int i, rc;
+ u8 reg;
+
+ for (i = 0; i < size; i++) {
+ rc = qpnp_wled_read_reg(wled, base_addr + dbg_regs[i], ®);
+ if (rc < 0)
+ return rc;
+
+ count += snprintf(buf + count, PAGE_SIZE - count,
+ "%s: REG_0x%x = 0x%x\n", label,
+ base_addr + dbg_regs[i], reg);
+
+ if (count >= PAGE_SIZE)
+ return PAGE_SIZE - 1;
+ }
+
+ return count;
+}
+
+/* sysfs show function for debug registers */
+static ssize_t qpnp_wled_dump_regs_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct qpnp_wled *wled = dev_get_drvdata(dev);
+ int count = 0;
+
+ count = qpnp_wled_dump_regs(wled, wled->ctrl_base,
+ qpnp_wled_ctrl_dbg_regs,
+ ARRAY_SIZE(qpnp_wled_ctrl_dbg_regs),
+ "wled_ctrl", count, buf);
+
+ if (count < 0 || count == PAGE_SIZE - 1)
+ return count;
+
+ count = qpnp_wled_dump_regs(wled, wled->sink_base,
+ qpnp_wled_sink_dbg_regs,
+ ARRAY_SIZE(qpnp_wled_sink_dbg_regs),
+ "wled_sink", count, buf);
+
+ if (count < 0 || count == PAGE_SIZE - 1)
+ return count;
+
+ return count;
+}
+
+/* sysfs show function for ramp delay in each step */
+static ssize_t qpnp_wled_ramp_ms_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct qpnp_wled *wled = dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", wled->ramp_ms);
+}
+
+/* sysfs store function for ramp delay in each step */
+static ssize_t qpnp_wled_ramp_ms_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct qpnp_wled *wled = dev_get_drvdata(dev);
+ int data, rc;
+
+ rc = kstrtoint(buf, 10, &data);
+ if (rc)
+ return rc;
+
+ wled->ramp_ms = data;
+ return count;
+}
+
+/* sysfs show function for ramp step */
+static ssize_t qpnp_wled_ramp_step_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct qpnp_wled *wled = dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", wled->ramp_step);
+}
+
+/* sysfs store function for ramp step */
+static ssize_t qpnp_wled_ramp_step_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct qpnp_wled *wled = dev_get_drvdata(dev);
+ int data, rc;
+
+ rc = kstrtoint(buf, 10, &data);
+ if (rc)
+ return rc;
+
+ wled->ramp_step = data;
+ return count;
+}
+
+/* sysfs show function for dim mode */
+static ssize_t qpnp_wled_dim_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct qpnp_wled *wled = dev_get_drvdata(dev);
+ char *str;
+
+ if (wled->dim_mode == QPNP_WLED_DIM_ANALOG)
+ str = "analog";
+ else if (wled->dim_mode == QPNP_WLED_DIM_DIGITAL)
+ str = "digital";
+ else
+ str = "hybrid";
+
+ return snprintf(buf, PAGE_SIZE, "%s\n", str);
+}
+
+/* sysfs store function for dim mode*/
+static ssize_t qpnp_wled_dim_mode_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct qpnp_wled *wled = dev_get_drvdata(dev);
+ char str[QPNP_WLED_STR_SIZE + 1];
+ int rc, temp;
+ u8 reg;
+
+ if (snprintf(str, QPNP_WLED_STR_SIZE, "%s", buf) > QPNP_WLED_STR_SIZE)
+ return -EINVAL;
+
+ if (strcmp(str, "analog") == 0)
+ temp = QPNP_WLED_DIM_ANALOG;
+ else if (strcmp(str, "digital") == 0)
+ temp = QPNP_WLED_DIM_DIGITAL;
+ else
+ temp = QPNP_WLED_DIM_HYBRID;
+
+ if (temp == wled->dim_mode)
+ return count;
+
+ rc = qpnp_wled_read_reg(wled, QPNP_WLED_MOD_REG(wled->sink_base), ®);
+ if (rc < 0)
+ return rc;
+
+ if (temp == QPNP_WLED_DIM_HYBRID) {
+ reg &= QPNP_WLED_DIM_HYB_MASK;
+ reg |= (1 << QPNP_WLED_DIM_HYB_SHIFT);
+ } else {
+ reg &= QPNP_WLED_DIM_HYB_MASK;
+ reg |= (0 << QPNP_WLED_DIM_HYB_SHIFT);
+ reg &= QPNP_WLED_DIM_ANA_MASK;
+ reg |= temp;
+ }
+
+ rc = qpnp_wled_write_reg(wled, QPNP_WLED_MOD_REG(wled->sink_base), reg);
+ if (rc)
+ return rc;
+
+ wled->dim_mode = temp;
+
+ return count;
+}
+
+/* sysfs show function for full scale current in ua*/
+static ssize_t qpnp_wled_fs_curr_ua_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct qpnp_wled *wled = dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", wled->fs_curr_ua);
+}
+
+/* sysfs store function for full scale current in ua*/
+static ssize_t qpnp_wled_fs_curr_ua_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct qpnp_wled *wled = dev_get_drvdata(dev);
+ int data, i, rc, temp;
+ u8 reg;
+
+ rc = kstrtoint(buf, 10, &data);
+ if (rc)
+ return rc;
+
+ for (i = 0; i < wled->num_strings; i++) {
+ if (data < QPNP_WLED_FS_CURR_MIN_UA)
+ data = QPNP_WLED_FS_CURR_MIN_UA;
+ else if (data > QPNP_WLED_FS_CURR_MAX_UA)
+ data = QPNP_WLED_FS_CURR_MAX_UA;
+
+ rc = qpnp_wled_read_reg(wled,
+ QPNP_WLED_FS_CURR_REG(wled->sink_base,
+ wled->strings[i]), ®);
+ if (rc < 0)
+ return rc;
+ reg &= QPNP_WLED_FS_CURR_MASK;
+ temp = data / QPNP_WLED_FS_CURR_STEP_UA;
+ reg |= temp;
+ rc = qpnp_wled_write_reg(wled,
+ QPNP_WLED_FS_CURR_REG(wled->sink_base,
+ wled->strings[i]), reg);
+ if (rc)
+ return rc;
+ }
+
+ wled->fs_curr_ua = data;
+
+ rc = qpnp_wled_sync_reg_toggle(wled);
+ if (rc < 0) {
+ dev_err(&wled->pdev->dev, "Failed to toggle sync reg %d\n", rc);
+ return rc;
+ }
+
+ return count;
+}
+
+/* sysfs attributes exported by wled */
+static struct device_attribute qpnp_wled_attrs[] = {
+ __ATTR(dump_regs, 0664, qpnp_wled_dump_regs_show, NULL),
+ __ATTR(dim_mode, 0664, qpnp_wled_dim_mode_show,
+ qpnp_wled_dim_mode_store),
+ __ATTR(fs_curr_ua, 0664, qpnp_wled_fs_curr_ua_show,
+ qpnp_wled_fs_curr_ua_store),
+ __ATTR(start_ramp, 0664, NULL, qpnp_wled_ramp_store),
+ __ATTR(ramp_ms, 0664, qpnp_wled_ramp_ms_show, qpnp_wled_ramp_ms_store),
+ __ATTR(ramp_step, 0664, qpnp_wled_ramp_step_show,
+ qpnp_wled_ramp_step_store),
+};
+
+/* worker for setting wled brightness */
+static void qpnp_wled_work(struct work_struct *work)
+{
+ struct qpnp_wled *wled;
+ int level, rc;
+
+ wled = container_of(work, struct qpnp_wled, work);
+
+ level = wled->cdev.brightness;
+
+ mutex_lock(&wled->lock);
+
+ if (level) {
+ rc = qpnp_wled_set_level(wled, level);
+ if (rc) {
+ dev_err(&wled->pdev->dev, "wled set level failed\n");
+ goto unlock_mutex;
+ }
+ }
+
+ if (!!level != wled->prev_state) {
+ if (!!level) {
+ /*
+ * For AMOLED display in pmi8998, SWIRE_AVDD_DEFAULT has
+ * to be reconfigured every time the module is enabled.
+ */
+ rc = qpnp_wled_swire_avdd_config(wled);
+ if (rc < 0) {
+ pr_err("Write to SWIRE_AVDD_DEFAULT register failed rc:%d\n",
+ rc);
+ goto unlock_mutex;
+ }
+ }
+
+ rc = qpnp_wled_module_en(wled, wled->ctrl_base, !!level);
+ if (rc) {
+ dev_err(&wled->pdev->dev, "wled %sable failed\n",
+ level ? "en" : "dis");
+ goto unlock_mutex;
+ }
+ }
+
+ wled->prev_state = !!level;
+unlock_mutex:
+ mutex_unlock(&wled->lock);
+}
+
+/* get api registered with led classdev for wled brightness */
+static enum led_brightness qpnp_wled_get(struct led_classdev *led_cdev)
+{
+ struct qpnp_wled *wled;
+
+ wled = container_of(led_cdev, struct qpnp_wled, cdev);
+
+ return wled->cdev.brightness;
+}
+
+/* set api registered with led classdev for wled brightness */
+static void qpnp_wled_set(struct led_classdev *led_cdev,
+ enum led_brightness level)
+{
+ struct qpnp_wled *wled;
+
+ wled = container_of(led_cdev, struct qpnp_wled, cdev);
+
+ if (level < LED_OFF)
+ level = LED_OFF;
+ else if (level > wled->cdev.max_brightness)
+ level = wled->cdev.max_brightness;
+
+ wled->cdev.brightness = level;
+ schedule_work(&wled->work);
+}
+
+static int qpnp_wled_set_disp(struct qpnp_wled *wled, u16 base_addr)
+{
+ int rc;
+ u8 reg;
+
+ /* display type */
+ rc = qpnp_wled_read_reg(wled, QPNP_WLED_DISP_SEL_REG(base_addr), ®);
+ if (rc < 0)
+ return rc;
+
+ reg &= QPNP_WLED_DISP_SEL_MASK;
+ reg |= (wled->disp_type_amoled << QPNP_WLED_DISP_SEL_SHIFT);
+
+ rc = qpnp_wled_sec_write_reg(wled, QPNP_WLED_DISP_SEL_REG(base_addr),
+ reg);
+ if (rc)
+ return rc;
+
+ if (wled->disp_type_amoled) {
+ /* Configure the PSM CTRL register for AMOLED */
+ if (wled->vref_psm_mv < QPNP_WLED_VREF_PSM_MIN_MV)
+ wled->vref_psm_mv = QPNP_WLED_VREF_PSM_MIN_MV;
+ else if (wled->vref_psm_mv > QPNP_WLED_VREF_PSM_MAX_MV)
+ wled->vref_psm_mv = QPNP_WLED_VREF_PSM_MAX_MV;
+
+ rc = qpnp_wled_read_reg(wled,
+ QPNP_WLED_PSM_CTRL_REG(wled->ctrl_base), ®);
+ if (rc < 0)
+ return rc;
+
+ reg &= QPNP_WLED_VREF_PSM_MASK;
+ reg |= ((wled->vref_psm_mv - QPNP_WLED_VREF_PSM_MIN_MV)/
+ QPNP_WLED_VREF_PSM_STEP_MV);
+ reg |= QPNP_WLED_PSM_CTRL_OVERWRITE;
+ rc = qpnp_wled_write_reg(wled,
+ QPNP_WLED_PSM_CTRL_REG(wled->ctrl_base), reg);
+ if (rc)
+ return rc;
+
+ /* Configure the VLOOP COMP RES register for AMOLED */
+ if (wled->loop_comp_res_kohm < QPNP_WLED_LOOP_COMP_RES_MIN_KOHM)
+ wled->loop_comp_res_kohm =
+ QPNP_WLED_LOOP_COMP_RES_MIN_KOHM;
+ else if (wled->loop_comp_res_kohm >
+ QPNP_WLED_LOOP_COMP_RES_MAX_KOHM)
+ wled->loop_comp_res_kohm =
+ QPNP_WLED_LOOP_COMP_RES_MAX_KOHM;
+
+ rc = qpnp_wled_read_reg(wled,
+ QPNP_WLED_VLOOP_COMP_RES_REG(wled->ctrl_base),
+ ®);
+ if (rc < 0)
+ return rc;
+
+ reg &= QPNP_WLED_VLOOP_COMP_RES_MASK;
+ reg |= ((wled->loop_comp_res_kohm -
+ QPNP_WLED_LOOP_COMP_RES_MIN_KOHM)/
+ QPNP_WLED_LOOP_COMP_RES_STEP_KOHM);
+ reg |= QPNP_WLED_VLOOP_COMP_RES_OVERWRITE;
+ rc = qpnp_wled_write_reg(wled,
+ QPNP_WLED_VLOOP_COMP_RES_REG(wled->ctrl_base),
+ reg);
+ if (rc)
+ return rc;
+
+ /* Configure the CTRL TEST4 register for AMOLED */
+ rc = qpnp_wled_read_reg(wled,
+ QPNP_WLED_TEST4_REG(wled->ctrl_base), ®);
+ if (rc < 0)
+ return rc;
+
+ reg |= QPNP_WLED_TEST4_EN_IIND_UP;
+ rc = qpnp_wled_sec_write_reg(wled,
+ QPNP_WLED_TEST4_REG(base_addr), reg);
+ if (rc)
+ return rc;
+ } else {
+ /*
+ * enable VREF_UP to avoid false ovp on low brightness for LCD
+ */
+ reg = QPNP_WLED_TEST4_EN_VREF_UP
+ | QPNP_WLED_TEST4_EN_DEB_BYPASS_ILIM_BIT;
+ rc = qpnp_wled_sec_write_reg(wled,
+ QPNP_WLED_TEST4_REG(base_addr), reg);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+
+/* ovp irq handler */
+static irqreturn_t qpnp_wled_ovp_irq_handler(int irq, void *_wled)
+{
+ struct qpnp_wled *wled = _wled;
+ int rc;
+ u8 val;
+
+ rc = qpnp_wled_read_reg(wled,
+ QPNP_WLED_FAULT_STATUS(wled->ctrl_base), &val);
+ if (rc < 0) {
+ pr_err("Error in reading WLED_FAULT_STATUS rc=%d\n", rc);
+ return IRQ_HANDLED;
+ }
+
+ pr_err("WLED OVP fault detected, fault_status= %x\n", val);
+ return IRQ_HANDLED;
+}
+
+/* short circuit irq handler */
+static irqreturn_t qpnp_wled_sc_irq_handler(int irq, void *_wled)
+{
+ struct qpnp_wled *wled = _wled;
+ int rc;
+ u8 val;
+
+ rc = qpnp_wled_read_reg(wled,
+ QPNP_WLED_FAULT_STATUS(wled->ctrl_base), &val);
+ if (rc < 0) {
+ pr_err("Error in reading WLED_FAULT_STATUS rc=%d\n", rc);
+ return IRQ_HANDLED;
+ }
+
+ pr_err("WLED short circuit detected %d times fault_status=%x\n",
+ ++wled->sc_cnt, val);
+ mutex_lock(&wled->lock);
+ qpnp_wled_module_en(wled, wled->ctrl_base, false);
+ msleep(QPNP_WLED_SC_DLY_MS);
+ qpnp_wled_module_en(wled, wled->ctrl_base, true);
+ mutex_unlock(&wled->lock);
+
+ return IRQ_HANDLED;
+}
+
+static bool is_avdd_trim_adjustment_required(struct qpnp_wled *wled)
+{
+ int rc;
+ u8 reg = 0;
+
+ /*
+ * AVDD trim adjustment is not required for pmi8998/pm660l and not
+ * supported for pmi8994.
+ */
+ if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE ||
+ wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE ||
+ wled->pmic_rev_id->pmic_subtype == PMI8994_SUBTYPE)
+ return false;
+
+ /*
+ * Configure TRIM_REG only if disp_type_amoled and it has
+ * not already been programmed by bootloader.
+ */
+ if (!wled->disp_type_amoled)
+ return false;
+
+ rc = qpnp_wled_read_reg(wled,
+ QPNP_WLED_CTRL_SPARE_REG(wled->ctrl_base), ®);
+ if (rc < 0)
+ return false;
+
+ return !(reg & QPNP_WLED_AVDD_SET_BIT);
+}
+
+static int qpnp_wled_gm_config(struct qpnp_wled *wled)
+{
+ int rc;
+ u8 mask = 0, reg = 0;
+
+ /* Configure the LOOP COMP GM register */
+ if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE ||
+ wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) {
+ if (wled->loop_auto_gm_en)
+ reg |= QPNP_WLED_VLOOP_COMP_AUTO_GM_EN;
+
+ if (wled->loop_auto_gm_thresh >
+ QPNP_WLED_LOOP_AUTO_GM_THRESH_MAX)
+ wled->loop_auto_gm_thresh =
+ QPNP_WLED_LOOP_AUTO_GM_THRESH_MAX;
+
+ reg |= wled->loop_auto_gm_thresh <<
+ QPNP_WLED_VLOOP_COMP_AUTO_GM_THRESH_SHIFT;
+ mask |= QPNP_WLED_VLOOP_COMP_AUTO_GM_EN |
+ QPNP_WLED_VLOOP_COMP_AUTO_GM_THRESH_MASK;
+ }
+
+ if (wled->loop_ea_gm < QPNP_WLED_LOOP_EA_GM_MIN)
+ wled->loop_ea_gm = QPNP_WLED_LOOP_EA_GM_MIN;
+ else if (wled->loop_ea_gm > QPNP_WLED_LOOP_EA_GM_MAX)
+ wled->loop_ea_gm = QPNP_WLED_LOOP_EA_GM_MAX;
+
+ reg |= wled->loop_ea_gm | QPNP_WLED_VLOOP_COMP_GM_OVERWRITE;
+ mask |= QPNP_WLED_VLOOP_COMP_GM_MASK |
+ QPNP_WLED_VLOOP_COMP_GM_OVERWRITE;
+
+ rc = qpnp_wled_masked_write_reg(wled,
+ QPNP_WLED_VLOOP_COMP_GM_REG(wled->ctrl_base), mask,
+ reg);
+ if (rc)
+ pr_err("write VLOOP_COMP_GM_REG failed, rc=%d]\n", rc);
+
+ return rc;
+}
+
+static int qpnp_wled_ovp_config(struct qpnp_wled *wled)
+{
+ int rc, i, *ovp_table;
+ u8 reg;
+
+ /*
+ * Configure the OVP register based on ovp_mv only if display type is
+ * not AMOLED.
+ */
+ if (wled->disp_type_amoled)
+ return 0;
+
+ if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE ||
+ wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE)
+ ovp_table = qpnp_wled_ovp_thresholds_pmi8998;
+ else
+ ovp_table = qpnp_wled_ovp_thresholds_pmi8994;
+
+ for (i = 0; i < NUM_SUPPORTED_OVP_THRESHOLDS; i++) {
+ if (wled->ovp_mv == ovp_table[i])
+ break;
+ }
+
+ if (i == NUM_SUPPORTED_OVP_THRESHOLDS) {
+ dev_err(&wled->pdev->dev,
+ "Invalid ovp threshold specified in device tree\n");
+ return -EINVAL;
+ }
+
+ reg = i & QPNP_WLED_OVP_MASK;
+ rc = qpnp_wled_masked_write_reg(wled,
+ QPNP_WLED_OVP_REG(wled->ctrl_base),
+ QPNP_WLED_OVP_MASK, reg);
+ if (rc)
+ return rc;
+
+ return 0;
+}
+
+static int qpnp_wled_avdd_trim_config(struct qpnp_wled *wled)
+{
+ int rc, i;
+ u8 reg;
+
+ for (i = 0; i < NUM_SUPPORTED_AVDD_VOLTAGES; i++) {
+ if (wled->avdd_target_voltage_mv ==
+ qpnp_wled_avdd_target_voltages[i])
+ break;
+ }
+
+ if (i == NUM_SUPPORTED_AVDD_VOLTAGES) {
+ dev_err(&wled->pdev->dev,
+ "Invalid avdd target voltage specified in device tree\n");
+ return -EINVAL;
+ }
+
+ /* Update WLED_OVP register based on desired target voltage */
+ reg = qpnp_wled_ovp_reg_settings[i];
+ rc = qpnp_wled_masked_write_reg(wled,
+ QPNP_WLED_OVP_REG(wled->ctrl_base),
+ QPNP_WLED_OVP_MASK, reg);
+ if (rc)
+ return rc;
+
+ /* Update WLED_TRIM register based on desired target voltage */
+ rc = qpnp_wled_read_reg(wled,
+ QPNP_WLED_REF_7P7_TRIM_REG(wled->ctrl_base), ®);
+ if (rc)
+ return rc;
+
+ reg += qpnp_wled_avdd_trim_adjustments[i];
+ if ((s8)reg < QPNP_WLED_AVDD_MIN_TRIM_VAL ||
+ (s8)reg > QPNP_WLED_AVDD_MAX_TRIM_VAL) {
+ dev_dbg(&wled->pdev->dev,
+ "adjusted trim %d is not within range, capping it\n",
+ (s8)reg);
+ if ((s8)reg < QPNP_WLED_AVDD_MIN_TRIM_VAL)
+ reg = QPNP_WLED_AVDD_MIN_TRIM_VAL;
+ else
+ reg = QPNP_WLED_AVDD_MAX_TRIM_VAL;
+ }
+
+ reg &= QPNP_WLED_7P7_TRIM_MASK;
+ rc = qpnp_wled_sec_write_reg(wled,
+ QPNP_WLED_REF_7P7_TRIM_REG(wled->ctrl_base), reg);
+ if (rc < 0)
+ dev_err(&wled->pdev->dev, "Write to 7P7_TRIM register failed, rc=%d\n",
+ rc);
+ return rc;
+}
+
+static int qpnp_wled_avdd_mode_config(struct qpnp_wled *wled)
+{
+ int rc;
+ u8 reg = 0;
+
+ /*
+ * At present, configuring the mode to SPMI/SWIRE for controlling
+ * AVDD voltage is available only in pmi8998/pm660l.
+ */
+ if (wled->pmic_rev_id->pmic_subtype != PMI8998_SUBTYPE &&
+ wled->pmic_rev_id->pmic_subtype != PM660L_SUBTYPE)
+ return 0;
+
+ /* AMOLED_VOUT should be configured for AMOLED */
+ if (!wled->disp_type_amoled)
+ return 0;
+
+ /* Configure avdd register */
+ if (wled->avdd_target_voltage_mv > QPNP_WLED_AVDD_MAX_MV) {
+ dev_dbg(&wled->pdev->dev, "Capping avdd target voltage to %d\n",
+ QPNP_WLED_AVDD_MAX_MV);
+ wled->avdd_target_voltage_mv = QPNP_WLED_AVDD_MAX_MV;
+ } else if (wled->avdd_target_voltage_mv < QPNP_WLED_AVDD_MIN_MV) {
+ dev_info(&wled->pdev->dev, "Capping avdd target voltage to %d\n",
+ QPNP_WLED_AVDD_MIN_MV);
+ wled->avdd_target_voltage_mv = QPNP_WLED_AVDD_MIN_MV;
+ }
+
+ if (wled->avdd_mode_spmi) {
+ reg = QPNP_WLED_AVDD_MV_TO_REG(wled->avdd_target_voltage_mv);
+ reg |= QPNP_WLED_AVDD_SEL_SPMI_BIT;
+ rc = qpnp_wled_write_reg(wled,
+ QPNP_WLED_AMOLED_VOUT_REG(wled->ctrl_base),
+ reg);
+ if (rc < 0)
+ pr_err("Write to AMOLED_VOUT register failed, rc=%d\n",
+ rc);
+ } else {
+ rc = qpnp_wled_swire_avdd_config(wled);
+ if (rc < 0)
+ pr_err("Write to SWIRE_AVDD_DEFAULT register failed rc:%d\n",
+ rc);
+ }
+
+ return rc;
+}
+
+static int qpnp_wled_ilim_config(struct qpnp_wled *wled)
+{
+ int rc, i, *ilim_table;
+ u8 reg;
+
+ if (wled->ilim_ma < PMI8994_WLED_ILIM_MIN_MA)
+ wled->ilim_ma = PMI8994_WLED_ILIM_MIN_MA;
+
+ if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE ||
+ wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) {
+ ilim_table = qpnp_wled_ilim_settings_pmi8998;
+ if (wled->ilim_ma > PMI8998_WLED_ILIM_MAX_MA)
+ wled->ilim_ma = PMI8998_WLED_ILIM_MAX_MA;
+ } else {
+ ilim_table = qpnp_wled_ilim_settings_pmi8994;
+ if (wled->ilim_ma > PMI8994_WLED_ILIM_MAX_MA)
+ wled->ilim_ma = PMI8994_WLED_ILIM_MAX_MA;
+ }
+
+ for (i = 0; i < NUM_SUPPORTED_ILIM_THRESHOLDS; i++) {
+ if (wled->ilim_ma == ilim_table[i])
+ break;
+ }
+
+ if (i == NUM_SUPPORTED_ILIM_THRESHOLDS) {
+ dev_err(&wled->pdev->dev,
+ "Invalid ilim threshold specified in device tree\n");
+ return -EINVAL;
+ }
+
+ reg = (i & QPNP_WLED_ILIM_MASK) | QPNP_WLED_ILIM_OVERWRITE;
+ rc = qpnp_wled_masked_write_reg(wled,
+ QPNP_WLED_ILIM_REG(wled->ctrl_base),
+ QPNP_WLED_ILIM_MASK | QPNP_WLED_ILIM_OVERWRITE, reg);
+ if (rc < 0)
+ dev_err(&wled->pdev->dev, "Write to ILIM register failed, rc=%d\n",
+ rc);
+ return rc;
+}
+
+static int qpnp_wled_vref_config(struct qpnp_wled *wled)
+{
+
+ struct wled_vref_setting vref_setting;
+ int rc;
+ u8 reg = 0;
+
+ if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE ||
+ wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE)
+ vref_setting = vref_setting_pmi8998;
+ else
+ vref_setting = vref_setting_pmi8994;
+
+ if (wled->vref_uv < vref_setting.min_uv)
+ wled->vref_uv = vref_setting.min_uv;
+ else if (wled->vref_uv > vref_setting.max_uv)
+ wled->vref_uv = vref_setting.max_uv;
+
+ reg |= DIV_ROUND_CLOSEST(wled->vref_uv - vref_setting.min_uv,
+ vref_setting.step_uv);
+
+ rc = qpnp_wled_masked_write_reg(wled,
+ QPNP_WLED_VREF_REG(wled->ctrl_base),
+ QPNP_WLED_VREF_MASK, reg);
+ if (rc)
+ pr_err("Write VREF_REG failed, rc=%d\n", rc);
+
+ return rc;
+}
+
+/* Configure WLED registers */
+static int qpnp_wled_config(struct qpnp_wled *wled)
+{
+ int rc, i, temp;
+ u8 reg = 0;
+
+ /* Configure display type */
+ rc = qpnp_wled_set_disp(wled, wled->ctrl_base);
+ if (rc < 0)
+ return rc;
+
+ /* Configure the FEEDBACK OUTPUT register */
+ rc = qpnp_wled_read_reg(wled, QPNP_WLED_FDBK_OP_REG(wled->ctrl_base),
+ ®);
+ if (rc < 0)
+ return rc;
+ reg &= QPNP_WLED_FDBK_OP_MASK;
+ reg |= wled->fdbk_op;
+ rc = qpnp_wled_write_reg(wled, QPNP_WLED_FDBK_OP_REG(wled->ctrl_base),
+ reg);
+ if (rc)
+ return rc;
+
+ /* Configure the VREF register */
+ rc = qpnp_wled_vref_config(wled);
+ if (rc < 0) {
+ pr_err("Error in configuring wled vref, rc=%d\n", rc);
+ return rc;
+ }
+
+ /* Configure VLOOP_COMP_GM register */
+ rc = qpnp_wled_gm_config(wled);
+ if (rc < 0) {
+ pr_err("Error in configureing wled gm, rc=%d\n", rc);
+ return rc;
+ }
+
+ /* Configure the ILIM register */
+ rc = qpnp_wled_ilim_config(wled);
+ if (rc < 0) {
+ pr_err("Error in configuring wled ilim, rc=%d\n", rc);
+ return rc;
+ }
+
+ /* Configure auto PFM mode for LCD mode only */
+ if ((wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE ||
+ wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE)
+ && !wled->disp_type_amoled) {
+ reg = 0;
+ reg |= wled->lcd_auto_pfm_thresh;
+ reg |= wled->lcd_auto_pfm_en <<
+ QPNP_WLED_LCD_AUTO_PFM_EN_SHIFT;
+ rc = qpnp_wled_masked_write_reg(wled,
+ QPNP_WLED_LCD_AUTO_PFM_REG(wled->ctrl_base),
+ QPNP_WLED_LCD_AUTO_PFM_EN_BIT |
+ QPNP_WLED_LCD_AUTO_PFM_THRESH_MASK, reg);
+ if (rc < 0) {
+ pr_err("Write LCD_AUTO_PFM failed, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ /* Configure the Soft start Ramp delay: for AMOLED - 0,for LCD - 2 */
+ reg = (wled->disp_type_amoled) ? 0 : 2;
+ rc = qpnp_wled_write_reg(wled,
+ QPNP_WLED_SOFTSTART_RAMP_DLY(wled->ctrl_base), reg);
+ if (rc)
+ return rc;
+
+ /* Configure the MAX BOOST DUTY register */
+ if (wled->boost_duty_ns < QPNP_WLED_BOOST_DUTY_MIN_NS)
+ wled->boost_duty_ns = QPNP_WLED_BOOST_DUTY_MIN_NS;
+ else if (wled->boost_duty_ns > QPNP_WLED_BOOST_DUTY_MAX_NS)
+ wled->boost_duty_ns = QPNP_WLED_BOOST_DUTY_MAX_NS;
+
+ rc = qpnp_wled_read_reg(wled, QPNP_WLED_BOOST_DUTY_REG(wled->ctrl_base),
+ ®);
+ if (rc < 0)
+ return rc;
+ reg &= QPNP_WLED_BOOST_DUTY_MASK;
+ reg |= (wled->boost_duty_ns / QPNP_WLED_BOOST_DUTY_STEP_NS);
+ rc = qpnp_wled_write_reg(wled,
+ QPNP_WLED_BOOST_DUTY_REG(wled->ctrl_base), reg);
+ if (rc)
+ return rc;
+
+ /* Configure the SWITCHING FREQ register */
+ if (wled->switch_freq_khz == QPNP_WLED_SWITCH_FREQ_1600_KHZ)
+ temp = QPNP_WLED_SWITCH_FREQ_1600_KHZ_CODE;
+ else
+ temp = QPNP_WLED_SWITCH_FREQ_800_KHZ_CODE;
+
+ rc = qpnp_wled_read_reg(wled,
+ QPNP_WLED_SWITCH_FREQ_REG(wled->ctrl_base), ®);
+ if (rc < 0)
+ return rc;
+ reg &= QPNP_WLED_SWITCH_FREQ_MASK;
+ reg |= (temp | QPNP_WLED_SWITCH_FREQ_OVERWRITE);
+ rc = qpnp_wled_write_reg(wled,
+ QPNP_WLED_SWITCH_FREQ_REG(wled->ctrl_base), reg);
+ if (rc)
+ return rc;
+
+ rc = qpnp_wled_ovp_config(wled);
+ if (rc < 0) {
+ pr_err("Error in configuring OVP threshold, rc=%d\n", rc);
+ return rc;
+ }
+
+ if (is_avdd_trim_adjustment_required(wled)) {
+ rc = qpnp_wled_avdd_trim_config(wled);
+ if (rc < 0)
+ return rc;
+ }
+
+ rc = qpnp_wled_avdd_mode_config(wled);
+ if (rc < 0)
+ return rc;
+
+ /* Configure the MODULATION register */
+ if (wled->mod_freq_khz <= QPNP_WLED_MOD_FREQ_1200_KHZ) {
+ wled->mod_freq_khz = QPNP_WLED_MOD_FREQ_1200_KHZ;
+ temp = 3;
+ } else if (wled->mod_freq_khz <= QPNP_WLED_MOD_FREQ_2400_KHZ) {
+ wled->mod_freq_khz = QPNP_WLED_MOD_FREQ_2400_KHZ;
+ temp = 2;
+ } else if (wled->mod_freq_khz <= QPNP_WLED_MOD_FREQ_9600_KHZ) {
+ wled->mod_freq_khz = QPNP_WLED_MOD_FREQ_9600_KHZ;
+ temp = 1;
+ } else if (wled->mod_freq_khz <= QPNP_WLED_MOD_FREQ_19200_KHZ) {
+ wled->mod_freq_khz = QPNP_WLED_MOD_FREQ_19200_KHZ;
+ temp = 0;
+ } else {
+ wled->mod_freq_khz = QPNP_WLED_MOD_FREQ_9600_KHZ;
+ temp = 1;
+ }
+
+ rc = qpnp_wled_read_reg(wled, QPNP_WLED_MOD_REG(wled->sink_base), ®);
+ if (rc < 0)
+ return rc;
+ reg &= QPNP_WLED_MOD_FREQ_MASK;
+ reg |= (temp << QPNP_WLED_MOD_FREQ_SHIFT);
+
+ reg &= QPNP_WLED_PHASE_STAG_MASK;
+ reg |= (wled->en_phase_stag << QPNP_WLED_PHASE_STAG_SHIFT);
+
+ reg &= QPNP_WLED_ACC_CLK_FREQ_MASK;
+ reg |= (temp << QPNP_WLED_ACC_CLK_FREQ_SHIFT);
+
+ reg &= QPNP_WLED_DIM_RES_MASK;
+ reg |= (wled->en_9b_dim_res << QPNP_WLED_DIM_RES_SHIFT);
+
+ if (wled->dim_mode == QPNP_WLED_DIM_HYBRID) {
+ reg &= QPNP_WLED_DIM_HYB_MASK;
+ reg |= (1 << QPNP_WLED_DIM_HYB_SHIFT);
+ } else {
+ reg &= QPNP_WLED_DIM_HYB_MASK;
+ reg |= (0 << QPNP_WLED_DIM_HYB_SHIFT);
+ reg &= QPNP_WLED_DIM_ANA_MASK;
+ reg |= wled->dim_mode;
+ }
+
+ rc = qpnp_wled_write_reg(wled, QPNP_WLED_MOD_REG(wled->sink_base), reg);
+ if (rc)
+ return rc;
+
+ /* Configure the HYBRID THRESHOLD register */
+ if (wled->hyb_thres < QPNP_WLED_HYB_THRES_MIN)
+ wled->hyb_thres = QPNP_WLED_HYB_THRES_MIN;
+ else if (wled->hyb_thres > QPNP_WLED_HYB_THRES_MAX)
+ wled->hyb_thres = QPNP_WLED_HYB_THRES_MAX;
+
+ rc = qpnp_wled_read_reg(wled, QPNP_WLED_HYB_THRES_REG(wled->sink_base),
+ ®);
+ if (rc < 0)
+ return rc;
+ reg &= QPNP_WLED_HYB_THRES_MASK;
+ temp = fls(wled->hyb_thres / QPNP_WLED_HYB_THRES_MIN) - 1;
+ reg |= temp;
+ rc = qpnp_wled_write_reg(wled, QPNP_WLED_HYB_THRES_REG(wled->sink_base),
+ reg);
+ if (rc)
+ return rc;
+
+ /* Configure TEST5 register */
+ if (wled->dim_mode == QPNP_WLED_DIM_DIGITAL) {
+ reg = QPNP_WLED_SINK_TEST5_DIG;
+ } else {
+ reg = QPNP_WLED_SINK_TEST5_HYB;
+ if (wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE)
+ reg |= QPNP_WLED_SINK_TEST5_HVG_PULL_STR_BIT;
+ }
+
+ rc = qpnp_wled_sec_write_reg(wled,
+ QPNP_WLED_SINK_TEST5_REG(wled->sink_base), reg);
+ if (rc)
+ return rc;
+
+ /* disable all current sinks and enable selected strings */
+ reg = 0x00;
+ rc = qpnp_wled_write_reg(wled, QPNP_WLED_CURR_SINK_REG(wled->sink_base),
+ reg);
+
+ for (i = 0; i < wled->num_strings; i++) {
+ if (wled->strings[i] >= QPNP_WLED_MAX_STRINGS) {
+ dev_err(&wled->pdev->dev, "Invalid string number\n");
+ return -EINVAL;
+ }
+
+ /* MODULATOR */
+ rc = qpnp_wled_read_reg(wled,
+ QPNP_WLED_MOD_EN_REG(wled->sink_base,
+ wled->strings[i]), ®);
+ if (rc < 0)
+ return rc;
+ reg &= QPNP_WLED_MOD_EN_MASK;
+ reg |= (QPNP_WLED_MOD_EN << QPNP_WLED_MOD_EN_SHFT);
+
+ if (wled->dim_mode == QPNP_WLED_DIM_HYBRID)
+ reg &= QPNP_WLED_GATE_DRV_MASK;
+ else
+ reg |= ~QPNP_WLED_GATE_DRV_MASK;
+
+ rc = qpnp_wled_write_reg(wled,
+ QPNP_WLED_MOD_EN_REG(wled->sink_base,
+ wled->strings[i]), reg);
+ if (rc)
+ return rc;
+
+ /* SYNC DELAY */
+ if (wled->sync_dly_us > QPNP_WLED_SYNC_DLY_MAX_US)
+ wled->sync_dly_us = QPNP_WLED_SYNC_DLY_MAX_US;
+
+ rc = qpnp_wled_read_reg(wled,
+ QPNP_WLED_SYNC_DLY_REG(wled->sink_base,
+ wled->strings[i]), ®);
+ if (rc < 0)
+ return rc;
+ reg &= QPNP_WLED_SYNC_DLY_MASK;
+ temp = wled->sync_dly_us / QPNP_WLED_SYNC_DLY_STEP_US;
+ reg |= temp;
+ rc = qpnp_wled_write_reg(wled,
+ QPNP_WLED_SYNC_DLY_REG(wled->sink_base,
+ wled->strings[i]), reg);
+ if (rc)
+ return rc;
+
+ /* FULL SCALE CURRENT */
+ if (wled->fs_curr_ua > QPNP_WLED_FS_CURR_MAX_UA)
+ wled->fs_curr_ua = QPNP_WLED_FS_CURR_MAX_UA;
+
+ rc = qpnp_wled_read_reg(wled,
+ QPNP_WLED_FS_CURR_REG(wled->sink_base,
+ wled->strings[i]), ®);
+ if (rc < 0)
+ return rc;
+ reg &= QPNP_WLED_FS_CURR_MASK;
+ temp = wled->fs_curr_ua / QPNP_WLED_FS_CURR_STEP_UA;
+ reg |= temp;
+ rc = qpnp_wled_write_reg(wled,
+ QPNP_WLED_FS_CURR_REG(wled->sink_base,
+ wled->strings[i]), reg);
+ if (rc)
+ return rc;
+
+ /* CABC */
+ rc = qpnp_wled_read_reg(wled,
+ QPNP_WLED_CABC_REG(wled->sink_base,
+ wled->strings[i]), ®);
+ if (rc < 0)
+ return rc;
+ reg &= QPNP_WLED_CABC_MASK;
+ reg |= (wled->en_cabc << QPNP_WLED_CABC_SHIFT);
+ rc = qpnp_wled_write_reg(wled,
+ QPNP_WLED_CABC_REG(wled->sink_base,
+ wled->strings[i]), reg);
+ if (rc)
+ return rc;
+
+ /* Enable CURRENT SINK */
+ rc = qpnp_wled_read_reg(wled,
+ QPNP_WLED_CURR_SINK_REG(wled->sink_base), ®);
+ if (rc < 0)
+ return rc;
+ temp = wled->strings[i] + QPNP_WLED_CURR_SINK_SHIFT;
+ reg |= (1 << temp);
+ rc = qpnp_wled_write_reg(wled,
+ QPNP_WLED_CURR_SINK_REG(wled->sink_base), reg);
+ if (rc)
+ return rc;
+ }
+
+ rc = qpnp_wled_sync_reg_toggle(wled);
+ if (rc < 0) {
+ dev_err(&wled->pdev->dev, "Failed to toggle sync reg %d\n", rc);
+ return rc;
+ }
+
+ /* setup ovp and sc irqs */
+ if (wled->ovp_irq >= 0) {
+ rc = devm_request_threaded_irq(&wled->pdev->dev, wled->ovp_irq,
+ NULL, qpnp_wled_ovp_irq_handler, IRQF_ONESHOT,
+ "qpnp_wled_ovp_irq", wled);
+ if (rc < 0) {
+ dev_err(&wled->pdev->dev,
+ "Unable to request ovp(%d) IRQ(err:%d)\n",
+ wled->ovp_irq, rc);
+ return rc;
+ }
+ }
+
+ if (wled->sc_irq >= 0) {
+ wled->sc_cnt = 0;
+ rc = devm_request_threaded_irq(&wled->pdev->dev, wled->sc_irq,
+ NULL, qpnp_wled_sc_irq_handler, IRQF_ONESHOT,
+ "qpnp_wled_sc_irq", wled);
+ if (rc < 0) {
+ dev_err(&wled->pdev->dev,
+ "Unable to request sc(%d) IRQ(err:%d)\n",
+ wled->sc_irq, rc);
+ return rc;
+ }
+
+ rc = qpnp_wled_read_reg(wled,
+ QPNP_WLED_SC_PRO_REG(wled->ctrl_base), ®);
+ if (rc < 0)
+ return rc;
+ reg &= QPNP_WLED_EN_SC_DEB_CYCLES_MASK;
+ reg |= 1 << QPNP_WLED_EN_SC_SHIFT;
+
+ if (wled->sc_deb_cycles < QPNP_WLED_SC_DEB_CYCLES_MIN)
+ wled->sc_deb_cycles = QPNP_WLED_SC_DEB_CYCLES_MIN;
+ else if (wled->sc_deb_cycles > QPNP_WLED_SC_DEB_CYCLES_MAX)
+ wled->sc_deb_cycles = QPNP_WLED_SC_DEB_CYCLES_MAX;
+ temp = fls(wled->sc_deb_cycles) - QPNP_WLED_SC_DEB_CYCLES_SUB;
+ reg |= (temp << 1);
+
+ if (wled->disp_type_amoled)
+ reg |= QPNP_WLED_SC_PRO_EN_DSCHGR;
+
+ rc = qpnp_wled_write_reg(wled,
+ QPNP_WLED_SC_PRO_REG(wled->ctrl_base), reg);
+ if (rc)
+ return rc;
+
+ if (wled->en_ext_pfet_sc_pro) {
+ reg = QPNP_WLED_EXT_FET_DTEST2;
+ rc = qpnp_wled_sec_write_reg(wled,
+ QPNP_WLED_TEST1_REG(wled->ctrl_base),
+ reg);
+ if (rc)
+ return rc;
+ }
+ } else {
+ rc = qpnp_wled_read_reg(wled,
+ QPNP_WLED_SC_PRO_REG(wled->ctrl_base), ®);
+ if (rc < 0)
+ return rc;
+ reg &= QPNP_WLED_EN_DEB_CYCLES_MASK;
+
+ if (wled->sc_deb_cycles < QPNP_WLED_SC_DEB_CYCLES_MIN)
+ wled->sc_deb_cycles = QPNP_WLED_SC_DEB_CYCLES_MIN;
+ else if (wled->sc_deb_cycles > QPNP_WLED_SC_DEB_CYCLES_MAX)
+ wled->sc_deb_cycles = QPNP_WLED_SC_DEB_CYCLES_MAX;
+ temp = fls(wled->sc_deb_cycles) - QPNP_WLED_SC_DEB_CYCLES_SUB;
+ reg |= (temp << 1);
+
+ rc = qpnp_wled_write_reg(wled,
+ QPNP_WLED_SC_PRO_REG(wled->ctrl_base), reg);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+
+/* parse wled dtsi parameters */
+static int qpnp_wled_parse_dt(struct qpnp_wled *wled)
+{
+ struct platform_device *pdev = wled->pdev;
+ struct property *prop;
+ const char *temp_str;
+ u32 temp_val;
+ int rc, i;
+ u8 *strings;
+
+ wled->cdev.name = "wled";
+ rc = of_property_read_string(pdev->dev.of_node,
+ "linux,name", &wled->cdev.name);
+ if (rc && (rc != -EINVAL)) {
+ dev_err(&pdev->dev, "Unable to read led name\n");
+ return rc;
+ }
+
+ wled->cdev.default_trigger = QPNP_WLED_TRIGGER_NONE;
+ rc = of_property_read_string(pdev->dev.of_node, "linux,default-trigger",
+ &wled->cdev.default_trigger);
+ if (rc && (rc != -EINVAL)) {
+ dev_err(&pdev->dev, "Unable to read led trigger\n");
+ return rc;
+ }
+
+ wled->disp_type_amoled = of_property_read_bool(pdev->dev.of_node,
+ "qcom,disp-type-amoled");
+ if (wled->disp_type_amoled) {
+ wled->vref_psm_mv = QPNP_WLED_VREF_PSM_DFLT_AMOLED_MV;
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,vref-psm-mv", &temp_val);
+ if (!rc) {
+ wled->vref_psm_mv = temp_val;
+ } else if (rc != -EINVAL) {
+ dev_err(&pdev->dev, "Unable to read vref-psm\n");
+ return rc;
+ }
+
+ wled->loop_comp_res_kohm =
+ QPNP_WLED_LOOP_COMP_RES_DFLT_AMOLED_KOHM;
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,loop-comp-res-kohm", &temp_val);
+ if (!rc) {
+ wled->loop_comp_res_kohm = temp_val;
+ } else if (rc != -EINVAL) {
+ dev_err(&pdev->dev, "Unable to read loop-comp-res-kohm\n");
+ return rc;
+ }
+
+ wled->avdd_mode_spmi = of_property_read_bool(pdev->dev.of_node,
+ "qcom,avdd-mode-spmi");
+
+ wled->avdd_target_voltage_mv = QPNP_WLED_DFLT_AVDD_MV;
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,avdd-target-voltage-mv", &temp_val);
+ if (!rc) {
+ wled->avdd_target_voltage_mv = temp_val;
+ } else if (rc != -EINVAL) {
+ dev_err(&pdev->dev, "Unable to read avdd target voltage\n");
+ return rc;
+ }
+ }
+
+ if (wled->disp_type_amoled) {
+ if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE ||
+ wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE)
+ wled->loop_ea_gm =
+ QPNP_WLED_LOOP_GM_DFLT_AMOLED_PMI8998;
+ else
+ wled->loop_ea_gm =
+ QPNP_WLED_LOOP_EA_GM_DFLT_AMOLED_PMI8994;
+ } else {
+ wled->loop_ea_gm = QPNP_WLED_LOOP_GM_DFLT_WLED;
+ }
+
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,loop-ea-gm", &temp_val);
+ if (!rc) {
+ wled->loop_ea_gm = temp_val;
+ } else if (rc != -EINVAL) {
+ dev_err(&pdev->dev, "Unable to read loop-ea-gm\n");
+ return rc;
+ }
+
+ if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE ||
+ wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) {
+ wled->loop_auto_gm_en =
+ of_property_read_bool(pdev->dev.of_node,
+ "qcom,loop-auto-gm-en");
+ wled->loop_auto_gm_thresh = QPNP_WLED_LOOP_AUTO_GM_DFLT_THRESH;
+ rc = of_property_read_u8(pdev->dev.of_node,
+ "qcom,loop-auto-gm-thresh",
+ &wled->loop_auto_gm_thresh);
+ if (rc && rc != -EINVAL) {
+ dev_err(&pdev->dev,
+ "Unable to read loop-auto-gm-thresh\n");
+ return rc;
+ }
+ }
+
+ if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE ||
+ wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) {
+
+ if (wled->pmic_rev_id->rev4 == PMI8998_V2P0_REV4)
+ wled->lcd_auto_pfm_en = false;
+ else
+ wled->lcd_auto_pfm_en = true;
+
+ wled->lcd_auto_pfm_thresh = QPNP_WLED_LCD_AUTO_PFM_DFLT_THRESH;
+ rc = of_property_read_u8(pdev->dev.of_node,
+ "qcom,lcd-auto-pfm-thresh",
+ &wled->lcd_auto_pfm_thresh);
+ if (rc && rc != -EINVAL) {
+ dev_err(&pdev->dev,
+ "Unable to read lcd-auto-pfm-thresh\n");
+ return rc;
+ }
+
+ if (wled->lcd_auto_pfm_thresh >
+ QPNP_WLED_LCD_AUTO_PFM_THRESH_MAX)
+ wled->lcd_auto_pfm_thresh =
+ QPNP_WLED_LCD_AUTO_PFM_THRESH_MAX;
+ }
+
+ wled->sc_deb_cycles = QPNP_WLED_SC_DEB_CYCLES_DFLT;
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,sc-deb-cycles", &temp_val);
+ if (!rc) {
+ wled->sc_deb_cycles = temp_val;
+ } else if (rc != -EINVAL) {
+ dev_err(&pdev->dev, "Unable to read sc debounce cycles\n");
+ return rc;
+ }
+
+ wled->fdbk_op = QPNP_WLED_FDBK_AUTO;
+ rc = of_property_read_string(pdev->dev.of_node,
+ "qcom,fdbk-output", &temp_str);
+ if (!rc) {
+ if (strcmp(temp_str, "wled1") == 0)
+ wled->fdbk_op = QPNP_WLED_FDBK_WLED1;
+ else if (strcmp(temp_str, "wled2") == 0)
+ wled->fdbk_op = QPNP_WLED_FDBK_WLED2;
+ else if (strcmp(temp_str, "wled3") == 0)
+ wled->fdbk_op = QPNP_WLED_FDBK_WLED3;
+ else if (strcmp(temp_str, "wled4") == 0)
+ wled->fdbk_op = QPNP_WLED_FDBK_WLED4;
+ else
+ wled->fdbk_op = QPNP_WLED_FDBK_AUTO;
+ } else if (rc != -EINVAL) {
+ dev_err(&pdev->dev, "Unable to read feedback output\n");
+ return rc;
+ }
+
+ if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE ||
+ wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE)
+ wled->vref_uv = vref_setting_pmi8998.default_uv;
+ else
+ wled->vref_uv = vref_setting_pmi8994.default_uv;
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,vref-uv", &temp_val);
+ if (!rc) {
+ wled->vref_uv = temp_val;
+ } else if (rc != -EINVAL) {
+ dev_err(&pdev->dev, "Unable to read vref\n");
+ return rc;
+ }
+
+ wled->switch_freq_khz = QPNP_WLED_SWITCH_FREQ_800_KHZ;
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,switch-freq-khz", &temp_val);
+ if (!rc) {
+ wled->switch_freq_khz = temp_val;
+ } else if (rc != -EINVAL) {
+ dev_err(&pdev->dev, "Unable to read switch freq\n");
+ return rc;
+ }
+
+ if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE ||
+ wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE)
+ wled->ovp_mv = 29600;
+ else
+ wled->ovp_mv = 29500;
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,ovp-mv", &temp_val);
+ if (!rc) {
+ wled->ovp_mv = temp_val;
+ } else if (rc != -EINVAL) {
+ dev_err(&pdev->dev, "Unable to read ovp\n");
+ return rc;
+ }
+
+ if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE ||
+ wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) {
+ if (wled->disp_type_amoled)
+ wled->ilim_ma = PMI8998_AMOLED_DFLT_ILIM_MA;
+ else
+ wled->ilim_ma = PMI8998_WLED_DFLT_ILIM_MA;
+ } else {
+ if (wled->disp_type_amoled)
+ wled->ilim_ma = PMI8994_AMOLED_DFLT_ILIM_MA;
+ else
+ wled->ilim_ma = PMI8994_WLED_DFLT_ILIM_MA;
+ }
+
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,ilim-ma", &temp_val);
+ if (!rc) {
+ wled->ilim_ma = temp_val;
+ } else if (rc != -EINVAL) {
+ dev_err(&pdev->dev, "Unable to read ilim\n");
+ return rc;
+ }
+
+ wled->boost_duty_ns = QPNP_WLED_DEF_BOOST_DUTY_NS;
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,boost-duty-ns", &temp_val);
+ if (!rc) {
+ wled->boost_duty_ns = temp_val;
+ } else if (rc != -EINVAL) {
+ dev_err(&pdev->dev, "Unable to read boost duty\n");
+ return rc;
+ }
+
+ wled->mod_freq_khz = QPNP_WLED_MOD_FREQ_9600_KHZ;
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,mod-freq-khz", &temp_val);
+ if (!rc) {
+ wled->mod_freq_khz = temp_val;
+ } else if (rc != -EINVAL) {
+ dev_err(&pdev->dev, "Unable to read modulation freq\n");
+ return rc;
+ }
+
+ wled->dim_mode = QPNP_WLED_DIM_HYBRID;
+ rc = of_property_read_string(pdev->dev.of_node,
+ "qcom,dim-mode", &temp_str);
+ if (!rc) {
+ if (strcmp(temp_str, "analog") == 0)
+ wled->dim_mode = QPNP_WLED_DIM_ANALOG;
+ else if (strcmp(temp_str, "digital") == 0)
+ wled->dim_mode = QPNP_WLED_DIM_DIGITAL;
+ else
+ wled->dim_mode = QPNP_WLED_DIM_HYBRID;
+ } else if (rc != -EINVAL) {
+ dev_err(&pdev->dev, "Unable to read dim mode\n");
+ return rc;
+ }
+
+ if (wled->dim_mode == QPNP_WLED_DIM_HYBRID) {
+ wled->hyb_thres = QPNP_WLED_DEF_HYB_THRES;
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,hyb-thres", &temp_val);
+ if (!rc) {
+ wled->hyb_thres = temp_val;
+ } else if (rc != -EINVAL) {
+ dev_err(&pdev->dev, "Unable to read hyb threshold\n");
+ return rc;
+ }
+ }
+
+ wled->sync_dly_us = QPNP_WLED_DEF_SYNC_DLY_US;
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,sync-dly-us", &temp_val);
+ if (!rc) {
+ wled->sync_dly_us = temp_val;
+ } else if (rc != -EINVAL) {
+ dev_err(&pdev->dev, "Unable to read sync delay\n");
+ return rc;
+ }
+
+ wled->fs_curr_ua = QPNP_WLED_FS_CURR_MAX_UA;
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,fs-curr-ua", &temp_val);
+ if (!rc) {
+ wled->fs_curr_ua = temp_val;
+ } else if (rc != -EINVAL) {
+ dev_err(&pdev->dev, "Unable to read full scale current\n");
+ return rc;
+ }
+
+ wled->cons_sync_write_delay_us = 0;
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,cons-sync-write-delay-us", &temp_val);
+ if (!rc)
+ wled->cons_sync_write_delay_us = temp_val;
+
+ wled->en_9b_dim_res = of_property_read_bool(pdev->dev.of_node,
+ "qcom,en-9b-dim-res");
+ wled->en_phase_stag = of_property_read_bool(pdev->dev.of_node,
+ "qcom,en-phase-stag");
+ wled->en_cabc = of_property_read_bool(pdev->dev.of_node,
+ "qcom,en-cabc");
+
+ prop = of_find_property(pdev->dev.of_node,
+ "qcom,led-strings-list", &temp_val);
+ if (!prop || !temp_val || temp_val > QPNP_WLED_MAX_STRINGS) {
+ dev_err(&pdev->dev, "Invalid strings info, use default");
+ wled->num_strings = QPNP_WLED_MAX_STRINGS;
+ for (i = 0; i < wled->num_strings; i++)
+ wled->strings[i] = i;
+ } else {
+ wled->num_strings = temp_val;
+ strings = prop->value;
+ for (i = 0; i < wled->num_strings; ++i)
+ wled->strings[i] = strings[i];
+ }
+
+ wled->ovp_irq = platform_get_irq_byname(pdev, "ovp-irq");
+ if (wled->ovp_irq < 0)
+ dev_dbg(&pdev->dev, "ovp irq is not used\n");
+
+ wled->sc_irq = platform_get_irq_byname(pdev, "sc-irq");
+ if (wled->sc_irq < 0)
+ dev_dbg(&pdev->dev, "sc irq is not used\n");
+
+ wled->en_ext_pfet_sc_pro = of_property_read_bool(pdev->dev.of_node,
+ "qcom,en-ext-pfet-sc-pro");
+
+ return 0;
+}
+
+static int qpnp_wled_probe(struct platform_device *pdev)
+{
+ struct qpnp_wled *wled;
+ struct device_node *revid_node;
+ int rc = 0, i;
+ const __be32 *prop;
+
+ wled = devm_kzalloc(&pdev->dev, sizeof(*wled), GFP_KERNEL);
+ if (!wled)
+ return -ENOMEM;
+ wled->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!wled->regmap) {
+ dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+ return -EINVAL;
+ }
+
+ wled->pdev = pdev;
+
+ revid_node = of_parse_phandle(pdev->dev.of_node, "qcom,pmic-revid", 0);
+ if (!revid_node) {
+ pr_err("Missing qcom,pmic-revid property - driver failed\n");
+ return -EINVAL;
+ }
+
+ wled->pmic_rev_id = get_revid_data(revid_node);
+ if (IS_ERR_OR_NULL(wled->pmic_rev_id)) {
+ pr_err("Unable to get pmic_revid rc=%ld\n",
+ PTR_ERR(wled->pmic_rev_id));
+ /*
+ * the revid peripheral must be registered, any failure
+ * here only indicates that the rev-id module has not
+ * probed yet.
+ */
+ return -EPROBE_DEFER;
+ }
+
+ pr_debug("PMIC subtype %d Digital major %d\n",
+ wled->pmic_rev_id->pmic_subtype, wled->pmic_rev_id->rev4);
+
+ prop = of_get_address_by_name(pdev->dev.of_node, QPNP_WLED_SINK_BASE,
+ 0, 0);
+ if (!prop) {
+ dev_err(&pdev->dev, "Couldnt find sink's addr rc %d\n", rc);
+ return rc;
+ }
+ wled->sink_base = be32_to_cpu(*prop);
+
+ prop = of_get_address_by_name(pdev->dev.of_node, QPNP_WLED_CTRL_BASE,
+ 0, 0);
+ if (!prop) {
+ dev_err(&pdev->dev, "Couldnt find ctrl's addr rc = %d\n", rc);
+ return rc;
+ }
+ wled->ctrl_base = be32_to_cpu(*prop);
+
+ dev_set_drvdata(&pdev->dev, wled);
+
+ rc = qpnp_wled_parse_dt(wled);
+ if (rc) {
+ dev_err(&pdev->dev, "DT parsing failed\n");
+ return rc;
+ }
+
+ mutex_init(&wled->bus_lock);
+ rc = qpnp_wled_config(wled);
+ if (rc) {
+ dev_err(&pdev->dev, "wled config failed\n");
+ return rc;
+ }
+
+ mutex_init(&wled->lock);
+ INIT_WORK(&wled->work, qpnp_wled_work);
+ wled->ramp_ms = QPNP_WLED_RAMP_DLY_MS;
+ wled->ramp_step = 1;
+
+ wled->cdev.brightness_set = qpnp_wled_set;
+ wled->cdev.brightness_get = qpnp_wled_get;
+
+ wled->cdev.max_brightness = WLED_MAX_LEVEL_4095;
+
+ rc = led_classdev_register(&pdev->dev, &wled->cdev);
+ if (rc) {
+ dev_err(&pdev->dev, "wled registration failed(%d)\n", rc);
+ goto wled_register_fail;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(qpnp_wled_attrs); i++) {
+ rc = sysfs_create_file(&wled->cdev.dev->kobj,
+ &qpnp_wled_attrs[i].attr);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "sysfs creation failed\n");
+ goto sysfs_fail;
+ }
+ }
+
+ return 0;
+
+sysfs_fail:
+ for (i--; i >= 0; i--)
+ sysfs_remove_file(&wled->cdev.dev->kobj,
+ &qpnp_wled_attrs[i].attr);
+ led_classdev_unregister(&wled->cdev);
+wled_register_fail:
+ cancel_work_sync(&wled->work);
+ mutex_destroy(&wled->lock);
+ return rc;
+}
+
+static int qpnp_wled_remove(struct platform_device *pdev)
+{
+ struct qpnp_wled *wled = dev_get_drvdata(&pdev->dev);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(qpnp_wled_attrs); i++)
+ sysfs_remove_file(&wled->cdev.dev->kobj,
+ &qpnp_wled_attrs[i].attr);
+
+ led_classdev_unregister(&wled->cdev);
+ cancel_work_sync(&wled->work);
+ mutex_destroy(&wled->lock);
+
+ return 0;
+}
+
+static const struct of_device_id spmi_match_table[] = {
+ { .compatible = "qcom,qpnp-wled",},
+ { },
+};
+
+static struct platform_driver qpnp_wled_driver = {
+ .driver = {
+ .name = "qcom,qpnp-wled",
+ .of_match_table = spmi_match_table,
+ },
+ .probe = qpnp_wled_probe,
+ .remove = qpnp_wled_remove,
+};
+
+static int __init qpnp_wled_init(void)
+{
+ return platform_driver_register(&qpnp_wled_driver);
+}
+module_init(qpnp_wled_init);
+
+static void __exit qpnp_wled_exit(void)
+{
+ platform_driver_unregister(&qpnp_wled_driver);
+}
+module_exit(qpnp_wled_exit);
+
+MODULE_DESCRIPTION("QPNP WLED driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("leds:leds-qpnp-wled");
diff --git a/drivers/leds/leds-qpnp.c b/drivers/leds/leds-qpnp.c
new file mode 100644
index 0000000..85a6be8
--- /dev/null
+++ b/drivers/leds/leds-qpnp.c
@@ -0,0 +1,4188 @@
+/* Copyright (c) 2012-2015, 2017, The Linux Foundation. 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/regmap.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/leds.h>
+#include <linux/err.h>
+#include <linux/spinlock.h>
+#include <linux/of_platform.h>
+#include <linux/of_device.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/qpnp/pwm.h>
+#include <linux/workqueue.h>
+#include <linux/delay.h>
+#include <linux/regulator/consumer.h>
+#include <linux/delay.h>
+
+#define WLED_MOD_EN_REG(base, n) (base + 0x60 + n*0x10)
+#define WLED_IDAC_DLY_REG(base, n) (WLED_MOD_EN_REG(base, n) + 0x01)
+#define WLED_FULL_SCALE_REG(base, n) (WLED_IDAC_DLY_REG(base, n) + 0x01)
+#define WLED_MOD_SRC_SEL_REG(base, n) (WLED_FULL_SCALE_REG(base, n) + 0x01)
+
+/* wled control registers */
+#define WLED_OVP_INT_STATUS(base) (base + 0x10)
+#define WLED_BRIGHTNESS_CNTL_LSB(base, n) (base + 0x40 + 2*n)
+#define WLED_BRIGHTNESS_CNTL_MSB(base, n) (base + 0x41 + 2*n)
+#define WLED_MOD_CTRL_REG(base) (base + 0x46)
+#define WLED_SYNC_REG(base) (base + 0x47)
+#define WLED_FDBCK_CTRL_REG(base) (base + 0x48)
+#define WLED_SWITCHING_FREQ_REG(base) (base + 0x4C)
+#define WLED_OVP_CFG_REG(base) (base + 0x4D)
+#define WLED_BOOST_LIMIT_REG(base) (base + 0x4E)
+#define WLED_CURR_SINK_REG(base) (base + 0x4F)
+#define WLED_HIGH_POLE_CAP_REG(base) (base + 0x58)
+#define WLED_CURR_SINK_MASK 0xE0
+#define WLED_CURR_SINK_SHFT 0x05
+#define WLED_DISABLE_ALL_SINKS 0x00
+#define WLED_DISABLE_1_2_SINKS 0x80
+#define WLED_SWITCH_FREQ_MASK 0x0F
+#define WLED_OVP_VAL_MASK 0x03
+#define WLED_OVP_INT_MASK 0x02
+#define WLED_OVP_VAL_BIT_SHFT 0x00
+#define WLED_BOOST_LIMIT_MASK 0x07
+#define WLED_BOOST_LIMIT_BIT_SHFT 0x00
+#define WLED_BOOST_ON 0x80
+#define WLED_BOOST_OFF 0x00
+#define WLED_EN_MASK 0x80
+#define WLED_NO_MASK 0x00
+#define WLED_CP_SELECT_MAX 0x03
+#define WLED_CP_SELECT_MASK 0x02
+#define WLED_USE_EXT_GEN_MOD_SRC 0x01
+#define WLED_CTL_DLY_STEP 200
+#define WLED_CTL_DLY_MAX 1400
+#define WLED_MAX_CURR 25
+#define WLED_NO_CURRENT 0x00
+#define WLED_OVP_DELAY 1000
+#define WLED_OVP_DELAY_INT 200
+#define WLED_OVP_DELAY_LOOP 100
+#define WLED_MSB_MASK 0x0F
+#define WLED_MAX_CURR_MASK 0x1F
+#define WLED_OP_FDBCK_MASK 0x07
+#define WLED_OP_FDBCK_BIT_SHFT 0x00
+#define WLED_OP_FDBCK_DEFAULT 0x00
+
+#define WLED_SET_ILIM_CODE 0x01
+
+#define WLED_MAX_LEVEL 4095
+#define WLED_8_BIT_MASK 0xFF
+#define WLED_4_BIT_MASK 0x0F
+#define WLED_8_BIT_SHFT 0x08
+#define WLED_MAX_DUTY_CYCLE 0xFFF
+
+#define WLED_SYNC_VAL 0x07
+#define WLED_SYNC_RESET_VAL 0x00
+
+#define PMIC_VER_8026 0x04
+#define PMIC_VER_8941 0x01
+#define PMIC_VERSION_REG 0x0105
+
+#define WLED_DEFAULT_STRINGS 0x01
+#define WLED_THREE_STRINGS 0x03
+#define WLED_MAX_TRIES 5
+#define WLED_DEFAULT_OVP_VAL 0x02
+#define WLED_BOOST_LIM_DEFAULT 0x03
+#define WLED_CP_SEL_DEFAULT 0x00
+#define WLED_CTRL_DLY_DEFAULT 0x00
+#define WLED_SWITCH_FREQ_DEFAULT 0x0B
+
+#define FLASH_SAFETY_TIMER(base) (base + 0x40)
+#define FLASH_MAX_CURR(base) (base + 0x41)
+#define FLASH_LED_0_CURR(base) (base + 0x42)
+#define FLASH_LED_1_CURR(base) (base + 0x43)
+#define FLASH_CLAMP_CURR(base) (base + 0x44)
+#define FLASH_LED_TMR_CTRL(base) (base + 0x48)
+#define FLASH_HEADROOM(base) (base + 0x4A)
+#define FLASH_STARTUP_DELAY(base) (base + 0x4B)
+#define FLASH_MASK_ENABLE(base) (base + 0x4C)
+#define FLASH_VREG_OK_FORCE(base) (base + 0x4F)
+#define FLASH_ENABLE_CONTROL(base) (base + 0x46)
+#define FLASH_LED_STROBE_CTRL(base) (base + 0x47)
+#define FLASH_WATCHDOG_TMR(base) (base + 0x49)
+#define FLASH_FAULT_DETECT(base) (base + 0x51)
+#define FLASH_PERIPHERAL_SUBTYPE(base) (base + 0x05)
+#define FLASH_CURRENT_RAMP(base) (base + 0x54)
+
+#define FLASH_MAX_LEVEL 0x4F
+#define TORCH_MAX_LEVEL 0x0F
+#define FLASH_NO_MASK 0x00
+
+#define FLASH_MASK_1 0x20
+#define FLASH_MASK_REG_MASK 0xE0
+#define FLASH_HEADROOM_MASK 0x03
+#define FLASH_SAFETY_TIMER_MASK 0x7F
+#define FLASH_CURRENT_MASK 0xFF
+#define FLASH_MAX_CURRENT_MASK 0x7F
+#define FLASH_TMR_MASK 0x03
+#define FLASH_TMR_WATCHDOG 0x03
+#define FLASH_TMR_SAFETY 0x00
+#define FLASH_FAULT_DETECT_MASK 0X80
+#define FLASH_HW_VREG_OK 0x40
+#define FLASH_SW_VREG_OK 0x80
+#define FLASH_VREG_MASK 0xC0
+#define FLASH_STARTUP_DLY_MASK 0x02
+#define FLASH_CURRENT_RAMP_MASK 0xBF
+
+#define FLASH_ENABLE_ALL 0xE0
+#define FLASH_ENABLE_MODULE 0x80
+#define FLASH_ENABLE_MODULE_MASK 0x80
+#define FLASH_DISABLE_ALL 0x00
+#define FLASH_ENABLE_MASK 0xE0
+#define FLASH_ENABLE_LED_0 0xC0
+#define FLASH_ENABLE_LED_1 0xA0
+#define FLASH_INIT_MASK 0xE0
+#define FLASH_SELFCHECK_ENABLE 0x80
+#define FLASH_WATCHDOG_MASK 0x1F
+#define FLASH_RAMP_STEP_27US 0xBF
+
+#define FLASH_HW_SW_STROBE_SEL_MASK 0x04
+#define FLASH_STROBE_MASK 0xC7
+#define FLASH_LED_0_OUTPUT 0x80
+#define FLASH_LED_1_OUTPUT 0x40
+#define FLASH_TORCH_OUTPUT 0xC0
+
+#define FLASH_CURRENT_PRGM_MIN 1
+#define FLASH_CURRENT_PRGM_SHIFT 1
+#define FLASH_CURRENT_MAX 0x4F
+#define FLASH_CURRENT_TORCH 0x07
+
+#define FLASH_DURATION_200ms 0x13
+#define TORCH_DURATION_12s 0x0A
+#define FLASH_CLAMP_200mA 0x0F
+
+#define FLASH_SUBTYPE_DUAL 0x01
+#define FLASH_SUBTYPE_SINGLE 0x02
+
+#define FLASH_RAMP_UP_DELAY_US 1000
+#define FLASH_RAMP_DN_DELAY_US 2160
+
+#define LED_TRIGGER_DEFAULT "none"
+
+#define RGB_LED_SRC_SEL(base) (base + 0x45)
+#define RGB_LED_EN_CTL(base) (base + 0x46)
+#define RGB_LED_ATC_CTL(base) (base + 0x47)
+
+#define RGB_MAX_LEVEL LED_FULL
+#define RGB_LED_ENABLE_RED 0x80
+#define RGB_LED_ENABLE_GREEN 0x40
+#define RGB_LED_ENABLE_BLUE 0x20
+#define RGB_LED_SOURCE_VPH_PWR 0x01
+#define RGB_LED_ENABLE_MASK 0xE0
+#define RGB_LED_SRC_MASK 0x03
+#define QPNP_LED_PWM_FLAGS (PM_PWM_LUT_LOOP | PM_PWM_LUT_RAMP_UP)
+#define QPNP_LUT_RAMP_STEP_DEFAULT 255
+#define PWM_LUT_MAX_SIZE 63
+#define PWM_GPLED_LUT_MAX_SIZE 31
+#define RGB_LED_DISABLE 0x00
+
+#define MPP_MAX_LEVEL LED_FULL
+#define LED_MPP_MODE_CTRL(base) (base + 0x40)
+#define LED_MPP_VIN_CTRL(base) (base + 0x41)
+#define LED_MPP_EN_CTRL(base) (base + 0x46)
+#define LED_MPP_SINK_CTRL(base) (base + 0x4C)
+
+#define LED_MPP_CURRENT_MIN 5
+#define LED_MPP_CURRENT_MAX 40
+#define LED_MPP_VIN_CTRL_DEFAULT 0
+#define LED_MPP_CURRENT_PER_SETTING 5
+#define LED_MPP_SOURCE_SEL_DEFAULT LED_MPP_MODE_ENABLE
+
+#define LED_MPP_SINK_MASK 0x07
+#define LED_MPP_MODE_MASK 0x7F
+#define LED_MPP_VIN_MASK 0x03
+#define LED_MPP_EN_MASK 0x80
+#define LED_MPP_SRC_MASK 0x0F
+#define LED_MPP_MODE_CTRL_MASK 0x70
+
+#define LED_MPP_MODE_SINK (0x06 << 4)
+#define LED_MPP_MODE_ENABLE 0x01
+#define LED_MPP_MODE_OUTPUT 0x10
+#define LED_MPP_MODE_DISABLE 0x00
+#define LED_MPP_EN_ENABLE 0x80
+#define LED_MPP_EN_DISABLE 0x00
+
+#define MPP_SOURCE_DTEST1 0x08
+
+#define GPIO_MAX_LEVEL LED_FULL
+#define LED_GPIO_MODE_CTRL(base) (base + 0x40)
+#define LED_GPIO_VIN_CTRL(base) (base + 0x41)
+#define LED_GPIO_EN_CTRL(base) (base + 0x46)
+
+#define LED_GPIO_VIN_CTRL_DEFAULT 0
+#define LED_GPIO_SOURCE_SEL_DEFAULT LED_GPIO_MODE_ENABLE
+
+#define LED_GPIO_MODE_MASK 0x3F
+#define LED_GPIO_VIN_MASK 0x0F
+#define LED_GPIO_EN_MASK 0x80
+#define LED_GPIO_SRC_MASK 0x0F
+#define LED_GPIO_MODE_CTRL_MASK 0x30
+
+#define LED_GPIO_MODE_ENABLE 0x01
+#define LED_GPIO_MODE_DISABLE 0x00
+#define LED_GPIO_MODE_OUTPUT 0x10
+#define LED_GPIO_EN_ENABLE 0x80
+#define LED_GPIO_EN_DISABLE 0x00
+
+#define KPDBL_MAX_LEVEL LED_FULL
+#define KPDBL_ROW_SRC_SEL(base) (base + 0x40)
+#define KPDBL_ENABLE(base) (base + 0x46)
+#define KPDBL_ROW_SRC(base) (base + 0xE5)
+
+#define KPDBL_ROW_SRC_SEL_VAL_MASK 0x0F
+#define KPDBL_ROW_SCAN_EN_MASK 0x80
+#define KPDBL_ROW_SCAN_VAL_MASK 0x0F
+#define KPDBL_ROW_SCAN_EN_SHIFT 7
+#define KPDBL_MODULE_EN 0x80
+#define KPDBL_MODULE_DIS 0x00
+#define KPDBL_MODULE_EN_MASK 0x80
+#define NUM_KPDBL_LEDS 4
+#define KPDBL_MASTER_BIT_INDEX 0
+
+/**
+ * enum qpnp_leds - QPNP supported led ids
+ * @QPNP_ID_WLED - White led backlight
+ */
+enum qpnp_leds {
+ QPNP_ID_WLED = 0,
+ QPNP_ID_FLASH1_LED0,
+ QPNP_ID_FLASH1_LED1,
+ QPNP_ID_RGB_RED,
+ QPNP_ID_RGB_GREEN,
+ QPNP_ID_RGB_BLUE,
+ QPNP_ID_LED_MPP,
+ QPNP_ID_KPDBL,
+ QPNP_ID_LED_GPIO,
+ QPNP_ID_MAX,
+};
+
+/* current boost limit */
+enum wled_current_boost_limit {
+ WLED_CURR_LIMIT_105mA,
+ WLED_CURR_LIMIT_385mA,
+ WLED_CURR_LIMIT_525mA,
+ WLED_CURR_LIMIT_805mA,
+ WLED_CURR_LIMIT_980mA,
+ WLED_CURR_LIMIT_1260mA,
+ WLED_CURR_LIMIT_1400mA,
+ WLED_CURR_LIMIT_1680mA,
+};
+
+/* over voltage protection threshold */
+enum wled_ovp_threshold {
+ WLED_OVP_35V,
+ WLED_OVP_32V,
+ WLED_OVP_29V,
+ WLED_OVP_27V,
+};
+
+enum flash_headroom {
+ HEADROOM_250mV = 0,
+ HEADROOM_300mV,
+ HEADROOM_400mV,
+ HEADROOM_500mV,
+};
+
+enum flash_startup_dly {
+ DELAY_10us = 0,
+ DELAY_32us,
+ DELAY_64us,
+ DELAY_128us,
+};
+
+enum led_mode {
+ PWM_MODE = 0,
+ LPG_MODE,
+ MANUAL_MODE,
+};
+
+static u8 wled_debug_regs[] = {
+ /* brightness registers */
+ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45,
+ /* common registers */
+ 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
+ /* LED1 */
+ 0x60, 0x61, 0x62, 0x63, 0x66,
+ /* LED2 */
+ 0x70, 0x71, 0x72, 0x73, 0x76,
+ /* LED3 */
+ 0x80, 0x81, 0x82, 0x83, 0x86,
+};
+
+static u8 flash_debug_regs[] = {
+ 0x40, 0x41, 0x42, 0x43, 0x44, 0x48, 0x49, 0x4b, 0x4c,
+ 0x4f, 0x46, 0x47,
+};
+
+static u8 rgb_pwm_debug_regs[] = {
+ 0x45, 0x46, 0x47,
+};
+
+static u8 mpp_debug_regs[] = {
+ 0x40, 0x41, 0x42, 0x45, 0x46, 0x4c,
+};
+
+static u8 kpdbl_debug_regs[] = {
+ 0x40, 0x46, 0xb1, 0xb3, 0xb4, 0xe5,
+};
+
+static u8 gpio_debug_regs[] = {
+ 0x40, 0x41, 0x42, 0x45, 0x46,
+};
+
+/**
+ * pwm_config_data - pwm configuration data
+ * @lut_params - lut parameters to be used by pwm driver
+ * @pwm_device - pwm device
+ * @pwm_period_us - period for pwm, in us
+ * @mode - mode the led operates in
+ * @old_duty_pcts - storage for duty pcts that may need to be reused
+ * @default_mode - default mode of LED as set in device tree
+ * @use_blink - use blink sysfs entry
+ * @blinking - device is currently blinking w/LPG mode
+ */
+struct pwm_config_data {
+ struct lut_params lut_params;
+ struct pwm_device *pwm_dev;
+ u32 pwm_period_us;
+ struct pwm_duty_cycles *duty_cycles;
+ int *old_duty_pcts;
+ u8 mode;
+ u8 default_mode;
+ bool pwm_enabled;
+ bool use_blink;
+ bool blinking;
+};
+
+/**
+ * wled_config_data - wled configuration data
+ * @num_strings - number of wled strings to be configured
+ * @num_physical_strings - physical number of strings supported
+ * @ovp_val - over voltage protection threshold
+ * @boost_curr_lim - boot current limit
+ * @cp_select - high pole capacitance
+ * @ctrl_delay_us - delay in activation of led
+ * @dig_mod_gen_en - digital module generator
+ * @cs_out_en - current sink output enable
+ * @op_fdbck - selection of output as feedback for the boost
+ */
+struct wled_config_data {
+ u8 num_strings;
+ u8 num_physical_strings;
+ u8 ovp_val;
+ u8 boost_curr_lim;
+ u8 cp_select;
+ u8 ctrl_delay_us;
+ u8 switch_freq;
+ u8 op_fdbck;
+ u8 pmic_version;
+ bool dig_mod_gen_en;
+ bool cs_out_en;
+};
+
+/**
+ * mpp_config_data - mpp configuration data
+ * @pwm_cfg - device pwm configuration
+ * @current_setting - current setting, 5ma-40ma in 5ma increments
+ * @source_sel - source selection
+ * @mode_ctrl - mode control
+ * @vin_ctrl - input control
+ * @min_brightness - minimum brightness supported
+ * @pwm_mode - pwm mode in use
+ * @max_uV - maximum regulator voltage
+ * @min_uV - minimum regulator voltage
+ * @mpp_reg - regulator to power mpp based LED
+ * @enable - flag indicating LED on or off
+ */
+struct mpp_config_data {
+ struct pwm_config_data *pwm_cfg;
+ u8 current_setting;
+ u8 source_sel;
+ u8 mode_ctrl;
+ u8 vin_ctrl;
+ u8 min_brightness;
+ u8 pwm_mode;
+ u32 max_uV;
+ u32 min_uV;
+ struct regulator *mpp_reg;
+ bool enable;
+};
+
+/**
+ * flash_config_data - flash configuration data
+ * @current_prgm - current to be programmed, scaled by max level
+ * @clamp_curr - clamp current to use
+ * @headroom - headroom value to use
+ * @duration - duration of the flash
+ * @enable_module - enable address for particular flash
+ * @trigger_flash - trigger flash
+ * @startup_dly - startup delay for flash
+ * @strobe_type - select between sw and hw strobe
+ * @peripheral_subtype - module peripheral subtype
+ * @current_addr - address to write for current
+ * @second_addr - address of secondary flash to be written
+ * @safety_timer - enable safety timer or watchdog timer
+ * @torch_enable - enable flash LED torch mode
+ * @flash_reg_get - flash regulator attached or not
+ * @flash_wa_reg_get - workaround regulator attached or not
+ * @flash_on - flash status, on or off
+ * @torch_on - torch status, on or off
+ * @vreg_ok - specifies strobe type, sw or hw
+ * @no_smbb_support - specifies if smbb boost is not required and there is a
+ single regulator for both flash and torch
+ * @flash_boost_reg - boost regulator for flash
+ * @torch_boost_reg - boost regulator for torch
+ * @flash_wa_reg - flash regulator for wa
+ */
+struct flash_config_data {
+ u8 current_prgm;
+ u8 clamp_curr;
+ u8 headroom;
+ u8 duration;
+ u8 enable_module;
+ u8 trigger_flash;
+ u8 startup_dly;
+ u8 strobe_type;
+ u8 peripheral_subtype;
+ u16 current_addr;
+ u16 second_addr;
+ bool safety_timer;
+ bool torch_enable;
+ bool flash_reg_get;
+ bool flash_wa_reg_get;
+ bool flash_on;
+ bool torch_on;
+ bool vreg_ok;
+ bool no_smbb_support;
+ struct regulator *flash_boost_reg;
+ struct regulator *torch_boost_reg;
+ struct regulator *flash_wa_reg;
+};
+
+/**
+ * kpdbl_config_data - kpdbl configuration data
+ * @pwm_cfg - device pwm configuration
+ * @mode - running mode: pwm or lut
+ * @row_id - row id of the led
+ * @row_src_vbst - 0 for vph_pwr and 1 for vbst
+ * @row_src_en - enable row source
+ * @always_on - always on row
+ * @lut_params - lut parameters to be used by pwm driver
+ * @duty_cycles - duty cycles for lut
+ * @pwm_mode - pwm mode in use
+ */
+struct kpdbl_config_data {
+ struct pwm_config_data *pwm_cfg;
+ u32 row_id;
+ bool row_src_vbst;
+ bool row_src_en;
+ bool always_on;
+ struct pwm_duty_cycles *duty_cycles;
+ struct lut_params lut_params;
+ u8 pwm_mode;
+};
+
+/**
+ * rgb_config_data - rgb configuration data
+ * @pwm_cfg - device pwm configuration
+ * @enable - bits to enable led
+ */
+struct rgb_config_data {
+ struct pwm_config_data *pwm_cfg;
+ u8 enable;
+};
+
+/**
+ * gpio_config_data - gpio configuration data
+ * @source_sel - source selection
+ * @mode_ctrl - mode control
+ * @vin_ctrl - input control
+ * @enable - flag indicating LED on or off
+ */
+struct gpio_config_data {
+ u8 source_sel;
+ u8 mode_ctrl;
+ u8 vin_ctrl;
+ bool enable;
+};
+
+/**
+ * struct qpnp_led_data - internal led data structure
+ * @led_classdev - led class device
+ * @delayed_work - delayed work for turning off the LED
+ * @workqueue - dedicated workqueue to handle concurrency
+ * @work - workqueue for led
+ * @id - led index
+ * @base_reg - base register given in device tree
+ * @lock - to protect the transactions
+ * @reg - cached value of led register
+ * @num_leds - number of leds in the module
+ * @max_current - maximum current supported by LED
+ * @default_on - true: default state max, false, default state 0
+ * @turn_off_delay_ms - number of msec before turning off the LED
+ */
+struct qpnp_led_data {
+ struct led_classdev cdev;
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ struct delayed_work dwork;
+ struct workqueue_struct *workqueue;
+ struct work_struct work;
+ int id;
+ u16 base;
+ u8 reg;
+ u8 num_leds;
+ struct mutex lock;
+ struct wled_config_data *wled_cfg;
+ struct flash_config_data *flash_cfg;
+ struct kpdbl_config_data *kpdbl_cfg;
+ struct rgb_config_data *rgb_cfg;
+ struct mpp_config_data *mpp_cfg;
+ struct gpio_config_data *gpio_cfg;
+ int max_current;
+ bool default_on;
+ bool in_order_command_processing;
+ int turn_off_delay_ms;
+};
+
+static DEFINE_MUTEX(flash_lock);
+static struct pwm_device *kpdbl_master;
+static u32 kpdbl_master_period_us;
+DECLARE_BITMAP(kpdbl_leds_in_use, NUM_KPDBL_LEDS);
+static bool is_kpdbl_master_turn_on;
+
+static int
+qpnp_led_masked_write(struct qpnp_led_data *led, u16 addr, u8 mask, u8 val)
+{
+ int rc;
+
+ rc = regmap_update_bits(led->regmap, addr, mask, val);
+ if (rc)
+ dev_err(&led->pdev->dev,
+ "Unable to regmap_update_bits to addr=%x, rc(%d)\n",
+ addr, rc);
+ return rc;
+}
+
+static void qpnp_dump_regs(struct qpnp_led_data *led, u8 regs[], u8 array_size)
+{
+ int i;
+ u8 val;
+
+ pr_debug("===== %s LED register dump start =====\n", led->cdev.name);
+ for (i = 0; i < array_size; i++) {
+ regmap_bulk_read(led->regmap, led->base + regs[i], &val,
+ sizeof(val));
+ pr_debug("%s: 0x%x = 0x%x\n", led->cdev.name,
+ led->base + regs[i], val);
+ }
+ pr_debug("===== %s LED register dump end =====\n", led->cdev.name);
+}
+
+static int qpnp_wled_sync(struct qpnp_led_data *led)
+{
+ int rc;
+ u8 val;
+
+ /* sync */
+ val = WLED_SYNC_VAL;
+ rc = regmap_write(led->regmap, WLED_SYNC_REG(led->base), val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "WLED set sync reg failed(%d)\n", rc);
+ return rc;
+ }
+
+ val = WLED_SYNC_RESET_VAL;
+ rc = regmap_write(led->regmap, WLED_SYNC_REG(led->base), val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "WLED reset sync reg failed(%d)\n", rc);
+ return rc;
+ }
+ return 0;
+}
+
+static int qpnp_wled_set(struct qpnp_led_data *led)
+{
+ int rc, duty, level, tries = 0;
+ u8 val, i, num_wled_strings;
+ uint sink_val, ilim_val, ovp_val;
+
+ num_wled_strings = led->wled_cfg->num_strings;
+
+ level = led->cdev.brightness;
+
+ if (level > WLED_MAX_LEVEL)
+ level = WLED_MAX_LEVEL;
+ if (level == 0) {
+ for (i = 0; i < num_wled_strings; i++) {
+ rc = qpnp_led_masked_write(led,
+ WLED_FULL_SCALE_REG(led->base, i),
+ WLED_MAX_CURR_MASK, WLED_NO_CURRENT);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Write max current failure (%d)\n",
+ rc);
+ return rc;
+ }
+ }
+
+ rc = qpnp_wled_sync(led);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "WLED sync failed(%d)\n", rc);
+ return rc;
+ }
+
+ rc = regmap_read(led->regmap, WLED_CURR_SINK_REG(led->base),
+ &sink_val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "WLED read sink reg failed(%d)\n", rc);
+ return rc;
+ }
+
+ if (led->wled_cfg->pmic_version == PMIC_VER_8026) {
+ val = WLED_DISABLE_ALL_SINKS;
+ rc = regmap_write(led->regmap,
+ WLED_CURR_SINK_REG(led->base), val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "WLED write sink reg failed(%d)\n", rc);
+ return rc;
+ }
+
+ usleep_range(WLED_OVP_DELAY, WLED_OVP_DELAY + 10);
+ } else if (led->wled_cfg->pmic_version == PMIC_VER_8941) {
+ if (led->wled_cfg->num_physical_strings <=
+ WLED_THREE_STRINGS) {
+ val = WLED_DISABLE_1_2_SINKS;
+ rc = regmap_write(led->regmap,
+ WLED_CURR_SINK_REG(led->base),
+ val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "WLED write sink reg failed");
+ return rc;
+ }
+
+ rc = regmap_read(led->regmap,
+ WLED_BOOST_LIMIT_REG(led->base),
+ &ilim_val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Unable to read boost reg");
+ }
+ val = WLED_SET_ILIM_CODE;
+ rc = regmap_write(led->regmap,
+ WLED_BOOST_LIMIT_REG(led->base),
+ val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "WLED write sink reg failed");
+ return rc;
+ }
+ usleep_range(WLED_OVP_DELAY,
+ WLED_OVP_DELAY + 10);
+ } else {
+ val = WLED_DISABLE_ALL_SINKS;
+ rc = regmap_write(led->regmap,
+ WLED_CURR_SINK_REG(led->base),
+ val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "WLED write sink reg failed");
+ return rc;
+ }
+
+ msleep(WLED_OVP_DELAY_INT);
+ while (tries < WLED_MAX_TRIES) {
+ rc = regmap_read(led->regmap,
+ WLED_OVP_INT_STATUS(led->base),
+ &ovp_val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Unable to read boost reg");
+ }
+
+ if (ovp_val & WLED_OVP_INT_MASK)
+ break;
+
+ msleep(WLED_OVP_DELAY_LOOP);
+ tries++;
+ }
+ usleep_range(WLED_OVP_DELAY,
+ WLED_OVP_DELAY + 10);
+ }
+ }
+
+ val = WLED_BOOST_OFF;
+ rc = regmap_write(led->regmap, WLED_MOD_CTRL_REG(led->base),
+ val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "WLED write ctrl reg failed(%d)\n", rc);
+ return rc;
+ }
+
+ for (i = 0; i < num_wled_strings; i++) {
+ rc = qpnp_led_masked_write(led,
+ WLED_FULL_SCALE_REG(led->base, i),
+ WLED_MAX_CURR_MASK, (u8)led->max_current);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Write max current failure (%d)\n",
+ rc);
+ return rc;
+ }
+ }
+
+ rc = qpnp_wled_sync(led);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "WLED sync failed(%d)\n", rc);
+ return rc;
+ }
+
+ if (led->wled_cfg->pmic_version == PMIC_VER_8941) {
+ if (led->wled_cfg->num_physical_strings <=
+ WLED_THREE_STRINGS) {
+ rc = regmap_write(led->regmap,
+ WLED_BOOST_LIMIT_REG(led->base),
+ ilim_val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "WLED write sink reg failed");
+ return rc;
+ }
+ } else {
+ /* restore OVP to original value */
+ rc = regmap_write(led->regmap,
+ WLED_OVP_CFG_REG(led->base),
+ *&led->wled_cfg->ovp_val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "WLED write sink reg failed");
+ return rc;
+ }
+ }
+ }
+
+ /* re-enable all sinks */
+ rc = regmap_write(led->regmap, WLED_CURR_SINK_REG(led->base),
+ sink_val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "WLED write sink reg failed(%d)\n", rc);
+ return rc;
+ }
+
+ } else {
+ val = WLED_BOOST_ON;
+ rc = regmap_write(led->regmap, WLED_MOD_CTRL_REG(led->base),
+ val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "WLED write ctrl reg failed(%d)\n", rc);
+ return rc;
+ }
+ }
+
+ duty = (WLED_MAX_DUTY_CYCLE * level) / WLED_MAX_LEVEL;
+
+ /* program brightness control registers */
+ for (i = 0; i < num_wled_strings; i++) {
+ rc = qpnp_led_masked_write(led,
+ WLED_BRIGHTNESS_CNTL_MSB(led->base, i), WLED_MSB_MASK,
+ (duty >> WLED_8_BIT_SHFT) & WLED_4_BIT_MASK);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "WLED set brightness MSB failed(%d)\n", rc);
+ return rc;
+ }
+ val = duty & WLED_8_BIT_MASK;
+ rc = regmap_write(led->regmap,
+ WLED_BRIGHTNESS_CNTL_LSB(led->base, i), val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "WLED set brightness LSB failed(%d)\n", rc);
+ return rc;
+ }
+ }
+
+ rc = qpnp_wled_sync(led);
+ if (rc) {
+ dev_err(&led->pdev->dev, "WLED sync failed(%d)\n", rc);
+ return rc;
+ }
+ return 0;
+}
+
+static int qpnp_mpp_set(struct qpnp_led_data *led)
+{
+ int rc;
+ u8 val;
+ int duty_us, duty_ns, period_us;
+
+ if (led->cdev.brightness) {
+ if (led->mpp_cfg->mpp_reg && !led->mpp_cfg->enable) {
+ rc = regulator_set_voltage(led->mpp_cfg->mpp_reg,
+ led->mpp_cfg->min_uV,
+ led->mpp_cfg->max_uV);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Regulator voltage set failed rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = regulator_enable(led->mpp_cfg->mpp_reg);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Regulator enable failed(%d)\n", rc);
+ goto err_reg_enable;
+ }
+ }
+
+ led->mpp_cfg->enable = true;
+
+ if (led->cdev.brightness < led->mpp_cfg->min_brightness) {
+ dev_warn(&led->pdev->dev, "brightness is less than supported, set to minimum supported\n");
+ led->cdev.brightness = led->mpp_cfg->min_brightness;
+ }
+
+ if (led->mpp_cfg->pwm_mode != MANUAL_MODE) {
+ if (!led->mpp_cfg->pwm_cfg->blinking) {
+ led->mpp_cfg->pwm_cfg->mode =
+ led->mpp_cfg->pwm_cfg->default_mode;
+ led->mpp_cfg->pwm_mode =
+ led->mpp_cfg->pwm_cfg->default_mode;
+ }
+ }
+ if (led->mpp_cfg->pwm_mode == PWM_MODE) {
+ /*config pwm for brightness scaling*/
+ period_us = led->mpp_cfg->pwm_cfg->pwm_period_us;
+ if (period_us > INT_MAX / NSEC_PER_USEC) {
+ duty_us = (period_us * led->cdev.brightness) /
+ LED_FULL;
+ rc = pwm_config_us(
+ led->mpp_cfg->pwm_cfg->pwm_dev,
+ duty_us,
+ period_us);
+ } else {
+ duty_ns = ((period_us * NSEC_PER_USEC) /
+ LED_FULL) * led->cdev.brightness;
+ rc = pwm_config(
+ led->mpp_cfg->pwm_cfg->pwm_dev,
+ duty_ns,
+ period_us * NSEC_PER_USEC);
+ }
+ if (rc < 0) {
+ dev_err(&led->pdev->dev, "Failed to configure pwm for new values\n");
+ goto err_mpp_reg_write;
+ }
+ }
+
+ if (led->mpp_cfg->pwm_mode != MANUAL_MODE)
+ pwm_enable(led->mpp_cfg->pwm_cfg->pwm_dev);
+ else {
+ if (led->cdev.brightness < LED_MPP_CURRENT_MIN)
+ led->cdev.brightness = LED_MPP_CURRENT_MIN;
+ else {
+ /*
+ * PMIC supports LED intensity from 5mA - 40mA
+ * in steps of 5mA. Brightness is rounded to
+ * 5mA or nearest lower supported values
+ */
+ led->cdev.brightness /= LED_MPP_CURRENT_MIN;
+ led->cdev.brightness *= LED_MPP_CURRENT_MIN;
+ }
+
+ val = (led->cdev.brightness / LED_MPP_CURRENT_MIN) - 1;
+
+ rc = qpnp_led_masked_write(led,
+ LED_MPP_SINK_CTRL(led->base),
+ LED_MPP_SINK_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Failed to write sink control reg\n");
+ goto err_mpp_reg_write;
+ }
+ }
+
+ val = (led->mpp_cfg->source_sel & LED_MPP_SRC_MASK) |
+ (led->mpp_cfg->mode_ctrl & LED_MPP_MODE_CTRL_MASK);
+
+ rc = qpnp_led_masked_write(led,
+ LED_MPP_MODE_CTRL(led->base), LED_MPP_MODE_MASK,
+ val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Failed to write led mode reg\n");
+ goto err_mpp_reg_write;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ LED_MPP_EN_CTRL(led->base), LED_MPP_EN_MASK,
+ LED_MPP_EN_ENABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Failed to write led enable reg\n");
+ goto err_mpp_reg_write;
+ }
+ } else {
+ if (led->mpp_cfg->pwm_mode != MANUAL_MODE) {
+ led->mpp_cfg->pwm_cfg->mode =
+ led->mpp_cfg->pwm_cfg->default_mode;
+ led->mpp_cfg->pwm_mode =
+ led->mpp_cfg->pwm_cfg->default_mode;
+ pwm_disable(led->mpp_cfg->pwm_cfg->pwm_dev);
+ }
+ rc = qpnp_led_masked_write(led,
+ LED_MPP_MODE_CTRL(led->base),
+ LED_MPP_MODE_MASK,
+ LED_MPP_MODE_DISABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Failed to write led mode reg\n");
+ goto err_mpp_reg_write;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ LED_MPP_EN_CTRL(led->base),
+ LED_MPP_EN_MASK,
+ LED_MPP_EN_DISABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Failed to write led enable reg\n");
+ goto err_mpp_reg_write;
+ }
+
+ if (led->mpp_cfg->mpp_reg && led->mpp_cfg->enable) {
+ rc = regulator_disable(led->mpp_cfg->mpp_reg);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "MPP regulator disable failed(%d)\n",
+ rc);
+ return rc;
+ }
+
+ rc = regulator_set_voltage(led->mpp_cfg->mpp_reg,
+ 0, led->mpp_cfg->max_uV);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "MPP regulator voltage set failed(%d)\n",
+ rc);
+ return rc;
+ }
+ }
+
+ led->mpp_cfg->enable = false;
+ }
+
+ if (led->mpp_cfg->pwm_mode != MANUAL_MODE)
+ led->mpp_cfg->pwm_cfg->blinking = false;
+ qpnp_dump_regs(led, mpp_debug_regs, ARRAY_SIZE(mpp_debug_regs));
+
+ return 0;
+
+err_mpp_reg_write:
+ if (led->mpp_cfg->mpp_reg)
+ regulator_disable(led->mpp_cfg->mpp_reg);
+err_reg_enable:
+ if (led->mpp_cfg->mpp_reg)
+ regulator_set_voltage(led->mpp_cfg->mpp_reg, 0,
+ led->mpp_cfg->max_uV);
+ led->mpp_cfg->enable = false;
+
+ return rc;
+}
+
+static int qpnp_gpio_set(struct qpnp_led_data *led)
+{
+ int rc, val;
+
+ if (led->cdev.brightness) {
+ val = (led->gpio_cfg->source_sel & LED_GPIO_SRC_MASK) |
+ (led->gpio_cfg->mode_ctrl & LED_GPIO_MODE_CTRL_MASK);
+
+ rc = qpnp_led_masked_write(led,
+ LED_GPIO_MODE_CTRL(led->base),
+ LED_GPIO_MODE_MASK,
+ val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Failed to write led mode reg\n");
+ goto err_gpio_reg_write;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ LED_GPIO_EN_CTRL(led->base),
+ LED_GPIO_EN_MASK,
+ LED_GPIO_EN_ENABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Failed to write led enable reg\n");
+ goto err_gpio_reg_write;
+ }
+
+ led->gpio_cfg->enable = true;
+ } else {
+ rc = qpnp_led_masked_write(led,
+ LED_GPIO_MODE_CTRL(led->base),
+ LED_GPIO_MODE_MASK,
+ LED_GPIO_MODE_DISABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Failed to write led mode reg\n");
+ goto err_gpio_reg_write;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ LED_GPIO_EN_CTRL(led->base),
+ LED_GPIO_EN_MASK,
+ LED_GPIO_EN_DISABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Failed to write led enable reg\n");
+ goto err_gpio_reg_write;
+ }
+
+ led->gpio_cfg->enable = false;
+ }
+
+ qpnp_dump_regs(led, gpio_debug_regs, ARRAY_SIZE(gpio_debug_regs));
+
+ return 0;
+
+err_gpio_reg_write:
+ led->gpio_cfg->enable = false;
+
+ return rc;
+}
+
+static int qpnp_flash_regulator_operate(struct qpnp_led_data *led, bool on)
+{
+ int rc, i;
+ struct qpnp_led_data *led_array;
+ bool regulator_on = false;
+
+ led_array = dev_get_drvdata(&led->pdev->dev);
+ if (!led_array) {
+ dev_err(&led->pdev->dev, "Unable to get LED array\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < led->num_leds; i++)
+ regulator_on |= led_array[i].flash_cfg->flash_on;
+
+ if (!on)
+ goto regulator_turn_off;
+
+ if (!regulator_on && !led->flash_cfg->flash_on) {
+ for (i = 0; i < led->num_leds; i++) {
+ if (led_array[i].flash_cfg->flash_reg_get) {
+ if (led_array[i].flash_cfg->flash_wa_reg_get) {
+ rc = regulator_enable(
+ led_array[i].flash_cfg->
+ flash_wa_reg);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Flash wa regulator enable failed(%d)\n",
+ rc);
+ return rc;
+ }
+ }
+
+ rc = regulator_enable(
+ led_array[i].flash_cfg->flash_boost_reg);
+ if (rc) {
+ if (led_array[i].flash_cfg->
+ flash_wa_reg_get)
+ /*
+ * Disable flash wa regulator
+ * when flash boost regulator
+ * enable fails
+ */
+ regulator_disable(
+ led_array[i].flash_cfg->
+ flash_wa_reg);
+ dev_err(&led->pdev->dev, "Flash boost regulator enable failed(%d)\n",
+ rc);
+ return rc;
+ }
+ led->flash_cfg->flash_on = true;
+ }
+ break;
+ }
+ }
+
+ return 0;
+
+regulator_turn_off:
+ if (regulator_on && led->flash_cfg->flash_on) {
+ for (i = 0; i < led->num_leds; i++) {
+ if (led_array[i].flash_cfg->flash_reg_get) {
+ rc = qpnp_led_masked_write(led,
+ FLASH_ENABLE_CONTROL(led->base),
+ FLASH_ENABLE_MASK,
+ FLASH_DISABLE_ALL);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Enable reg write failed(%d)\n",
+ rc);
+ }
+
+ rc = regulator_disable(
+ led_array[i].flash_cfg->flash_boost_reg);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Flash boost regulator disable failed(%d)\n",
+ rc);
+ return rc;
+ }
+ if (led_array[i].flash_cfg->flash_wa_reg_get) {
+ rc = regulator_disable(
+ led_array[i].flash_cfg->
+ flash_wa_reg);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Flash_wa regulator disable failed(%d)\n",
+ rc);
+ return rc;
+ }
+ }
+ led->flash_cfg->flash_on = false;
+ }
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int qpnp_torch_regulator_operate(struct qpnp_led_data *led, bool on)
+{
+ int rc;
+
+ if (!on)
+ goto regulator_turn_off;
+
+ if (!led->flash_cfg->torch_on) {
+ rc = regulator_enable(led->flash_cfg->torch_boost_reg);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Regulator enable failed(%d)\n", rc);
+ return rc;
+ }
+ led->flash_cfg->torch_on = true;
+ }
+ return 0;
+
+regulator_turn_off:
+ if (led->flash_cfg->torch_on) {
+ rc = qpnp_led_masked_write(led, FLASH_ENABLE_CONTROL(led->base),
+ FLASH_ENABLE_MODULE_MASK, FLASH_DISABLE_ALL);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Enable reg write failed(%d)\n", rc);
+ }
+
+ rc = regulator_disable(led->flash_cfg->torch_boost_reg);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Regulator disable failed(%d)\n", rc);
+ return rc;
+ }
+ led->flash_cfg->torch_on = false;
+ }
+ return 0;
+}
+
+static int qpnp_flash_set(struct qpnp_led_data *led)
+{
+ int rc, error;
+ int val = led->cdev.brightness;
+
+ if (led->flash_cfg->torch_enable)
+ led->flash_cfg->current_prgm =
+ (val * TORCH_MAX_LEVEL / led->max_current);
+ else
+ led->flash_cfg->current_prgm =
+ (val * FLASH_MAX_LEVEL / led->max_current);
+
+ /* Set led current */
+ if (val > 0) {
+ if (led->flash_cfg->torch_enable) {
+ if (led->flash_cfg->peripheral_subtype ==
+ FLASH_SUBTYPE_DUAL) {
+ if (!led->flash_cfg->no_smbb_support)
+ rc = qpnp_torch_regulator_operate(led,
+ true);
+ else
+ rc = qpnp_flash_regulator_operate(led,
+ true);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Torch regulator operate failed(%d)\n",
+ rc);
+ return rc;
+ }
+ } else if (led->flash_cfg->peripheral_subtype ==
+ FLASH_SUBTYPE_SINGLE) {
+ rc = qpnp_flash_regulator_operate(led, true);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Flash regulator operate failed(%d)\n",
+ rc);
+ goto error_flash_set;
+ }
+ }
+
+ qpnp_led_masked_write(led, FLASH_MAX_CURR(led->base),
+ FLASH_CURRENT_MASK,
+ TORCH_MAX_LEVEL);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Max current reg write failed(%d)\n",
+ rc);
+ goto error_reg_write;
+ }
+
+ qpnp_led_masked_write(led,
+ FLASH_LED_TMR_CTRL(led->base),
+ FLASH_TMR_MASK,
+ FLASH_TMR_WATCHDOG);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Timer control reg write failed(%d)\n",
+ rc);
+ goto error_reg_write;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ led->flash_cfg->current_addr,
+ FLASH_CURRENT_MASK,
+ led->flash_cfg->current_prgm);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Current reg write failed(%d)\n", rc);
+ goto error_reg_write;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ led->flash_cfg->second_addr,
+ FLASH_CURRENT_MASK,
+ led->flash_cfg->current_prgm);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "2nd Current reg write failed(%d)\n",
+ rc);
+ goto error_reg_write;
+ }
+
+ qpnp_led_masked_write(led,
+ FLASH_WATCHDOG_TMR(led->base),
+ FLASH_WATCHDOG_MASK,
+ led->flash_cfg->duration);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Max current reg write failed(%d)\n",
+ rc);
+ goto error_reg_write;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_ENABLE_CONTROL(led->base),
+ FLASH_ENABLE_MASK,
+ led->flash_cfg->enable_module);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Enable reg write failed(%d)\n",
+ rc);
+ goto error_reg_write;
+ }
+
+ if (!led->flash_cfg->strobe_type)
+ led->flash_cfg->trigger_flash &=
+ ~FLASH_HW_SW_STROBE_SEL_MASK;
+ else
+ led->flash_cfg->trigger_flash |=
+ FLASH_HW_SW_STROBE_SEL_MASK;
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_LED_STROBE_CTRL(led->base),
+ led->flash_cfg->trigger_flash,
+ led->flash_cfg->trigger_flash);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "LED %d strobe reg write failed(%d)\n",
+ led->id, rc);
+ goto error_reg_write;
+ }
+ } else {
+ rc = qpnp_flash_regulator_operate(led, true);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Flash regulator operate failed(%d)\n",
+ rc);
+ goto error_flash_set;
+ }
+
+ qpnp_led_masked_write(led,
+ FLASH_LED_TMR_CTRL(led->base),
+ FLASH_TMR_MASK,
+ FLASH_TMR_SAFETY);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Timer control reg write failed(%d)\n",
+ rc);
+ goto error_reg_write;
+ }
+
+ /* Set flash safety timer */
+ rc = qpnp_led_masked_write(led,
+ FLASH_SAFETY_TIMER(led->base),
+ FLASH_SAFETY_TIMER_MASK,
+ led->flash_cfg->duration);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Safety timer reg write failed(%d)\n",
+ rc);
+ goto error_flash_set;
+ }
+
+ /* Set max current */
+ rc = qpnp_led_masked_write(led,
+ FLASH_MAX_CURR(led->base), FLASH_CURRENT_MASK,
+ FLASH_MAX_LEVEL);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Max current reg write failed(%d)\n",
+ rc);
+ goto error_flash_set;
+ }
+
+ /* Set clamp current */
+ rc = qpnp_led_masked_write(led,
+ FLASH_CLAMP_CURR(led->base),
+ FLASH_CURRENT_MASK,
+ led->flash_cfg->clamp_curr);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Clamp current reg write failed(%d)\n",
+ rc);
+ goto error_flash_set;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ led->flash_cfg->current_addr,
+ FLASH_CURRENT_MASK,
+ led->flash_cfg->current_prgm);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Current reg write failed(%d)\n", rc);
+ goto error_flash_set;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_ENABLE_CONTROL(led->base),
+ led->flash_cfg->enable_module,
+ led->flash_cfg->enable_module);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Enable reg write failed(%d)\n", rc);
+ goto error_flash_set;
+ }
+
+ /*
+ * Add 1ms delay for bharger enter stable state
+ */
+ usleep_range(FLASH_RAMP_UP_DELAY_US,
+ FLASH_RAMP_UP_DELAY_US + 10);
+
+ if (!led->flash_cfg->strobe_type)
+ led->flash_cfg->trigger_flash &=
+ ~FLASH_HW_SW_STROBE_SEL_MASK;
+ else
+ led->flash_cfg->trigger_flash |=
+ FLASH_HW_SW_STROBE_SEL_MASK;
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_LED_STROBE_CTRL(led->base),
+ led->flash_cfg->trigger_flash,
+ led->flash_cfg->trigger_flash);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "LED %d strobe reg write failed(%d)\n",
+ led->id, rc);
+ goto error_flash_set;
+ }
+ }
+ } else {
+ rc = qpnp_led_masked_write(led,
+ FLASH_LED_STROBE_CTRL(led->base),
+ led->flash_cfg->trigger_flash,
+ FLASH_DISABLE_ALL);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "LED %d flash write failed(%d)\n", led->id, rc);
+ if (led->flash_cfg->torch_enable)
+ goto error_torch_set;
+ else
+ goto error_flash_set;
+ }
+
+ if (led->flash_cfg->torch_enable) {
+ if (led->flash_cfg->peripheral_subtype ==
+ FLASH_SUBTYPE_DUAL) {
+ if (!led->flash_cfg->no_smbb_support)
+ rc = qpnp_torch_regulator_operate(led,
+ false);
+ else
+ rc = qpnp_flash_regulator_operate(led,
+ false);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Torch regulator operate failed(%d)\n",
+ rc);
+ return rc;
+ }
+ } else if (led->flash_cfg->peripheral_subtype ==
+ FLASH_SUBTYPE_SINGLE) {
+ rc = qpnp_flash_regulator_operate(led, false);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Flash regulator operate failed(%d)\n",
+ rc);
+ return rc;
+ }
+ }
+ } else {
+ /*
+ * Disable module after ramp down complete for stable
+ * behavior
+ */
+ usleep_range(FLASH_RAMP_UP_DELAY_US,
+ FLASH_RAMP_UP_DELAY_US + 10);
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_ENABLE_CONTROL(led->base),
+ led->flash_cfg->enable_module &
+ ~FLASH_ENABLE_MODULE_MASK,
+ FLASH_DISABLE_ALL);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Enable reg write failed(%d)\n", rc);
+ if (led->flash_cfg->torch_enable)
+ goto error_torch_set;
+ else
+ goto error_flash_set;
+ }
+
+ rc = qpnp_flash_regulator_operate(led, false);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Flash regulator operate failed(%d)\n",
+ rc);
+ return rc;
+ }
+ }
+ }
+
+ qpnp_dump_regs(led, flash_debug_regs, ARRAY_SIZE(flash_debug_regs));
+
+ return 0;
+
+error_reg_write:
+ if (led->flash_cfg->peripheral_subtype == FLASH_SUBTYPE_SINGLE)
+ goto error_flash_set;
+
+error_torch_set:
+ if (!led->flash_cfg->no_smbb_support)
+ error = qpnp_torch_regulator_operate(led, false);
+ else
+ error = qpnp_flash_regulator_operate(led, false);
+ if (error) {
+ dev_err(&led->pdev->dev,
+ "Torch regulator operate failed(%d)\n", rc);
+ return error;
+ }
+ return rc;
+
+error_flash_set:
+ error = qpnp_flash_regulator_operate(led, false);
+ if (error) {
+ dev_err(&led->pdev->dev,
+ "Flash regulator operate failed(%d)\n", rc);
+ return error;
+ }
+ return rc;
+}
+
+static int qpnp_kpdbl_set(struct qpnp_led_data *led)
+{
+ int rc;
+ int duty_us, duty_ns, period_us;
+
+ if (led->cdev.brightness) {
+ if (!led->kpdbl_cfg->pwm_cfg->blinking)
+ led->kpdbl_cfg->pwm_cfg->mode =
+ led->kpdbl_cfg->pwm_cfg->default_mode;
+
+ if (bitmap_empty(kpdbl_leds_in_use, NUM_KPDBL_LEDS)) {
+ rc = qpnp_led_masked_write(led, KPDBL_ENABLE(led->base),
+ KPDBL_MODULE_EN_MASK, KPDBL_MODULE_EN);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Enable reg write failed(%d)\n", rc);
+ return rc;
+ }
+ }
+
+ /* On some platforms, GPLED1 channel should always be enabled
+ * for the other GPLEDs 2/3/4 to glow. Before enabling GPLED
+ * 2/3/4, first check if GPLED1 is already enabled. If GPLED1
+ * channel is not enabled, then enable the GPLED1 channel but
+ * with a 0 brightness
+ */
+ if (!led->kpdbl_cfg->always_on &&
+ !test_bit(KPDBL_MASTER_BIT_INDEX, kpdbl_leds_in_use) &&
+ kpdbl_master) {
+ rc = pwm_config_us(kpdbl_master, 0,
+ kpdbl_master_period_us);
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "pwm config failed\n");
+ return rc;
+ }
+
+ rc = pwm_enable(kpdbl_master);
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "pwm enable failed\n");
+ return rc;
+ }
+ set_bit(KPDBL_MASTER_BIT_INDEX,
+ kpdbl_leds_in_use);
+ }
+
+ if (led->kpdbl_cfg->pwm_cfg->mode == PWM_MODE) {
+ period_us = led->kpdbl_cfg->pwm_cfg->pwm_period_us;
+ if (period_us > INT_MAX / NSEC_PER_USEC) {
+ duty_us = (period_us * led->cdev.brightness) /
+ KPDBL_MAX_LEVEL;
+ rc = pwm_config_us(
+ led->kpdbl_cfg->pwm_cfg->pwm_dev,
+ duty_us,
+ period_us);
+ } else {
+ duty_ns = ((period_us * NSEC_PER_USEC) /
+ KPDBL_MAX_LEVEL) * led->cdev.brightness;
+ rc = pwm_config(
+ led->kpdbl_cfg->pwm_cfg->pwm_dev,
+ duty_ns,
+ period_us * NSEC_PER_USEC);
+ }
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "pwm config failed\n");
+ return rc;
+ }
+ }
+
+ rc = pwm_enable(led->kpdbl_cfg->pwm_cfg->pwm_dev);
+ if (rc < 0) {
+ dev_err(&led->pdev->dev, "pwm enable failed\n");
+ return rc;
+ }
+
+ set_bit(led->kpdbl_cfg->row_id, kpdbl_leds_in_use);
+
+ /* is_kpdbl_master_turn_on will be set to true when GPLED1
+ * channel is enabled and has a valid brightness value
+ */
+ if (led->kpdbl_cfg->always_on)
+ is_kpdbl_master_turn_on = true;
+
+ } else {
+ led->kpdbl_cfg->pwm_cfg->mode =
+ led->kpdbl_cfg->pwm_cfg->default_mode;
+
+ /* Before disabling GPLED1, check if any other GPLED 2/3/4 is
+ * on. If any of the other GPLED 2/3/4 is on, then have the
+ * GPLED1 channel enabled with 0 brightness.
+ */
+ if (led->kpdbl_cfg->always_on) {
+ if (bitmap_weight(kpdbl_leds_in_use,
+ NUM_KPDBL_LEDS) > 1) {
+ rc = pwm_config_us(
+ led->kpdbl_cfg->pwm_cfg->pwm_dev, 0,
+ led->kpdbl_cfg->pwm_cfg->pwm_period_us);
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "pwm config failed\n");
+ return rc;
+ }
+
+ rc = pwm_enable(led->kpdbl_cfg->pwm_cfg->
+ pwm_dev);
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "pwm enable failed\n");
+ return rc;
+ }
+ } else {
+ if (kpdbl_master) {
+ pwm_disable(kpdbl_master);
+ clear_bit(KPDBL_MASTER_BIT_INDEX,
+ kpdbl_leds_in_use);
+ rc = qpnp_led_masked_write(
+ led, KPDBL_ENABLE(led->base),
+ KPDBL_MODULE_EN_MASK,
+ KPDBL_MODULE_DIS);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Failed to write led enable reg\n");
+ return rc;
+ }
+ }
+ }
+ is_kpdbl_master_turn_on = false;
+ } else {
+ pwm_disable(led->kpdbl_cfg->pwm_cfg->pwm_dev);
+ clear_bit(led->kpdbl_cfg->row_id, kpdbl_leds_in_use);
+ if (bitmap_weight(kpdbl_leds_in_use,
+ NUM_KPDBL_LEDS) == 1 && kpdbl_master &&
+ !is_kpdbl_master_turn_on) {
+ pwm_disable(kpdbl_master);
+ clear_bit(KPDBL_MASTER_BIT_INDEX,
+ kpdbl_leds_in_use);
+ rc = qpnp_led_masked_write(
+ led, KPDBL_ENABLE(led->base),
+ KPDBL_MODULE_EN_MASK, KPDBL_MODULE_DIS);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Failed to write led enable reg\n");
+ return rc;
+ }
+ is_kpdbl_master_turn_on = false;
+ }
+ }
+ }
+
+ led->kpdbl_cfg->pwm_cfg->blinking = false;
+
+ qpnp_dump_regs(led, kpdbl_debug_regs, ARRAY_SIZE(kpdbl_debug_regs));
+
+ return 0;
+}
+
+static int qpnp_rgb_set(struct qpnp_led_data *led)
+{
+ int rc;
+ int duty_us, duty_ns, period_us;
+
+ if (led->cdev.brightness) {
+ if (!led->rgb_cfg->pwm_cfg->blinking)
+ led->rgb_cfg->pwm_cfg->mode =
+ led->rgb_cfg->pwm_cfg->default_mode;
+ if (led->rgb_cfg->pwm_cfg->mode == PWM_MODE) {
+ period_us = led->rgb_cfg->pwm_cfg->pwm_period_us;
+ if (period_us > INT_MAX / NSEC_PER_USEC) {
+ duty_us = (period_us * led->cdev.brightness) /
+ LED_FULL;
+ rc = pwm_config_us(
+ led->rgb_cfg->pwm_cfg->pwm_dev,
+ duty_us,
+ period_us);
+ } else {
+ duty_ns = ((period_us * NSEC_PER_USEC) /
+ LED_FULL) * led->cdev.brightness;
+ rc = pwm_config(
+ led->rgb_cfg->pwm_cfg->pwm_dev,
+ duty_ns,
+ period_us * NSEC_PER_USEC);
+ }
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "pwm config failed\n");
+ return rc;
+ }
+ }
+ rc = qpnp_led_masked_write(led,
+ RGB_LED_EN_CTL(led->base),
+ led->rgb_cfg->enable, led->rgb_cfg->enable);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Failed to write led enable reg\n");
+ return rc;
+ }
+
+ if (led->rgb_cfg->pwm_cfg->pwm_enabled) {
+ pwm_disable(led->rgb_cfg->pwm_cfg->pwm_dev);
+ led->rgb_cfg->pwm_cfg->pwm_enabled = 0;
+ }
+
+ rc = pwm_enable(led->rgb_cfg->pwm_cfg->pwm_dev);
+ if (!rc)
+ led->rgb_cfg->pwm_cfg->pwm_enabled = 1;
+ } else {
+ led->rgb_cfg->pwm_cfg->mode =
+ led->rgb_cfg->pwm_cfg->default_mode;
+ pwm_disable(led->rgb_cfg->pwm_cfg->pwm_dev);
+ led->rgb_cfg->pwm_cfg->pwm_enabled = 0;
+ rc = qpnp_led_masked_write(led,
+ RGB_LED_EN_CTL(led->base),
+ led->rgb_cfg->enable, RGB_LED_DISABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Failed to write led enable reg\n");
+ return rc;
+ }
+ }
+
+ led->rgb_cfg->pwm_cfg->blinking = false;
+ qpnp_dump_regs(led, rgb_pwm_debug_regs, ARRAY_SIZE(rgb_pwm_debug_regs));
+
+ return 0;
+}
+
+static void qpnp_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct qpnp_led_data *led;
+
+ led = container_of(led_cdev, struct qpnp_led_data, cdev);
+ if (value < LED_OFF) {
+ dev_err(&led->pdev->dev, "Invalid brightness value\n");
+ return;
+ }
+
+ if (value > led->cdev.max_brightness)
+ value = led->cdev.max_brightness;
+
+ led->cdev.brightness = value;
+ if (led->in_order_command_processing)
+ queue_work(led->workqueue, &led->work);
+ else
+ schedule_work(&led->work);
+}
+
+static void __qpnp_led_work(struct qpnp_led_data *led,
+ enum led_brightness value)
+{
+ int rc;
+
+ if (led->id == QPNP_ID_FLASH1_LED0 || led->id == QPNP_ID_FLASH1_LED1)
+ mutex_lock(&flash_lock);
+ else
+ mutex_lock(&led->lock);
+
+ switch (led->id) {
+ case QPNP_ID_WLED:
+ rc = qpnp_wled_set(led);
+ if (rc < 0)
+ dev_err(&led->pdev->dev,
+ "WLED set brightness failed (%d)\n", rc);
+ break;
+ case QPNP_ID_FLASH1_LED0:
+ case QPNP_ID_FLASH1_LED1:
+ rc = qpnp_flash_set(led);
+ if (rc < 0)
+ dev_err(&led->pdev->dev,
+ "FLASH set brightness failed (%d)\n", rc);
+ break;
+ case QPNP_ID_RGB_RED:
+ case QPNP_ID_RGB_GREEN:
+ case QPNP_ID_RGB_BLUE:
+ rc = qpnp_rgb_set(led);
+ if (rc < 0)
+ dev_err(&led->pdev->dev,
+ "RGB set brightness failed (%d)\n", rc);
+ break;
+ case QPNP_ID_LED_MPP:
+ rc = qpnp_mpp_set(led);
+ if (rc < 0)
+ dev_err(&led->pdev->dev,
+ "MPP set brightness failed (%d)\n", rc);
+ break;
+ case QPNP_ID_LED_GPIO:
+ rc = qpnp_gpio_set(led);
+ if (rc < 0)
+ dev_err(&led->pdev->dev,
+ "GPIO set brightness failed (%d)\n",
+ rc);
+ break;
+ case QPNP_ID_KPDBL:
+ rc = qpnp_kpdbl_set(led);
+ if (rc < 0)
+ dev_err(&led->pdev->dev,
+ "KPDBL set brightness failed (%d)\n", rc);
+ break;
+ default:
+ dev_err(&led->pdev->dev, "Invalid LED(%d)\n", led->id);
+ break;
+ }
+ if (led->id == QPNP_ID_FLASH1_LED0 || led->id == QPNP_ID_FLASH1_LED1)
+ mutex_unlock(&flash_lock);
+ else
+ mutex_unlock(&led->lock);
+
+}
+
+static void qpnp_led_work(struct work_struct *work)
+{
+ struct qpnp_led_data *led = container_of(work,
+ struct qpnp_led_data, work);
+
+ __qpnp_led_work(led, led->cdev.brightness);
+}
+
+static int qpnp_led_set_max_brightness(struct qpnp_led_data *led)
+{
+ switch (led->id) {
+ case QPNP_ID_WLED:
+ led->cdev.max_brightness = WLED_MAX_LEVEL;
+ break;
+ case QPNP_ID_FLASH1_LED0:
+ case QPNP_ID_FLASH1_LED1:
+ led->cdev.max_brightness = led->max_current;
+ break;
+ case QPNP_ID_RGB_RED:
+ case QPNP_ID_RGB_GREEN:
+ case QPNP_ID_RGB_BLUE:
+ led->cdev.max_brightness = RGB_MAX_LEVEL;
+ break;
+ case QPNP_ID_LED_MPP:
+ if (led->mpp_cfg->pwm_mode == MANUAL_MODE)
+ led->cdev.max_brightness = led->max_current;
+ else
+ led->cdev.max_brightness = MPP_MAX_LEVEL;
+ break;
+ case QPNP_ID_LED_GPIO:
+ led->cdev.max_brightness = led->max_current;
+ break;
+ case QPNP_ID_KPDBL:
+ led->cdev.max_brightness = KPDBL_MAX_LEVEL;
+ break;
+ default:
+ dev_err(&led->pdev->dev, "Invalid LED(%d)\n", led->id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static enum led_brightness qpnp_led_get(struct led_classdev *led_cdev)
+{
+ struct qpnp_led_data *led;
+
+ led = container_of(led_cdev, struct qpnp_led_data, cdev);
+
+ return led->cdev.brightness;
+}
+
+static void qpnp_led_turn_off_delayed(struct work_struct *work)
+{
+ struct delayed_work *dwork = to_delayed_work(work);
+ struct qpnp_led_data *led
+ = container_of(dwork, struct qpnp_led_data, dwork);
+
+ led->cdev.brightness = LED_OFF;
+ qpnp_led_set(&led->cdev, led->cdev.brightness);
+}
+
+static void qpnp_led_turn_off(struct qpnp_led_data *led)
+{
+ INIT_DELAYED_WORK(&led->dwork, qpnp_led_turn_off_delayed);
+ schedule_delayed_work(&led->dwork,
+ msecs_to_jiffies(led->turn_off_delay_ms));
+}
+
+static int qpnp_wled_init(struct qpnp_led_data *led)
+{
+ int rc, i;
+ u8 num_wled_strings, val = 0;
+
+ num_wled_strings = led->wled_cfg->num_strings;
+
+ /* verify ranges */
+ if (led->wled_cfg->ovp_val > WLED_OVP_27V) {
+ dev_err(&led->pdev->dev, "Invalid ovp value\n");
+ return -EINVAL;
+ }
+
+ if (led->wled_cfg->boost_curr_lim > WLED_CURR_LIMIT_1680mA) {
+ dev_err(&led->pdev->dev, "Invalid boost current limit\n");
+ return -EINVAL;
+ }
+
+ if (led->wled_cfg->cp_select > WLED_CP_SELECT_MAX) {
+ dev_err(&led->pdev->dev, "Invalid pole capacitance\n");
+ return -EINVAL;
+ }
+
+ if (led->max_current > WLED_MAX_CURR) {
+ dev_err(&led->pdev->dev, "Invalid max current\n");
+ return -EINVAL;
+ }
+
+ if ((led->wled_cfg->ctrl_delay_us % WLED_CTL_DLY_STEP) ||
+ (led->wled_cfg->ctrl_delay_us > WLED_CTL_DLY_MAX)) {
+ dev_err(&led->pdev->dev, "Invalid control delay\n");
+ return -EINVAL;
+ }
+
+ /* program over voltage protection threshold */
+ rc = qpnp_led_masked_write(led, WLED_OVP_CFG_REG(led->base),
+ WLED_OVP_VAL_MASK,
+ (led->wled_cfg->ovp_val << WLED_OVP_VAL_BIT_SHFT));
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "WLED OVP reg write failed(%d)\n", rc);
+ return rc;
+ }
+
+ /* program current boost limit */
+ rc = qpnp_led_masked_write(led, WLED_BOOST_LIMIT_REG(led->base),
+ WLED_BOOST_LIMIT_MASK, led->wled_cfg->boost_curr_lim);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "WLED boost limit reg write failed(%d)\n", rc);
+ return rc;
+ }
+
+ /* program output feedback */
+ rc = qpnp_led_masked_write(led, WLED_FDBCK_CTRL_REG(led->base),
+ WLED_OP_FDBCK_MASK,
+ (led->wled_cfg->op_fdbck << WLED_OP_FDBCK_BIT_SHFT));
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "WLED fdbck ctrl reg write failed(%d)\n", rc);
+ return rc;
+ }
+
+ /* program switch frequency */
+ rc = qpnp_led_masked_write(led,
+ WLED_SWITCHING_FREQ_REG(led->base),
+ WLED_SWITCH_FREQ_MASK, led->wled_cfg->switch_freq);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "WLED switch freq reg write failed(%d)\n", rc);
+ return rc;
+ }
+
+ /* program current sink */
+ if (led->wled_cfg->cs_out_en) {
+ for (i = 0; i < led->wled_cfg->num_strings; i++)
+ val |= 1 << i;
+ rc = qpnp_led_masked_write(led, WLED_CURR_SINK_REG(led->base),
+ WLED_CURR_SINK_MASK, (val << WLED_CURR_SINK_SHFT));
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "WLED curr sink reg write failed(%d)\n", rc);
+ return rc;
+ }
+ }
+
+ /* program high pole capacitance */
+ rc = qpnp_led_masked_write(led, WLED_HIGH_POLE_CAP_REG(led->base),
+ WLED_CP_SELECT_MASK, led->wled_cfg->cp_select);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "WLED pole cap reg write failed(%d)\n", rc);
+ return rc;
+ }
+
+ /* program modulator, current mod src and cabc */
+ for (i = 0; i < num_wled_strings; i++) {
+ rc = qpnp_led_masked_write(led, WLED_MOD_EN_REG(led->base, i),
+ WLED_NO_MASK, WLED_EN_MASK);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "WLED mod enable reg write failed(%d)\n", rc);
+ return rc;
+ }
+
+ if (led->wled_cfg->dig_mod_gen_en) {
+ rc = qpnp_led_masked_write(led,
+ WLED_MOD_SRC_SEL_REG(led->base, i),
+ WLED_NO_MASK, WLED_USE_EXT_GEN_MOD_SRC);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "WLED dig mod en reg write failed(%d)\n", rc);
+ }
+ }
+
+ rc = qpnp_led_masked_write(led,
+ WLED_FULL_SCALE_REG(led->base, i), WLED_MAX_CURR_MASK,
+ (u8)led->max_current);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "WLED max current reg write failed(%d)\n", rc);
+ return rc;
+ }
+
+ }
+
+ /* Reset WLED enable register */
+ rc = qpnp_led_masked_write(led, WLED_MOD_CTRL_REG(led->base),
+ WLED_8_BIT_MASK, WLED_BOOST_OFF);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "WLED write ctrl reg failed(%d)\n", rc);
+ return rc;
+ }
+
+ /* dump wled registers */
+ qpnp_dump_regs(led, wled_debug_regs, ARRAY_SIZE(wled_debug_regs));
+
+ return 0;
+}
+
+static ssize_t led_mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct qpnp_led_data *led;
+ unsigned long state;
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ ssize_t ret = -EINVAL;
+
+ ret = kstrtoul(buf, 10, &state);
+ if (ret)
+ return ret;
+
+ led = container_of(led_cdev, struct qpnp_led_data, cdev);
+
+ /* '1' to enable torch mode; '0' to switch to flash mode */
+ if (state == 1)
+ led->flash_cfg->torch_enable = true;
+ else
+ led->flash_cfg->torch_enable = false;
+
+ return count;
+}
+
+static ssize_t led_strobe_type_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct qpnp_led_data *led;
+ unsigned long state;
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ ssize_t ret = -EINVAL;
+
+ ret = kstrtoul(buf, 10, &state);
+ if (ret)
+ return ret;
+
+ led = container_of(led_cdev, struct qpnp_led_data, cdev);
+
+ /* '0' for sw strobe; '1' for hw strobe */
+ if (state == 1)
+ led->flash_cfg->strobe_type = 1;
+ else
+ led->flash_cfg->strobe_type = 0;
+
+ return count;
+}
+
+static int qpnp_pwm_init(struct pwm_config_data *pwm_cfg,
+ struct platform_device *pdev,
+ const char *name)
+{
+ int rc, start_idx, idx_len, lut_max_size;
+
+ if (pwm_cfg->pwm_dev) {
+ if (pwm_cfg->mode == LPG_MODE) {
+ start_idx =
+ pwm_cfg->duty_cycles->start_idx;
+ idx_len =
+ pwm_cfg->duty_cycles->num_duty_pcts;
+
+ if (strnstr(name, "kpdbl", sizeof("kpdbl")))
+ lut_max_size = PWM_GPLED_LUT_MAX_SIZE;
+ else
+ lut_max_size = PWM_LUT_MAX_SIZE;
+
+ if (idx_len >= lut_max_size && start_idx) {
+ dev_err(&pdev->dev,
+ "Wrong LUT size or index\n");
+ return -EINVAL;
+ }
+
+ if ((start_idx + idx_len) > lut_max_size) {
+ dev_err(&pdev->dev, "Exceed LUT limit\n");
+ return -EINVAL;
+ }
+ rc = pwm_lut_config(pwm_cfg->pwm_dev,
+ pwm_cfg->pwm_period_us,
+ pwm_cfg->duty_cycles->duty_pcts,
+ pwm_cfg->lut_params);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "Failed to configure pwm LUT\n");
+ return rc;
+ }
+ }
+ } else {
+ dev_err(&pdev->dev, "Invalid PWM device\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static ssize_t pwm_us_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct qpnp_led_data *led;
+ u32 pwm_us;
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ ssize_t ret;
+ u32 previous_pwm_us;
+ struct pwm_config_data *pwm_cfg;
+
+ led = container_of(led_cdev, struct qpnp_led_data, cdev);
+
+ ret = kstrtou32(buf, 10, &pwm_us);
+ if (ret)
+ return ret;
+
+ switch (led->id) {
+ case QPNP_ID_LED_MPP:
+ pwm_cfg = led->mpp_cfg->pwm_cfg;
+ break;
+ case QPNP_ID_RGB_RED:
+ case QPNP_ID_RGB_GREEN:
+ case QPNP_ID_RGB_BLUE:
+ pwm_cfg = led->rgb_cfg->pwm_cfg;
+ break;
+ case QPNP_ID_KPDBL:
+ pwm_cfg = led->kpdbl_cfg->pwm_cfg;
+ break;
+ default:
+ dev_err(&led->pdev->dev, "Invalid LED id type for pwm_us\n");
+ return -EINVAL;
+ }
+
+ if (pwm_cfg->mode == LPG_MODE)
+ pwm_cfg->blinking = true;
+
+ previous_pwm_us = pwm_cfg->pwm_period_us;
+
+ pwm_cfg->pwm_period_us = pwm_us;
+ pwm_free(pwm_cfg->pwm_dev);
+ ret = qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+ if (ret) {
+ pwm_cfg->pwm_period_us = previous_pwm_us;
+ pwm_free(pwm_cfg->pwm_dev);
+ qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+ qpnp_led_set(&led->cdev, led->cdev.brightness);
+ dev_err(&led->pdev->dev,
+ "Failed to initialize pwm with new pwm_us value\n");
+ return ret;
+ }
+ qpnp_led_set(&led->cdev, led->cdev.brightness);
+ return count;
+}
+
+static ssize_t pause_lo_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct qpnp_led_data *led;
+ u32 pause_lo;
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ ssize_t ret;
+ u32 previous_pause_lo;
+ struct pwm_config_data *pwm_cfg;
+
+ ret = kstrtou32(buf, 10, &pause_lo);
+ if (ret)
+ return ret;
+ led = container_of(led_cdev, struct qpnp_led_data, cdev);
+
+ switch (led->id) {
+ case QPNP_ID_LED_MPP:
+ pwm_cfg = led->mpp_cfg->pwm_cfg;
+ break;
+ case QPNP_ID_RGB_RED:
+ case QPNP_ID_RGB_GREEN:
+ case QPNP_ID_RGB_BLUE:
+ pwm_cfg = led->rgb_cfg->pwm_cfg;
+ break;
+ case QPNP_ID_KPDBL:
+ pwm_cfg = led->kpdbl_cfg->pwm_cfg;
+ break;
+ default:
+ dev_err(&led->pdev->dev,
+ "Invalid LED id type for pause lo\n");
+ return -EINVAL;
+ }
+
+ if (pwm_cfg->mode == LPG_MODE)
+ pwm_cfg->blinking = true;
+
+ previous_pause_lo = pwm_cfg->lut_params.lut_pause_lo;
+
+ pwm_free(pwm_cfg->pwm_dev);
+ pwm_cfg->lut_params.lut_pause_lo = pause_lo;
+ ret = qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+ if (ret) {
+ pwm_cfg->lut_params.lut_pause_lo = previous_pause_lo;
+ pwm_free(pwm_cfg->pwm_dev);
+ qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+ qpnp_led_set(&led->cdev, led->cdev.brightness);
+ dev_err(&led->pdev->dev,
+ "Failed to initialize pwm with new pause lo value\n");
+ return ret;
+ }
+ qpnp_led_set(&led->cdev, led->cdev.brightness);
+ return count;
+}
+
+static ssize_t pause_hi_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct qpnp_led_data *led;
+ u32 pause_hi;
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ ssize_t ret;
+ u32 previous_pause_hi;
+ struct pwm_config_data *pwm_cfg;
+
+ ret = kstrtou32(buf, 10, &pause_hi);
+ if (ret)
+ return ret;
+ led = container_of(led_cdev, struct qpnp_led_data, cdev);
+
+ switch (led->id) {
+ case QPNP_ID_LED_MPP:
+ pwm_cfg = led->mpp_cfg->pwm_cfg;
+ break;
+ case QPNP_ID_RGB_RED:
+ case QPNP_ID_RGB_GREEN:
+ case QPNP_ID_RGB_BLUE:
+ pwm_cfg = led->rgb_cfg->pwm_cfg;
+ break;
+ case QPNP_ID_KPDBL:
+ pwm_cfg = led->kpdbl_cfg->pwm_cfg;
+ break;
+ default:
+ dev_err(&led->pdev->dev,
+ "Invalid LED id type for pause hi\n");
+ return -EINVAL;
+ }
+
+ if (pwm_cfg->mode == LPG_MODE)
+ pwm_cfg->blinking = true;
+
+ previous_pause_hi = pwm_cfg->lut_params.lut_pause_hi;
+
+ pwm_free(pwm_cfg->pwm_dev);
+ pwm_cfg->lut_params.lut_pause_hi = pause_hi;
+ ret = qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+ if (ret) {
+ pwm_cfg->lut_params.lut_pause_hi = previous_pause_hi;
+ pwm_free(pwm_cfg->pwm_dev);
+ qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+ qpnp_led_set(&led->cdev, led->cdev.brightness);
+ dev_err(&led->pdev->dev,
+ "Failed to initialize pwm with new pause hi value\n");
+ return ret;
+ }
+ qpnp_led_set(&led->cdev, led->cdev.brightness);
+ return count;
+}
+
+static ssize_t start_idx_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct qpnp_led_data *led;
+ u32 start_idx;
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ ssize_t ret;
+ u32 previous_start_idx;
+ struct pwm_config_data *pwm_cfg;
+
+ ret = kstrtou32(buf, 10, &start_idx);
+ if (ret)
+ return ret;
+ led = container_of(led_cdev, struct qpnp_led_data, cdev);
+
+ switch (led->id) {
+ case QPNP_ID_LED_MPP:
+ pwm_cfg = led->mpp_cfg->pwm_cfg;
+ break;
+ case QPNP_ID_RGB_RED:
+ case QPNP_ID_RGB_GREEN:
+ case QPNP_ID_RGB_BLUE:
+ pwm_cfg = led->rgb_cfg->pwm_cfg;
+ break;
+ case QPNP_ID_KPDBL:
+ pwm_cfg = led->kpdbl_cfg->pwm_cfg;
+ break;
+ default:
+ dev_err(&led->pdev->dev,
+ "Invalid LED id type for start idx\n");
+ return -EINVAL;
+ }
+
+ if (pwm_cfg->mode == LPG_MODE)
+ pwm_cfg->blinking = true;
+
+ previous_start_idx = pwm_cfg->duty_cycles->start_idx;
+ pwm_cfg->duty_cycles->start_idx = start_idx;
+ pwm_cfg->lut_params.start_idx = pwm_cfg->duty_cycles->start_idx;
+ pwm_free(pwm_cfg->pwm_dev);
+ ret = qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+ if (ret) {
+ pwm_cfg->duty_cycles->start_idx = previous_start_idx;
+ pwm_cfg->lut_params.start_idx = pwm_cfg->duty_cycles->start_idx;
+ pwm_free(pwm_cfg->pwm_dev);
+ qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+ qpnp_led_set(&led->cdev, led->cdev.brightness);
+ dev_err(&led->pdev->dev,
+ "Failed to initialize pwm with new start idx value\n");
+ return ret;
+ }
+ qpnp_led_set(&led->cdev, led->cdev.brightness);
+ return count;
+}
+
+static ssize_t ramp_step_ms_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct qpnp_led_data *led;
+ u32 ramp_step_ms;
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ ssize_t ret;
+ u32 previous_ramp_step_ms;
+ struct pwm_config_data *pwm_cfg;
+
+ ret = kstrtou32(buf, 10, &ramp_step_ms);
+ if (ret)
+ return ret;
+ led = container_of(led_cdev, struct qpnp_led_data, cdev);
+
+ switch (led->id) {
+ case QPNP_ID_LED_MPP:
+ pwm_cfg = led->mpp_cfg->pwm_cfg;
+ break;
+ case QPNP_ID_RGB_RED:
+ case QPNP_ID_RGB_GREEN:
+ case QPNP_ID_RGB_BLUE:
+ pwm_cfg = led->rgb_cfg->pwm_cfg;
+ break;
+ case QPNP_ID_KPDBL:
+ pwm_cfg = led->kpdbl_cfg->pwm_cfg;
+ break;
+ default:
+ dev_err(&led->pdev->dev,
+ "Invalid LED id type for ramp step\n");
+ return -EINVAL;
+ }
+
+ if (pwm_cfg->mode == LPG_MODE)
+ pwm_cfg->blinking = true;
+
+ previous_ramp_step_ms = pwm_cfg->lut_params.ramp_step_ms;
+
+ pwm_free(pwm_cfg->pwm_dev);
+ pwm_cfg->lut_params.ramp_step_ms = ramp_step_ms;
+ ret = qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+ if (ret) {
+ pwm_cfg->lut_params.ramp_step_ms = previous_ramp_step_ms;
+ pwm_free(pwm_cfg->pwm_dev);
+ qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+ qpnp_led_set(&led->cdev, led->cdev.brightness);
+ dev_err(&led->pdev->dev,
+ "Failed to initialize pwm with new ramp step value\n");
+ return ret;
+ }
+ qpnp_led_set(&led->cdev, led->cdev.brightness);
+ return count;
+}
+
+static ssize_t lut_flags_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct qpnp_led_data *led;
+ u32 lut_flags;
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ ssize_t ret;
+ u32 previous_lut_flags;
+ struct pwm_config_data *pwm_cfg;
+
+ ret = kstrtou32(buf, 10, &lut_flags);
+ if (ret)
+ return ret;
+ led = container_of(led_cdev, struct qpnp_led_data, cdev);
+
+ switch (led->id) {
+ case QPNP_ID_LED_MPP:
+ pwm_cfg = led->mpp_cfg->pwm_cfg;
+ break;
+ case QPNP_ID_RGB_RED:
+ case QPNP_ID_RGB_GREEN:
+ case QPNP_ID_RGB_BLUE:
+ pwm_cfg = led->rgb_cfg->pwm_cfg;
+ break;
+ case QPNP_ID_KPDBL:
+ pwm_cfg = led->kpdbl_cfg->pwm_cfg;
+ break;
+ default:
+ dev_err(&led->pdev->dev,
+ "Invalid LED id type for lut flags\n");
+ return -EINVAL;
+ }
+
+ if (pwm_cfg->mode == LPG_MODE)
+ pwm_cfg->blinking = true;
+
+ previous_lut_flags = pwm_cfg->lut_params.flags;
+
+ pwm_free(pwm_cfg->pwm_dev);
+ pwm_cfg->lut_params.flags = lut_flags;
+ ret = qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+ if (ret) {
+ pwm_cfg->lut_params.flags = previous_lut_flags;
+ pwm_free(pwm_cfg->pwm_dev);
+ qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+ qpnp_led_set(&led->cdev, led->cdev.brightness);
+ dev_err(&led->pdev->dev,
+ "Failed to initialize pwm with new lut flags value\n");
+ return ret;
+ }
+ qpnp_led_set(&led->cdev, led->cdev.brightness);
+ return count;
+}
+
+static ssize_t duty_pcts_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct qpnp_led_data *led;
+ int num_duty_pcts = 0;
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ char *buffer;
+ ssize_t ret;
+ int i = 0;
+ int max_duty_pcts;
+ struct pwm_config_data *pwm_cfg;
+ u32 previous_num_duty_pcts;
+ int value;
+ int *previous_duty_pcts;
+
+ led = container_of(led_cdev, struct qpnp_led_data, cdev);
+
+ switch (led->id) {
+ case QPNP_ID_LED_MPP:
+ pwm_cfg = led->mpp_cfg->pwm_cfg;
+ max_duty_pcts = PWM_LUT_MAX_SIZE;
+ break;
+ case QPNP_ID_RGB_RED:
+ case QPNP_ID_RGB_GREEN:
+ case QPNP_ID_RGB_BLUE:
+ pwm_cfg = led->rgb_cfg->pwm_cfg;
+ max_duty_pcts = PWM_LUT_MAX_SIZE;
+ break;
+ case QPNP_ID_KPDBL:
+ pwm_cfg = led->kpdbl_cfg->pwm_cfg;
+ max_duty_pcts = PWM_GPLED_LUT_MAX_SIZE;
+ break;
+ default:
+ dev_err(&led->pdev->dev,
+ "Invalid LED id type for duty pcts\n");
+ return -EINVAL;
+ }
+
+ if (pwm_cfg->mode == LPG_MODE)
+ pwm_cfg->blinking = true;
+
+ buffer = (char *)buf;
+
+ for (i = 0; i < max_duty_pcts; i++) {
+ if (buffer == NULL)
+ break;
+ ret = sscanf((const char *)buffer, "%u,%s", &value, buffer);
+ pwm_cfg->old_duty_pcts[i] = value;
+ num_duty_pcts++;
+ if (ret <= 1)
+ break;
+ }
+
+ if (num_duty_pcts >= max_duty_pcts) {
+ dev_err(&led->pdev->dev,
+ "Number of duty pcts given exceeds max (%d)\n",
+ max_duty_pcts);
+ return -EINVAL;
+ }
+
+ previous_num_duty_pcts = pwm_cfg->duty_cycles->num_duty_pcts;
+ previous_duty_pcts = pwm_cfg->duty_cycles->duty_pcts;
+
+ pwm_cfg->duty_cycles->num_duty_pcts = num_duty_pcts;
+ pwm_cfg->duty_cycles->duty_pcts = pwm_cfg->old_duty_pcts;
+ pwm_cfg->old_duty_pcts = previous_duty_pcts;
+ pwm_cfg->lut_params.idx_len = pwm_cfg->duty_cycles->num_duty_pcts;
+
+ pwm_free(pwm_cfg->pwm_dev);
+ ret = qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+ if (ret)
+ goto restore;
+
+ qpnp_led_set(&led->cdev, led->cdev.brightness);
+ return count;
+
+restore:
+ dev_err(&led->pdev->dev,
+ "Failed to initialize pwm with new duty pcts value\n");
+ pwm_cfg->duty_cycles->num_duty_pcts = previous_num_duty_pcts;
+ pwm_cfg->old_duty_pcts = pwm_cfg->duty_cycles->duty_pcts;
+ pwm_cfg->duty_cycles->duty_pcts = previous_duty_pcts;
+ pwm_cfg->lut_params.idx_len = pwm_cfg->duty_cycles->num_duty_pcts;
+ pwm_free(pwm_cfg->pwm_dev);
+ qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+ qpnp_led_set(&led->cdev, led->cdev.brightness);
+ return ret;
+}
+
+static void led_blink(struct qpnp_led_data *led,
+ struct pwm_config_data *pwm_cfg)
+{
+ int rc;
+
+ flush_work(&led->work);
+ mutex_lock(&led->lock);
+ if (pwm_cfg->use_blink) {
+ if (led->cdev.brightness) {
+ pwm_cfg->blinking = true;
+ if (led->id == QPNP_ID_LED_MPP)
+ led->mpp_cfg->pwm_mode = LPG_MODE;
+ else if (led->id == QPNP_ID_KPDBL)
+ led->kpdbl_cfg->pwm_mode = LPG_MODE;
+ pwm_cfg->mode = LPG_MODE;
+ } else {
+ pwm_cfg->blinking = false;
+ pwm_cfg->mode = pwm_cfg->default_mode;
+ if (led->id == QPNP_ID_LED_MPP)
+ led->mpp_cfg->pwm_mode = pwm_cfg->default_mode;
+ else if (led->id == QPNP_ID_KPDBL)
+ led->kpdbl_cfg->pwm_mode =
+ pwm_cfg->default_mode;
+ }
+ pwm_free(pwm_cfg->pwm_dev);
+ qpnp_pwm_init(pwm_cfg, led->pdev, led->cdev.name);
+ if (led->id == QPNP_ID_RGB_RED || led->id == QPNP_ID_RGB_GREEN
+ || led->id == QPNP_ID_RGB_BLUE) {
+ rc = qpnp_rgb_set(led);
+ if (rc < 0)
+ dev_err(&led->pdev->dev,
+ "RGB set brightness failed (%d)\n", rc);
+ } else if (led->id == QPNP_ID_LED_MPP) {
+ rc = qpnp_mpp_set(led);
+ if (rc < 0)
+ dev_err(&led->pdev->dev,
+ "MPP set brightness failed (%d)\n", rc);
+ } else if (led->id == QPNP_ID_KPDBL) {
+ rc = qpnp_kpdbl_set(led);
+ if (rc < 0)
+ dev_err(&led->pdev->dev,
+ "KPDBL set brightness failed (%d)\n", rc);
+ }
+ }
+ mutex_unlock(&led->lock);
+}
+
+static ssize_t blink_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct qpnp_led_data *led;
+ unsigned long blinking;
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ ssize_t ret = -EINVAL;
+
+ ret = kstrtoul(buf, 10, &blinking);
+ if (ret)
+ return ret;
+ led = container_of(led_cdev, struct qpnp_led_data, cdev);
+ led->cdev.brightness = blinking ? led->cdev.max_brightness : 0;
+
+ switch (led->id) {
+ case QPNP_ID_LED_MPP:
+ led_blink(led, led->mpp_cfg->pwm_cfg);
+ break;
+ case QPNP_ID_RGB_RED:
+ case QPNP_ID_RGB_GREEN:
+ case QPNP_ID_RGB_BLUE:
+ led_blink(led, led->rgb_cfg->pwm_cfg);
+ break;
+ case QPNP_ID_KPDBL:
+ led_blink(led, led->kpdbl_cfg->pwm_cfg);
+ break;
+ default:
+ dev_err(&led->pdev->dev, "Invalid LED id type for blink\n");
+ return -EINVAL;
+ }
+ return count;
+}
+
+static DEVICE_ATTR(led_mode, 0664, NULL, led_mode_store);
+static DEVICE_ATTR(strobe, 0664, NULL, led_strobe_type_store);
+static DEVICE_ATTR(pwm_us, 0664, NULL, pwm_us_store);
+static DEVICE_ATTR(pause_lo, 0664, NULL, pause_lo_store);
+static DEVICE_ATTR(pause_hi, 0664, NULL, pause_hi_store);
+static DEVICE_ATTR(start_idx, 0664, NULL, start_idx_store);
+static DEVICE_ATTR(ramp_step_ms, 0664, NULL, ramp_step_ms_store);
+static DEVICE_ATTR(lut_flags, 0664, NULL, lut_flags_store);
+static DEVICE_ATTR(duty_pcts, 0664, NULL, duty_pcts_store);
+static DEVICE_ATTR(blink, 0664, NULL, blink_store);
+
+static struct attribute *led_attrs[] = {
+ &dev_attr_led_mode.attr,
+ &dev_attr_strobe.attr,
+ NULL
+};
+
+static const struct attribute_group led_attr_group = {
+ .attrs = led_attrs,
+};
+
+static struct attribute *pwm_attrs[] = {
+ &dev_attr_pwm_us.attr,
+ NULL
+};
+
+static struct attribute *lpg_attrs[] = {
+ &dev_attr_pause_lo.attr,
+ &dev_attr_pause_hi.attr,
+ &dev_attr_start_idx.attr,
+ &dev_attr_ramp_step_ms.attr,
+ &dev_attr_lut_flags.attr,
+ &dev_attr_duty_pcts.attr,
+ NULL
+};
+
+static struct attribute *blink_attrs[] = {
+ &dev_attr_blink.attr,
+ NULL
+};
+
+static const struct attribute_group pwm_attr_group = {
+ .attrs = pwm_attrs,
+};
+
+static const struct attribute_group lpg_attr_group = {
+ .attrs = lpg_attrs,
+};
+
+static const struct attribute_group blink_attr_group = {
+ .attrs = blink_attrs,
+};
+
+static int qpnp_flash_init(struct qpnp_led_data *led)
+{
+ int rc;
+
+ led->flash_cfg->flash_on = false;
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_LED_STROBE_CTRL(led->base),
+ FLASH_STROBE_MASK, FLASH_DISABLE_ALL);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "LED %d flash write failed(%d)\n", led->id, rc);
+ return rc;
+ }
+
+ /* Disable flash LED module */
+ rc = qpnp_led_masked_write(led, FLASH_ENABLE_CONTROL(led->base),
+ FLASH_ENABLE_MASK, FLASH_DISABLE_ALL);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Enable reg write failed(%d)\n", rc);
+ return rc;
+ }
+
+ if (led->flash_cfg->torch_enable)
+ return 0;
+
+ /* Set headroom */
+ rc = qpnp_led_masked_write(led, FLASH_HEADROOM(led->base),
+ FLASH_HEADROOM_MASK, led->flash_cfg->headroom);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Headroom reg write failed(%d)\n", rc);
+ return rc;
+ }
+
+ /* Set startup delay */
+ rc = qpnp_led_masked_write(led,
+ FLASH_STARTUP_DELAY(led->base), FLASH_STARTUP_DLY_MASK,
+ led->flash_cfg->startup_dly);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Startup delay reg write failed(%d)\n", rc);
+ return rc;
+ }
+
+ /* Set timer control - safety or watchdog */
+ if (led->flash_cfg->safety_timer) {
+ rc = qpnp_led_masked_write(led,
+ FLASH_LED_TMR_CTRL(led->base),
+ FLASH_TMR_MASK, FLASH_TMR_SAFETY);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "LED timer ctrl reg write failed(%d)\n",
+ rc);
+ return rc;
+ }
+ }
+
+ /* Set Vreg force */
+ if (led->flash_cfg->vreg_ok)
+ rc = qpnp_led_masked_write(led, FLASH_VREG_OK_FORCE(led->base),
+ FLASH_VREG_MASK, FLASH_SW_VREG_OK);
+ else
+ rc = qpnp_led_masked_write(led, FLASH_VREG_OK_FORCE(led->base),
+ FLASH_VREG_MASK, FLASH_HW_VREG_OK);
+
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Vreg OK reg write failed(%d)\n", rc);
+ return rc;
+ }
+
+ /* Set self fault check */
+ rc = qpnp_led_masked_write(led, FLASH_FAULT_DETECT(led->base),
+ FLASH_FAULT_DETECT_MASK, FLASH_SELFCHECK_ENABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Fault detect reg write failed(%d)\n", rc);
+ return rc;
+ }
+
+ /* Set mask enable */
+ rc = qpnp_led_masked_write(led, FLASH_MASK_ENABLE(led->base),
+ FLASH_MASK_REG_MASK, FLASH_MASK_1);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Mask enable reg write failed(%d)\n", rc);
+ return rc;
+ }
+
+ /* Set current ramp */
+ rc = qpnp_led_masked_write(led, FLASH_CURRENT_RAMP(led->base),
+ FLASH_CURRENT_RAMP_MASK, FLASH_RAMP_STEP_27US);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Current ramp reg write failed(%d)\n", rc);
+ return rc;
+ }
+
+ led->flash_cfg->strobe_type = 0;
+
+ /* dump flash registers */
+ qpnp_dump_regs(led, flash_debug_regs, ARRAY_SIZE(flash_debug_regs));
+
+ return 0;
+}
+
+static int qpnp_kpdbl_init(struct qpnp_led_data *led)
+{
+ int rc;
+ uint val;
+
+ /* select row source - vbst or vph */
+ rc = regmap_read(led->regmap, KPDBL_ROW_SRC_SEL(led->base), &val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Unable to read from addr=%x, rc(%d)\n",
+ KPDBL_ROW_SRC_SEL(led->base), rc);
+ return rc;
+ }
+
+ if (led->kpdbl_cfg->row_src_vbst)
+ val |= 1 << led->kpdbl_cfg->row_id;
+ else
+ val &= ~(1 << led->kpdbl_cfg->row_id);
+
+ rc = regmap_write(led->regmap, KPDBL_ROW_SRC_SEL(led->base), val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Unable to read from addr=%x, rc(%d)\n",
+ KPDBL_ROW_SRC_SEL(led->base), rc);
+ return rc;
+ }
+
+ /* row source enable */
+ rc = regmap_read(led->regmap, KPDBL_ROW_SRC(led->base), &val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Unable to read from addr=%x, rc(%d)\n",
+ KPDBL_ROW_SRC(led->base), rc);
+ return rc;
+ }
+
+ if (led->kpdbl_cfg->row_src_en)
+ val |= KPDBL_ROW_SCAN_EN_MASK | (1 << led->kpdbl_cfg->row_id);
+ else
+ val &= ~(1 << led->kpdbl_cfg->row_id);
+
+ rc = regmap_write(led->regmap, KPDBL_ROW_SRC(led->base), val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Unable to write to addr=%x, rc(%d)\n",
+ KPDBL_ROW_SRC(led->base), rc);
+ return rc;
+ }
+
+ /* enable module */
+ rc = qpnp_led_masked_write(led, KPDBL_ENABLE(led->base),
+ KPDBL_MODULE_EN_MASK, KPDBL_MODULE_EN);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Enable module write failed(%d)\n", rc);
+ return rc;
+ }
+
+ rc = qpnp_pwm_init(led->kpdbl_cfg->pwm_cfg, led->pdev,
+ led->cdev.name);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Failed to initialize pwm\n");
+ return rc;
+ }
+
+ if (led->kpdbl_cfg->always_on) {
+ kpdbl_master = led->kpdbl_cfg->pwm_cfg->pwm_dev;
+ kpdbl_master_period_us = led->kpdbl_cfg->pwm_cfg->pwm_period_us;
+ }
+
+ /* dump kpdbl registers */
+ qpnp_dump_regs(led, kpdbl_debug_regs, ARRAY_SIZE(kpdbl_debug_regs));
+
+ return 0;
+}
+
+static int qpnp_rgb_init(struct qpnp_led_data *led)
+{
+ int rc;
+
+ rc = qpnp_led_masked_write(led, RGB_LED_SRC_SEL(led->base),
+ RGB_LED_SRC_MASK, RGB_LED_SOURCE_VPH_PWR);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Failed to write led source select register\n");
+ return rc;
+ }
+
+ rc = qpnp_pwm_init(led->rgb_cfg->pwm_cfg, led->pdev, led->cdev.name);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Failed to initialize pwm\n");
+ return rc;
+ }
+ /* Initialize led for use in auto trickle charging mode */
+ rc = qpnp_led_masked_write(led, RGB_LED_ATC_CTL(led->base),
+ led->rgb_cfg->enable, led->rgb_cfg->enable);
+
+ return 0;
+}
+
+static int qpnp_mpp_init(struct qpnp_led_data *led)
+{
+ int rc;
+ u8 val;
+
+
+ if (led->max_current < LED_MPP_CURRENT_MIN ||
+ led->max_current > LED_MPP_CURRENT_MAX) {
+ dev_err(&led->pdev->dev,
+ "max current for mpp is not valid\n");
+ return -EINVAL;
+ }
+
+ val = (led->mpp_cfg->current_setting / LED_MPP_CURRENT_PER_SETTING) - 1;
+
+ if (val < 0)
+ val = 0;
+
+ rc = qpnp_led_masked_write(led, LED_MPP_VIN_CTRL(led->base),
+ LED_MPP_VIN_MASK, led->mpp_cfg->vin_ctrl);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Failed to write led vin control reg\n");
+ return rc;
+ }
+
+ rc = qpnp_led_masked_write(led, LED_MPP_SINK_CTRL(led->base),
+ LED_MPP_SINK_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Failed to write sink control reg\n");
+ return rc;
+ }
+
+ if (led->mpp_cfg->pwm_mode != MANUAL_MODE) {
+ rc = qpnp_pwm_init(led->mpp_cfg->pwm_cfg, led->pdev,
+ led->cdev.name);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Failed to initialize pwm\n");
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+static int qpnp_gpio_init(struct qpnp_led_data *led)
+{
+ int rc;
+
+ rc = qpnp_led_masked_write(led, LED_GPIO_VIN_CTRL(led->base),
+ LED_GPIO_VIN_MASK, led->gpio_cfg->vin_ctrl);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Failed to write led vin control reg\n");
+ return rc;
+ }
+
+ return 0;
+}
+
+static int qpnp_led_initialize(struct qpnp_led_data *led)
+{
+ int rc = 0;
+
+ switch (led->id) {
+ case QPNP_ID_WLED:
+ rc = qpnp_wled_init(led);
+ if (rc)
+ dev_err(&led->pdev->dev,
+ "WLED initialize failed(%d)\n", rc);
+ break;
+ case QPNP_ID_FLASH1_LED0:
+ case QPNP_ID_FLASH1_LED1:
+ rc = qpnp_flash_init(led);
+ if (rc)
+ dev_err(&led->pdev->dev,
+ "FLASH initialize failed(%d)\n", rc);
+ break;
+ case QPNP_ID_RGB_RED:
+ case QPNP_ID_RGB_GREEN:
+ case QPNP_ID_RGB_BLUE:
+ rc = qpnp_rgb_init(led);
+ if (rc)
+ dev_err(&led->pdev->dev,
+ "RGB initialize failed(%d)\n", rc);
+ break;
+ case QPNP_ID_LED_MPP:
+ rc = qpnp_mpp_init(led);
+ if (rc)
+ dev_err(&led->pdev->dev,
+ "MPP initialize failed(%d)\n", rc);
+ break;
+ case QPNP_ID_LED_GPIO:
+ rc = qpnp_gpio_init(led);
+ if (rc)
+ dev_err(&led->pdev->dev,
+ "GPIO initialize failed(%d)\n", rc);
+ break;
+ case QPNP_ID_KPDBL:
+ rc = qpnp_kpdbl_init(led);
+ if (rc)
+ dev_err(&led->pdev->dev,
+ "KPDBL initialize failed(%d)\n", rc);
+ break;
+ default:
+ dev_err(&led->pdev->dev, "Invalid LED(%d)\n", led->id);
+ return -EINVAL;
+ }
+
+ return rc;
+}
+
+static int qpnp_get_common_configs(struct qpnp_led_data *led,
+ struct device_node *node)
+{
+ int rc;
+ u32 val;
+ const char *temp_string;
+
+ led->cdev.default_trigger = LED_TRIGGER_DEFAULT;
+ rc = of_property_read_string(node, "linux,default-trigger",
+ &temp_string);
+ if (!rc)
+ led->cdev.default_trigger = temp_string;
+ else if (rc != -EINVAL)
+ return rc;
+
+ led->default_on = false;
+ rc = of_property_read_string(node, "qcom,default-state",
+ &temp_string);
+ if (!rc) {
+ if (strcmp(temp_string, "on") == 0)
+ led->default_on = true;
+ } else if (rc != -EINVAL)
+ return rc;
+
+ led->turn_off_delay_ms = 0;
+ rc = of_property_read_u32(node, "qcom,turn-off-delay-ms", &val);
+ if (!rc)
+ led->turn_off_delay_ms = val;
+ else if (rc != -EINVAL)
+ return rc;
+
+ return 0;
+}
+
+/*
+ * Handlers for alternative sources of platform_data
+ */
+static int qpnp_get_config_wled(struct qpnp_led_data *led,
+ struct device_node *node)
+{
+ u32 val;
+ uint tmp;
+ int rc;
+
+ led->wled_cfg = devm_kzalloc(&led->pdev->dev,
+ sizeof(struct wled_config_data), GFP_KERNEL);
+ if (!led->wled_cfg)
+ return -ENOMEM;
+
+ rc = regmap_read(led->regmap, PMIC_VERSION_REG, &tmp);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Unable to read pmic ver, rc(%d)\n", rc);
+ }
+ led->wled_cfg->pmic_version = (u8)tmp;
+
+ led->wled_cfg->num_strings = WLED_DEFAULT_STRINGS;
+ rc = of_property_read_u32(node, "qcom,num-strings", &val);
+ if (!rc)
+ led->wled_cfg->num_strings = (u8) val;
+ else if (rc != -EINVAL)
+ return rc;
+
+ led->wled_cfg->num_physical_strings = led->wled_cfg->num_strings;
+ rc = of_property_read_u32(node, "qcom,num-physical-strings", &val);
+ if (!rc)
+ led->wled_cfg->num_physical_strings = (u8) val;
+ else if (rc != -EINVAL)
+ return rc;
+
+ led->wled_cfg->ovp_val = WLED_DEFAULT_OVP_VAL;
+ rc = of_property_read_u32(node, "qcom,ovp-val", &val);
+ if (!rc)
+ led->wled_cfg->ovp_val = (u8) val;
+ else if (rc != -EINVAL)
+ return rc;
+
+ led->wled_cfg->boost_curr_lim = WLED_BOOST_LIM_DEFAULT;
+ rc = of_property_read_u32(node, "qcom,boost-curr-lim", &val);
+ if (!rc)
+ led->wled_cfg->boost_curr_lim = (u8) val;
+ else if (rc != -EINVAL)
+ return rc;
+
+ led->wled_cfg->cp_select = WLED_CP_SEL_DEFAULT;
+ rc = of_property_read_u32(node, "qcom,cp-sel", &val);
+ if (!rc)
+ led->wled_cfg->cp_select = (u8) val;
+ else if (rc != -EINVAL)
+ return rc;
+
+ led->wled_cfg->ctrl_delay_us = WLED_CTRL_DLY_DEFAULT;
+ rc = of_property_read_u32(node, "qcom,ctrl-delay-us", &val);
+ if (!rc)
+ led->wled_cfg->ctrl_delay_us = (u8) val;
+ else if (rc != -EINVAL)
+ return rc;
+
+ led->wled_cfg->op_fdbck = WLED_OP_FDBCK_DEFAULT;
+ rc = of_property_read_u32(node, "qcom,op-fdbck", &val);
+ if (!rc)
+ led->wled_cfg->op_fdbck = (u8) val;
+ else if (rc != -EINVAL)
+ return rc;
+
+ led->wled_cfg->switch_freq = WLED_SWITCH_FREQ_DEFAULT;
+ rc = of_property_read_u32(node, "qcom,switch-freq", &val);
+ if (!rc)
+ led->wled_cfg->switch_freq = (u8) val;
+ else if (rc != -EINVAL)
+ return rc;
+
+ led->wled_cfg->dig_mod_gen_en =
+ of_property_read_bool(node, "qcom,dig-mod-gen-en");
+
+ led->wled_cfg->cs_out_en =
+ of_property_read_bool(node, "qcom,cs-out-en");
+
+ return 0;
+}
+
+static int qpnp_get_config_flash(struct qpnp_led_data *led,
+ struct device_node *node, bool *reg_set)
+{
+ int rc;
+ u32 val;
+ uint tmp;
+
+ led->flash_cfg = devm_kzalloc(&led->pdev->dev,
+ sizeof(struct flash_config_data), GFP_KERNEL);
+ if (!led->flash_cfg)
+ return -ENOMEM;
+
+ rc = regmap_read(led->regmap, FLASH_PERIPHERAL_SUBTYPE(led->base),
+ &tmp);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Unable to read from addr=%x, rc(%d)\n",
+ FLASH_PERIPHERAL_SUBTYPE(led->base), rc);
+ }
+ led->flash_cfg->peripheral_subtype = (u8)tmp;
+
+ led->flash_cfg->torch_enable =
+ of_property_read_bool(node, "qcom,torch-enable");
+
+ led->flash_cfg->no_smbb_support =
+ of_property_read_bool(node, "qcom,no-smbb-support");
+
+ if (of_find_property(of_get_parent(node), "flash-wa-supply",
+ NULL) && (!*reg_set)) {
+ led->flash_cfg->flash_wa_reg =
+ devm_regulator_get(&led->pdev->dev, "flash-wa");
+ if (IS_ERR_OR_NULL(led->flash_cfg->flash_wa_reg)) {
+ rc = PTR_ERR(led->flash_cfg->flash_wa_reg);
+ if (rc != EPROBE_DEFER) {
+ dev_err(&led->pdev->dev,
+ "Flash wa regulator get failed(%d)\n",
+ rc);
+ }
+ } else {
+ led->flash_cfg->flash_wa_reg_get = true;
+ }
+ }
+
+ if (led->id == QPNP_ID_FLASH1_LED0) {
+ led->flash_cfg->enable_module = FLASH_ENABLE_LED_0;
+ led->flash_cfg->current_addr = FLASH_LED_0_CURR(led->base);
+ led->flash_cfg->trigger_flash = FLASH_LED_0_OUTPUT;
+ if (!*reg_set) {
+ led->flash_cfg->flash_boost_reg =
+ regulator_get(&led->pdev->dev,
+ "flash-boost");
+ if (IS_ERR(led->flash_cfg->flash_boost_reg)) {
+ rc = PTR_ERR(led->flash_cfg->flash_boost_reg);
+ dev_err(&led->pdev->dev,
+ "Regulator get failed(%d)\n", rc);
+ goto error_get_flash_reg;
+ }
+ led->flash_cfg->flash_reg_get = true;
+ *reg_set = true;
+ } else
+ led->flash_cfg->flash_reg_get = false;
+
+ if (led->flash_cfg->torch_enable) {
+ led->flash_cfg->second_addr =
+ FLASH_LED_1_CURR(led->base);
+ }
+ } else if (led->id == QPNP_ID_FLASH1_LED1) {
+ led->flash_cfg->enable_module = FLASH_ENABLE_LED_1;
+ led->flash_cfg->current_addr = FLASH_LED_1_CURR(led->base);
+ led->flash_cfg->trigger_flash = FLASH_LED_1_OUTPUT;
+ if (!*reg_set) {
+ led->flash_cfg->flash_boost_reg =
+ regulator_get(&led->pdev->dev,
+ "flash-boost");
+ if (IS_ERR(led->flash_cfg->flash_boost_reg)) {
+ rc = PTR_ERR(led->flash_cfg->flash_boost_reg);
+ dev_err(&led->pdev->dev,
+ "Regulator get failed(%d)\n", rc);
+ goto error_get_flash_reg;
+ }
+ led->flash_cfg->flash_reg_get = true;
+ *reg_set = true;
+ } else
+ led->flash_cfg->flash_reg_get = false;
+
+ if (led->flash_cfg->torch_enable) {
+ led->flash_cfg->second_addr =
+ FLASH_LED_0_CURR(led->base);
+ }
+ } else {
+ dev_err(&led->pdev->dev, "Unknown flash LED name given\n");
+ return -EINVAL;
+ }
+
+ if (led->flash_cfg->torch_enable) {
+ if (of_find_property(of_get_parent(node), "torch-boost-supply",
+ NULL)) {
+ if (!led->flash_cfg->no_smbb_support) {
+ led->flash_cfg->torch_boost_reg =
+ regulator_get(&led->pdev->dev,
+ "torch-boost");
+ if (IS_ERR(led->flash_cfg->torch_boost_reg)) {
+ rc = PTR_ERR(led->flash_cfg->
+ torch_boost_reg);
+ dev_err(&led->pdev->dev,
+ "Torch regulator get failed(%d)\n", rc);
+ goto error_get_torch_reg;
+ }
+ }
+ led->flash_cfg->enable_module = FLASH_ENABLE_MODULE;
+ } else
+ led->flash_cfg->enable_module = FLASH_ENABLE_ALL;
+ led->flash_cfg->trigger_flash = FLASH_TORCH_OUTPUT;
+
+ rc = of_property_read_u32(node, "qcom,duration", &val);
+ if (!rc)
+ led->flash_cfg->duration = ((u8) val) - 2;
+ else if (rc == -EINVAL)
+ led->flash_cfg->duration = TORCH_DURATION_12s;
+ else {
+ if (led->flash_cfg->peripheral_subtype ==
+ FLASH_SUBTYPE_SINGLE)
+ goto error_get_flash_reg;
+ else if (led->flash_cfg->peripheral_subtype ==
+ FLASH_SUBTYPE_DUAL)
+ goto error_get_torch_reg;
+ }
+
+ rc = of_property_read_u32(node, "qcom,current", &val);
+ if (!rc)
+ led->flash_cfg->current_prgm = (val *
+ TORCH_MAX_LEVEL / led->max_current);
+ else {
+ if (led->flash_cfg->peripheral_subtype ==
+ FLASH_SUBTYPE_SINGLE)
+ goto error_get_flash_reg;
+ else if (led->flash_cfg->peripheral_subtype ==
+ FLASH_SUBTYPE_DUAL)
+ goto error_get_torch_reg;
+ goto error_get_torch_reg;
+ }
+
+ return 0;
+ }
+
+ rc = of_property_read_u32(node, "qcom,duration", &val);
+ if (!rc)
+ led->flash_cfg->duration = (u8)((val - 10) / 10);
+ else if (rc == -EINVAL)
+ led->flash_cfg->duration = FLASH_DURATION_200ms;
+ else
+ goto error_get_flash_reg;
+
+ rc = of_property_read_u32(node, "qcom,current", &val);
+ if (!rc)
+ led->flash_cfg->current_prgm = val * FLASH_MAX_LEVEL
+ / led->max_current;
+ else
+ goto error_get_flash_reg;
+
+ rc = of_property_read_u32(node, "qcom,headroom", &val);
+ if (!rc)
+ led->flash_cfg->headroom = (u8) val;
+ else if (rc == -EINVAL)
+ led->flash_cfg->headroom = HEADROOM_500mV;
+ else
+ goto error_get_flash_reg;
+
+ rc = of_property_read_u32(node, "qcom,clamp-curr", &val);
+ if (!rc)
+ led->flash_cfg->clamp_curr = (val *
+ FLASH_MAX_LEVEL / led->max_current);
+ else if (rc == -EINVAL)
+ led->flash_cfg->clamp_curr = FLASH_CLAMP_200mA;
+ else
+ goto error_get_flash_reg;
+
+ rc = of_property_read_u32(node, "qcom,startup-dly", &val);
+ if (!rc)
+ led->flash_cfg->startup_dly = (u8) val;
+ else if (rc == -EINVAL)
+ led->flash_cfg->startup_dly = DELAY_128us;
+ else
+ goto error_get_flash_reg;
+
+ led->flash_cfg->safety_timer =
+ of_property_read_bool(node, "qcom,safety-timer");
+
+ led->flash_cfg->vreg_ok =
+ of_property_read_bool(node, "qcom,sw_vreg_ok");
+
+ return 0;
+
+error_get_torch_reg:
+ if (led->flash_cfg->no_smbb_support)
+ regulator_put(led->flash_cfg->flash_boost_reg);
+ else
+ regulator_put(led->flash_cfg->torch_boost_reg);
+
+error_get_flash_reg:
+ regulator_put(led->flash_cfg->flash_boost_reg);
+ return rc;
+
+}
+
+static int qpnp_get_config_pwm(struct pwm_config_data *pwm_cfg,
+ struct platform_device *pdev,
+ struct device_node *node)
+{
+ struct property *prop;
+ int rc, i, lut_max_size;
+ u32 val;
+ u8 *temp_cfg;
+ const char *led_label;
+
+ pwm_cfg->pwm_dev = of_pwm_get(node, NULL);
+
+ if (IS_ERR(pwm_cfg->pwm_dev)) {
+ rc = PTR_ERR(pwm_cfg->pwm_dev);
+ dev_err(&pdev->dev, "Cannot get PWM device rc:(%d)\n", rc);
+ pwm_cfg->pwm_dev = NULL;
+ return rc;
+ }
+
+ if (pwm_cfg->mode != MANUAL_MODE) {
+ rc = of_property_read_u32(node, "qcom,pwm-us", &val);
+ if (!rc)
+ pwm_cfg->pwm_period_us = val;
+ else
+ return rc;
+ }
+
+ pwm_cfg->use_blink =
+ of_property_read_bool(node, "qcom,use-blink");
+
+ if (pwm_cfg->mode == LPG_MODE || pwm_cfg->use_blink) {
+ pwm_cfg->duty_cycles =
+ devm_kzalloc(&pdev->dev,
+ sizeof(struct pwm_duty_cycles), GFP_KERNEL);
+ if (!pwm_cfg->duty_cycles) {
+ dev_err(&pdev->dev, "Unable to allocate memory\n");
+ rc = -ENOMEM;
+ goto bad_lpg_params;
+ }
+
+ prop = of_find_property(node, "qcom,duty-pcts",
+ &pwm_cfg->duty_cycles->num_duty_pcts);
+ if (!prop) {
+ dev_err(&pdev->dev, "Looking up property node qcom,duty-pcts failed\n");
+ rc = -ENODEV;
+ goto bad_lpg_params;
+ } else if (!pwm_cfg->duty_cycles->num_duty_pcts) {
+ dev_err(&pdev->dev, "Invalid length of duty pcts\n");
+ rc = -EINVAL;
+ goto bad_lpg_params;
+ }
+
+ rc = of_property_read_string(node, "label", &led_label);
+
+ if (rc < 0) {
+ dev_err(&pdev->dev,
+ "Failure reading label, rc = %d\n", rc);
+ return rc;
+ }
+
+ if (strcmp(led_label, "kpdbl") == 0)
+ lut_max_size = PWM_GPLED_LUT_MAX_SIZE;
+ else
+ lut_max_size = PWM_LUT_MAX_SIZE;
+
+ pwm_cfg->duty_cycles->duty_pcts =
+ devm_kzalloc(&pdev->dev,
+ sizeof(int) * lut_max_size,
+ GFP_KERNEL);
+ if (!pwm_cfg->duty_cycles->duty_pcts) {
+ dev_err(&pdev->dev, "Unable to allocate memory\n");
+ rc = -ENOMEM;
+ goto bad_lpg_params;
+ }
+
+ pwm_cfg->old_duty_pcts =
+ devm_kzalloc(&pdev->dev,
+ sizeof(int) * lut_max_size,
+ GFP_KERNEL);
+ if (!pwm_cfg->old_duty_pcts) {
+ dev_err(&pdev->dev, "Unable to allocate memory\n");
+ rc = -ENOMEM;
+ goto bad_lpg_params;
+ }
+
+ temp_cfg = devm_kzalloc(&pdev->dev,
+ pwm_cfg->duty_cycles->num_duty_pcts *
+ sizeof(u8), GFP_KERNEL);
+ if (!temp_cfg) {
+ dev_err(&pdev->dev, "Failed to allocate memory for duty pcts\n");
+ rc = -ENOMEM;
+ goto bad_lpg_params;
+ }
+
+ memcpy(temp_cfg, prop->value,
+ pwm_cfg->duty_cycles->num_duty_pcts);
+
+ for (i = 0; i < pwm_cfg->duty_cycles->num_duty_pcts; i++)
+ pwm_cfg->duty_cycles->duty_pcts[i] =
+ (int) temp_cfg[i];
+
+ rc = of_property_read_u32(node, "qcom,start-idx", &val);
+ if (!rc) {
+ pwm_cfg->lut_params.start_idx = val;
+ pwm_cfg->duty_cycles->start_idx = val;
+ } else
+ goto bad_lpg_params;
+
+ pwm_cfg->lut_params.lut_pause_hi = 0;
+ rc = of_property_read_u32(node, "qcom,pause-hi", &val);
+ if (!rc)
+ pwm_cfg->lut_params.lut_pause_hi = val;
+ else if (rc != -EINVAL)
+ goto bad_lpg_params;
+
+ pwm_cfg->lut_params.lut_pause_lo = 0;
+ rc = of_property_read_u32(node, "qcom,pause-lo", &val);
+ if (!rc)
+ pwm_cfg->lut_params.lut_pause_lo = val;
+ else if (rc != -EINVAL)
+ goto bad_lpg_params;
+
+ pwm_cfg->lut_params.ramp_step_ms =
+ QPNP_LUT_RAMP_STEP_DEFAULT;
+ rc = of_property_read_u32(node, "qcom,ramp-step-ms", &val);
+ if (!rc)
+ pwm_cfg->lut_params.ramp_step_ms = val;
+ else if (rc != -EINVAL)
+ goto bad_lpg_params;
+
+ pwm_cfg->lut_params.flags = QPNP_LED_PWM_FLAGS;
+ rc = of_property_read_u32(node, "qcom,lut-flags", &val);
+ if (!rc)
+ pwm_cfg->lut_params.flags = (u8) val;
+ else if (rc != -EINVAL)
+ goto bad_lpg_params;
+
+ pwm_cfg->lut_params.idx_len =
+ pwm_cfg->duty_cycles->num_duty_pcts;
+ }
+ return 0;
+
+bad_lpg_params:
+ pwm_cfg->use_blink = false;
+ if (pwm_cfg->mode == PWM_MODE) {
+ dev_err(&pdev->dev, "LPG parameters not set for blink mode, defaulting to PWM mode\n");
+ return 0;
+ }
+ return rc;
+};
+
+static int qpnp_led_get_mode(const char *mode)
+{
+ if (strcmp(mode, "manual") == 0)
+ return MANUAL_MODE;
+ else if (strcmp(mode, "pwm") == 0)
+ return PWM_MODE;
+ else if (strcmp(mode, "lpg") == 0)
+ return LPG_MODE;
+ else
+ return -EINVAL;
+};
+
+static int qpnp_get_config_kpdbl(struct qpnp_led_data *led,
+ struct device_node *node)
+{
+ int rc;
+ u32 val;
+ u8 led_mode;
+ const char *mode;
+
+ led->kpdbl_cfg = devm_kzalloc(&led->pdev->dev,
+ sizeof(struct kpdbl_config_data), GFP_KERNEL);
+ if (!led->kpdbl_cfg)
+ return -ENOMEM;
+
+ rc = of_property_read_string(node, "qcom,mode", &mode);
+ if (!rc) {
+ led_mode = qpnp_led_get_mode(mode);
+ if ((led_mode == MANUAL_MODE) || (led_mode == -EINVAL)) {
+ dev_err(&led->pdev->dev, "Selected mode not supported for kpdbl.\n");
+ return -EINVAL;
+ }
+ led->kpdbl_cfg->pwm_cfg = devm_kzalloc(&led->pdev->dev,
+ sizeof(struct pwm_config_data),
+ GFP_KERNEL);
+ if (!led->kpdbl_cfg->pwm_cfg)
+ return -ENOMEM;
+
+ led->kpdbl_cfg->pwm_cfg->mode = led_mode;
+ led->kpdbl_cfg->pwm_cfg->default_mode = led_mode;
+ } else {
+ return rc;
+ }
+
+ rc = qpnp_get_config_pwm(led->kpdbl_cfg->pwm_cfg, led->pdev, node);
+ if (rc < 0)
+ return rc;
+
+ rc = of_property_read_u32(node, "qcom,row-id", &val);
+ if (!rc)
+ led->kpdbl_cfg->row_id = val;
+ else
+ return rc;
+
+ led->kpdbl_cfg->row_src_vbst =
+ of_property_read_bool(node, "qcom,row-src-vbst");
+
+ led->kpdbl_cfg->row_src_en =
+ of_property_read_bool(node, "qcom,row-src-en");
+
+ led->kpdbl_cfg->always_on =
+ of_property_read_bool(node, "qcom,always-on");
+
+ return 0;
+}
+
+static int qpnp_get_config_rgb(struct qpnp_led_data *led,
+ struct device_node *node)
+{
+ int rc;
+ u8 led_mode;
+ const char *mode;
+
+ led->rgb_cfg = devm_kzalloc(&led->pdev->dev,
+ sizeof(struct rgb_config_data), GFP_KERNEL);
+ if (!led->rgb_cfg)
+ return -ENOMEM;
+
+ if (led->id == QPNP_ID_RGB_RED)
+ led->rgb_cfg->enable = RGB_LED_ENABLE_RED;
+ else if (led->id == QPNP_ID_RGB_GREEN)
+ led->rgb_cfg->enable = RGB_LED_ENABLE_GREEN;
+ else if (led->id == QPNP_ID_RGB_BLUE)
+ led->rgb_cfg->enable = RGB_LED_ENABLE_BLUE;
+ else
+ return -EINVAL;
+
+ rc = of_property_read_string(node, "qcom,mode", &mode);
+ if (!rc) {
+ led_mode = qpnp_led_get_mode(mode);
+ if ((led_mode == MANUAL_MODE) || (led_mode == -EINVAL)) {
+ dev_err(&led->pdev->dev, "Selected mode not supported for rgb\n");
+ return -EINVAL;
+ }
+ led->rgb_cfg->pwm_cfg = devm_kzalloc(&led->pdev->dev,
+ sizeof(struct pwm_config_data),
+ GFP_KERNEL);
+ if (!led->rgb_cfg->pwm_cfg) {
+ dev_err(&led->pdev->dev,
+ "Unable to allocate memory\n");
+ return -ENOMEM;
+ }
+ led->rgb_cfg->pwm_cfg->mode = led_mode;
+ led->rgb_cfg->pwm_cfg->default_mode = led_mode;
+ } else {
+ return rc;
+ }
+
+ rc = qpnp_get_config_pwm(led->rgb_cfg->pwm_cfg, led->pdev, node);
+ if (rc < 0)
+ return rc;
+
+ return 0;
+}
+
+static int qpnp_get_config_mpp(struct qpnp_led_data *led,
+ struct device_node *node)
+{
+ int rc;
+ u32 val;
+ u8 led_mode;
+ const char *mode;
+
+ led->mpp_cfg = devm_kzalloc(&led->pdev->dev,
+ sizeof(struct mpp_config_data), GFP_KERNEL);
+ if (!led->mpp_cfg)
+ return -ENOMEM;
+
+ if (of_find_property(of_get_parent(node), "mpp-power-supply", NULL)) {
+ led->mpp_cfg->mpp_reg =
+ regulator_get(&led->pdev->dev,
+ "mpp-power");
+ if (IS_ERR(led->mpp_cfg->mpp_reg)) {
+ rc = PTR_ERR(led->mpp_cfg->mpp_reg);
+ dev_err(&led->pdev->dev,
+ "MPP regulator get failed(%d)\n", rc);
+ return rc;
+ }
+ }
+
+ if (led->mpp_cfg->mpp_reg) {
+ rc = of_property_read_u32(of_get_parent(node),
+ "qcom,mpp-power-max-voltage", &val);
+ if (!rc)
+ led->mpp_cfg->max_uV = val;
+ else
+ goto err_config_mpp;
+
+ rc = of_property_read_u32(of_get_parent(node),
+ "qcom,mpp-power-min-voltage", &val);
+ if (!rc)
+ led->mpp_cfg->min_uV = val;
+ else
+ goto err_config_mpp;
+ } else {
+ rc = of_property_read_u32(of_get_parent(node),
+ "qcom,mpp-power-max-voltage", &val);
+ if (!rc)
+ dev_warn(&led->pdev->dev, "No regulator specified\n");
+
+ rc = of_property_read_u32(of_get_parent(node),
+ "qcom,mpp-power-min-voltage", &val);
+ if (!rc)
+ dev_warn(&led->pdev->dev, "No regulator specified\n");
+ }
+
+ led->mpp_cfg->current_setting = LED_MPP_CURRENT_MIN;
+ rc = of_property_read_u32(node, "qcom,current-setting", &val);
+ if (!rc) {
+ if (led->mpp_cfg->current_setting < LED_MPP_CURRENT_MIN)
+ led->mpp_cfg->current_setting = LED_MPP_CURRENT_MIN;
+ else if (led->mpp_cfg->current_setting > LED_MPP_CURRENT_MAX)
+ led->mpp_cfg->current_setting = LED_MPP_CURRENT_MAX;
+ else
+ led->mpp_cfg->current_setting = (u8) val;
+ } else if (rc != -EINVAL)
+ goto err_config_mpp;
+
+ led->mpp_cfg->source_sel = LED_MPP_SOURCE_SEL_DEFAULT;
+ rc = of_property_read_u32(node, "qcom,source-sel", &val);
+ if (!rc)
+ led->mpp_cfg->source_sel = (u8) val;
+ else if (rc != -EINVAL)
+ goto err_config_mpp;
+
+ led->mpp_cfg->mode_ctrl = LED_MPP_MODE_SINK;
+ rc = of_property_read_u32(node, "qcom,mode-ctrl", &val);
+ if (!rc)
+ led->mpp_cfg->mode_ctrl = (u8) val;
+ else if (rc != -EINVAL)
+ goto err_config_mpp;
+
+ led->mpp_cfg->vin_ctrl = LED_MPP_VIN_CTRL_DEFAULT;
+ rc = of_property_read_u32(node, "qcom,vin-ctrl", &val);
+ if (!rc)
+ led->mpp_cfg->vin_ctrl = (u8) val;
+ else if (rc != -EINVAL)
+ goto err_config_mpp;
+
+ led->mpp_cfg->min_brightness = 0;
+ rc = of_property_read_u32(node, "qcom,min-brightness", &val);
+ if (!rc)
+ led->mpp_cfg->min_brightness = (u8) val;
+ else if (rc != -EINVAL)
+ goto err_config_mpp;
+
+ rc = of_property_read_string(node, "qcom,mode", &mode);
+ if (!rc) {
+ led_mode = qpnp_led_get_mode(mode);
+ led->mpp_cfg->pwm_mode = led_mode;
+ if (led_mode == MANUAL_MODE)
+ return MANUAL_MODE;
+ else if (led_mode == -EINVAL) {
+ dev_err(&led->pdev->dev, "Selected mode not supported for mpp\n");
+ rc = -EINVAL;
+ goto err_config_mpp;
+ }
+ led->mpp_cfg->pwm_cfg = devm_kzalloc(&led->pdev->dev,
+ sizeof(struct pwm_config_data),
+ GFP_KERNEL);
+ if (!led->mpp_cfg->pwm_cfg) {
+ dev_err(&led->pdev->dev,
+ "Unable to allocate memory\n");
+ rc = -ENOMEM;
+ goto err_config_mpp;
+ }
+ led->mpp_cfg->pwm_cfg->mode = led_mode;
+ led->mpp_cfg->pwm_cfg->default_mode = led_mode;
+ } else {
+ return rc;
+ }
+
+ rc = qpnp_get_config_pwm(led->mpp_cfg->pwm_cfg, led->pdev, node);
+ if (rc < 0)
+ goto err_config_mpp;
+
+ return 0;
+
+err_config_mpp:
+ if (led->mpp_cfg->mpp_reg)
+ regulator_put(led->mpp_cfg->mpp_reg);
+ return rc;
+}
+
+static int qpnp_get_config_gpio(struct qpnp_led_data *led,
+ struct device_node *node)
+{
+ int rc;
+ u32 val;
+
+ led->gpio_cfg = devm_kzalloc(&led->pdev->dev,
+ sizeof(struct gpio_config_data), GFP_KERNEL);
+ if (!led->gpio_cfg)
+ return -ENOMEM;
+
+ led->gpio_cfg->source_sel = LED_GPIO_SOURCE_SEL_DEFAULT;
+ rc = of_property_read_u32(node, "qcom,source-sel", &val);
+ if (!rc)
+ led->gpio_cfg->source_sel = (u8) val;
+ else if (rc != -EINVAL)
+ goto err_config_gpio;
+
+ led->gpio_cfg->mode_ctrl = LED_GPIO_MODE_OUTPUT;
+ rc = of_property_read_u32(node, "qcom,mode-ctrl", &val);
+ if (!rc)
+ led->gpio_cfg->mode_ctrl = (u8) val;
+ else if (rc != -EINVAL)
+ goto err_config_gpio;
+
+ led->gpio_cfg->vin_ctrl = LED_GPIO_VIN_CTRL_DEFAULT;
+ rc = of_property_read_u32(node, "qcom,vin-ctrl", &val);
+ if (!rc)
+ led->gpio_cfg->vin_ctrl = (u8) val;
+ else if (rc != -EINVAL)
+ goto err_config_gpio;
+
+ return 0;
+
+err_config_gpio:
+ return rc;
+}
+
+static int qpnp_leds_probe(struct platform_device *pdev)
+{
+ struct qpnp_led_data *led, *led_array;
+ unsigned int base;
+ struct device_node *node, *temp;
+ int rc, i, num_leds = 0, parsed_leds = 0;
+ const char *led_label;
+ bool regulator_probe = false;
+
+ node = pdev->dev.of_node;
+ if (node == NULL)
+ return -ENODEV;
+
+ temp = NULL;
+ while ((temp = of_get_next_child(node, temp)))
+ num_leds++;
+
+ if (!num_leds)
+ return -ECHILD;
+
+ led_array = devm_kcalloc(&pdev->dev, num_leds, sizeof(*led_array),
+ GFP_KERNEL);
+ if (!led_array)
+ return -ENOMEM;
+
+ for_each_child_of_node(node, temp) {
+ led = &led_array[parsed_leds];
+ led->num_leds = num_leds;
+ led->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!led->regmap) {
+ dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+ return -EINVAL;
+ }
+ led->pdev = pdev;
+
+ rc = of_property_read_u32(pdev->dev.of_node, "reg", &base);
+ if (rc < 0) {
+ dev_err(&pdev->dev,
+ "Couldn't find reg in node = %s rc = %d\n",
+ pdev->dev.of_node->full_name, rc);
+ goto fail_id_check;
+ }
+ led->base = base;
+
+ rc = of_property_read_string(temp, "label", &led_label);
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "Failure reading label, rc = %d\n", rc);
+ goto fail_id_check;
+ }
+
+ rc = of_property_read_string(temp, "linux,name",
+ &led->cdev.name);
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "Failure reading led name, rc = %d\n", rc);
+ goto fail_id_check;
+ }
+
+ rc = of_property_read_u32(temp, "qcom,max-current",
+ &led->max_current);
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "Failure reading max_current, rc = %d\n", rc);
+ goto fail_id_check;
+ }
+
+ rc = of_property_read_u32(temp, "qcom,id", &led->id);
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "Failure reading led id, rc = %d\n", rc);
+ goto fail_id_check;
+ }
+
+ rc = qpnp_get_common_configs(led, temp);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Failure reading common led configuration, rc = %d\n",
+ rc);
+ goto fail_id_check;
+ }
+
+ led->cdev.brightness_set = qpnp_led_set;
+ led->cdev.brightness_get = qpnp_led_get;
+
+ if (strcmp(led_label, "wled") == 0) {
+ rc = qpnp_get_config_wled(led, temp);
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "Unable to read wled config data\n");
+ goto fail_id_check;
+ }
+ } else if (strcmp(led_label, "flash") == 0) {
+ if (!of_find_property(node, "flash-boost-supply", NULL))
+ regulator_probe = true;
+ rc = qpnp_get_config_flash(led, temp, ®ulator_probe);
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "Unable to read flash config data\n");
+ goto fail_id_check;
+ }
+ } else if (strcmp(led_label, "rgb") == 0) {
+ rc = qpnp_get_config_rgb(led, temp);
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "Unable to read rgb config data\n");
+ goto fail_id_check;
+ }
+ } else if (strcmp(led_label, "mpp") == 0) {
+ rc = qpnp_get_config_mpp(led, temp);
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "Unable to read mpp config data\n");
+ goto fail_id_check;
+ }
+ } else if (strcmp(led_label, "gpio") == 0) {
+ rc = qpnp_get_config_gpio(led, temp);
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "Unable to read gpio config data\n");
+ goto fail_id_check;
+ }
+ } else if (strcmp(led_label, "kpdbl") == 0) {
+ bitmap_zero(kpdbl_leds_in_use, NUM_KPDBL_LEDS);
+ is_kpdbl_master_turn_on = false;
+ rc = qpnp_get_config_kpdbl(led, temp);
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "Unable to read kpdbl config data\n");
+ goto fail_id_check;
+ }
+ } else {
+ dev_err(&led->pdev->dev, "No LED matching label\n");
+ rc = -EINVAL;
+ goto fail_id_check;
+ }
+
+ if (led->id != QPNP_ID_FLASH1_LED0 &&
+ led->id != QPNP_ID_FLASH1_LED1)
+ mutex_init(&led->lock);
+
+ led->in_order_command_processing = of_property_read_bool
+ (temp, "qcom,in-order-command-processing");
+
+ if (led->in_order_command_processing) {
+ /*
+ * the command order from user space needs to be
+ * maintained use ordered workqueue to prevent
+ * concurrency
+ */
+ led->workqueue = alloc_ordered_workqueue
+ ("led_workqueue", 0);
+ if (!led->workqueue) {
+ rc = -ENOMEM;
+ goto fail_id_check;
+ }
+ }
+
+ INIT_WORK(&led->work, qpnp_led_work);
+
+ rc = qpnp_led_initialize(led);
+ if (rc < 0)
+ goto fail_id_check;
+
+ rc = qpnp_led_set_max_brightness(led);
+ if (rc < 0)
+ goto fail_id_check;
+
+ rc = led_classdev_register(&pdev->dev, &led->cdev);
+ if (rc) {
+ dev_err(&pdev->dev,
+ "unable to register led %d,rc=%d\n",
+ led->id, rc);
+ goto fail_id_check;
+ }
+
+ if (led->id == QPNP_ID_FLASH1_LED0 ||
+ led->id == QPNP_ID_FLASH1_LED1) {
+ rc = sysfs_create_group(&led->cdev.dev->kobj,
+ &led_attr_group);
+ if (rc)
+ goto fail_id_check;
+
+ }
+
+ if (led->id == QPNP_ID_LED_MPP) {
+ if (!led->mpp_cfg->pwm_cfg)
+ break;
+ if (led->mpp_cfg->pwm_cfg->mode == PWM_MODE) {
+ rc = sysfs_create_group(&led->cdev.dev->kobj,
+ &pwm_attr_group);
+ if (rc)
+ goto fail_id_check;
+ }
+ if (led->mpp_cfg->pwm_cfg->use_blink) {
+ rc = sysfs_create_group(&led->cdev.dev->kobj,
+ &blink_attr_group);
+ if (rc)
+ goto fail_id_check;
+
+ rc = sysfs_create_group(&led->cdev.dev->kobj,
+ &lpg_attr_group);
+ if (rc)
+ goto fail_id_check;
+ } else if (led->mpp_cfg->pwm_cfg->mode == LPG_MODE) {
+ rc = sysfs_create_group(&led->cdev.dev->kobj,
+ &lpg_attr_group);
+ if (rc)
+ goto fail_id_check;
+ }
+ } else if ((led->id == QPNP_ID_RGB_RED) ||
+ (led->id == QPNP_ID_RGB_GREEN) ||
+ (led->id == QPNP_ID_RGB_BLUE)) {
+ if (led->rgb_cfg->pwm_cfg->mode == PWM_MODE) {
+ rc = sysfs_create_group(&led->cdev.dev->kobj,
+ &pwm_attr_group);
+ if (rc)
+ goto fail_id_check;
+ }
+ if (led->rgb_cfg->pwm_cfg->use_blink) {
+ rc = sysfs_create_group(&led->cdev.dev->kobj,
+ &blink_attr_group);
+ if (rc)
+ goto fail_id_check;
+
+ rc = sysfs_create_group(&led->cdev.dev->kobj,
+ &lpg_attr_group);
+ if (rc)
+ goto fail_id_check;
+ } else if (led->rgb_cfg->pwm_cfg->mode == LPG_MODE) {
+ rc = sysfs_create_group(&led->cdev.dev->kobj,
+ &lpg_attr_group);
+ if (rc)
+ goto fail_id_check;
+ }
+ } else if (led->id == QPNP_ID_KPDBL) {
+ if (led->kpdbl_cfg->pwm_cfg->mode == PWM_MODE) {
+ rc = sysfs_create_group(&led->cdev.dev->kobj,
+ &pwm_attr_group);
+ if (rc)
+ goto fail_id_check;
+ }
+ if (led->kpdbl_cfg->pwm_cfg->use_blink) {
+ rc = sysfs_create_group(&led->cdev.dev->kobj,
+ &blink_attr_group);
+ if (rc)
+ goto fail_id_check;
+
+ rc = sysfs_create_group(&led->cdev.dev->kobj,
+ &lpg_attr_group);
+ if (rc)
+ goto fail_id_check;
+ } else if (led->kpdbl_cfg->pwm_cfg->mode == LPG_MODE) {
+ rc = sysfs_create_group(&led->cdev.dev->kobj,
+ &lpg_attr_group);
+ if (rc)
+ goto fail_id_check;
+ }
+ }
+
+ /* configure default state */
+ if (led->default_on) {
+ led->cdev.brightness = led->cdev.max_brightness;
+ __qpnp_led_work(led, led->cdev.brightness);
+ if (led->turn_off_delay_ms > 0)
+ qpnp_led_turn_off(led);
+ } else
+ led->cdev.brightness = LED_OFF;
+
+ parsed_leds++;
+ }
+ dev_set_drvdata(&pdev->dev, led_array);
+ return 0;
+
+fail_id_check:
+ for (i = 0; i < parsed_leds; i++) {
+ if (led_array[i].id != QPNP_ID_FLASH1_LED0 &&
+ led_array[i].id != QPNP_ID_FLASH1_LED1)
+ mutex_destroy(&led_array[i].lock);
+ if (led_array[i].in_order_command_processing)
+ destroy_workqueue(led_array[i].workqueue);
+ led_classdev_unregister(&led_array[i].cdev);
+ }
+
+ return rc;
+}
+
+static int qpnp_leds_remove(struct platform_device *pdev)
+{
+ struct qpnp_led_data *led_array = dev_get_drvdata(&pdev->dev);
+ int i, parsed_leds = led_array->num_leds;
+
+ for (i = 0; i < parsed_leds; i++) {
+ cancel_work_sync(&led_array[i].work);
+ if (led_array[i].id != QPNP_ID_FLASH1_LED0 &&
+ led_array[i].id != QPNP_ID_FLASH1_LED1)
+ mutex_destroy(&led_array[i].lock);
+
+ if (led_array[i].in_order_command_processing)
+ destroy_workqueue(led_array[i].workqueue);
+ led_classdev_unregister(&led_array[i].cdev);
+ switch (led_array[i].id) {
+ case QPNP_ID_WLED:
+ break;
+ case QPNP_ID_FLASH1_LED0:
+ case QPNP_ID_FLASH1_LED1:
+ if (led_array[i].flash_cfg->flash_reg_get)
+ regulator_put(
+ led_array[i].flash_cfg->flash_boost_reg);
+ if (led_array[i].flash_cfg->torch_enable)
+ if (!led_array[i].flash_cfg->no_smbb_support)
+ regulator_put(led_array[i].
+ flash_cfg->torch_boost_reg);
+ sysfs_remove_group(&led_array[i].cdev.dev->kobj,
+ &led_attr_group);
+ break;
+ case QPNP_ID_RGB_RED:
+ case QPNP_ID_RGB_GREEN:
+ case QPNP_ID_RGB_BLUE:
+ if (led_array[i].rgb_cfg->pwm_cfg->mode == PWM_MODE)
+ sysfs_remove_group(&led_array[i].cdev.dev->kobj,
+ &pwm_attr_group);
+ if (led_array[i].rgb_cfg->pwm_cfg->use_blink) {
+ sysfs_remove_group(&led_array[i].cdev.dev->kobj,
+ &blink_attr_group);
+ sysfs_remove_group(&led_array[i].cdev.dev->kobj,
+ &lpg_attr_group);
+ } else if (led_array[i].rgb_cfg->pwm_cfg->mode
+ == LPG_MODE)
+ sysfs_remove_group(&led_array[i].cdev.dev->kobj,
+ &lpg_attr_group);
+ break;
+ case QPNP_ID_LED_MPP:
+ if (!led_array[i].mpp_cfg->pwm_cfg)
+ break;
+ if (led_array[i].mpp_cfg->pwm_cfg->mode == PWM_MODE)
+ sysfs_remove_group(&led_array[i].cdev.dev->kobj,
+ &pwm_attr_group);
+ if (led_array[i].mpp_cfg->pwm_cfg->use_blink) {
+ sysfs_remove_group(&led_array[i].cdev.dev->kobj,
+ &blink_attr_group);
+ sysfs_remove_group(&led_array[i].cdev.dev->kobj,
+ &lpg_attr_group);
+ } else if (led_array[i].mpp_cfg->pwm_cfg->mode
+ == LPG_MODE)
+ sysfs_remove_group(&led_array[i].cdev.dev->kobj,
+ &lpg_attr_group);
+ if (led_array[i].mpp_cfg->mpp_reg)
+ regulator_put(led_array[i].mpp_cfg->mpp_reg);
+ break;
+ case QPNP_ID_KPDBL:
+ if (led_array[i].kpdbl_cfg->pwm_cfg->mode == PWM_MODE)
+ sysfs_remove_group(&led_array[i].cdev.dev->kobj,
+ &pwm_attr_group);
+ if (led_array[i].kpdbl_cfg->pwm_cfg->use_blink) {
+ sysfs_remove_group(&led_array[i].cdev.dev->kobj,
+ &blink_attr_group);
+ sysfs_remove_group(&led_array[i].cdev.dev->kobj,
+ &lpg_attr_group);
+ } else if (led_array[i].kpdbl_cfg->pwm_cfg->mode
+ == LPG_MODE)
+ sysfs_remove_group(&led_array[i].cdev.dev->kobj,
+ &lpg_attr_group);
+ break;
+ default:
+ dev_err(&led_array->pdev->dev,
+ "Invalid LED(%d)\n",
+ led_array[i].id);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id spmi_match_table[] = {
+ { .compatible = "qcom,leds-qpnp",},
+ { },
+};
+#else
+#define spmi_match_table NULL
+#endif
+
+static struct platform_driver qpnp_leds_driver = {
+ .driver = {
+ .name = "qcom,leds-qpnp",
+ .of_match_table = spmi_match_table,
+ },
+ .probe = qpnp_leds_probe,
+ .remove = qpnp_leds_remove,
+};
+
+static int __init qpnp_led_init(void)
+{
+ return platform_driver_register(&qpnp_leds_driver);
+}
+module_init(qpnp_led_init);
+
+static void __exit qpnp_led_exit(void)
+{
+ platform_driver_unregister(&qpnp_leds_driver);
+}
+module_exit(qpnp_led_exit);
+
+MODULE_DESCRIPTION("QPNP LEDs driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("leds:leds-qpnp");
+
diff --git a/drivers/leds/leds.h b/drivers/leds/leds.h
index 7d38e6b..dbb9a36 100644
--- a/drivers/leds/leds.h
+++ b/drivers/leds/leds.h
@@ -21,6 +21,22 @@
return led_cdev->brightness;
}
+static inline struct led_classdev *trigger_to_lcdev(struct led_trigger *trig)
+{
+ struct led_classdev *led_cdev;
+
+ read_lock(&trig->leddev_list_lock);
+ list_for_each_entry(led_cdev, &trig->led_cdevs, trig_list) {
+ if (!strcmp(led_cdev->default_trigger, trig->name)) {
+ read_unlock(&trig->leddev_list_lock);
+ return led_cdev;
+ }
+ }
+
+ read_unlock(&trig->leddev_list_lock);
+ return NULL;
+}
+
void led_init_core(struct led_classdev *led_cdev);
void led_stop_software_blink(struct led_classdev *led_cdev);
void led_set_brightness_nopm(struct led_classdev *led_cdev,
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 5f6083d1..1239e68 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -775,6 +775,17 @@
Say M here if you want to include support for PM8921 chip as a module.
This will build a module called "pm8921-core".
+config MFD_I2C_PMIC
+ tristate "QTI I2C PMIC support"
+ depends on I2C && OF
+ select IRQ_DOMAIN
+ select REGMAP_I2C
+ help
+ This enables support for controlling Qualcomm Technologies, Inc.
+ PMICs over I2C. The driver controls interrupts, and provides register
+ access for all of the device's peripherals. Some QTI PMIC chips
+ support communication over both I2C and SPMI.
+
config MFD_QCOM_RPM
tristate "Qualcomm Resource Power Manager (RPM)"
depends on ARCH_QCOM && OF
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index cdae17f..b2fe74b 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -173,6 +173,7 @@
obj-$(CONFIG_MFD_CS5535) += cs5535-mfd.o
obj-$(CONFIG_MFD_OMAP_USB_HOST) += omap-usb-host.o omap-usb-tll.o
obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o ssbi.o
+obj-$(CONFIG_MFD_I2C_PMIC) += qcom-i2c-pmic.o
obj-$(CONFIG_MFD_QCOM_RPM) += qcom_rpm.o
obj-$(CONFIG_MFD_SPMI_PMIC) += qcom-spmi-pmic.o
obj-$(CONFIG_TPS65911_COMPARATOR) += tps65911-comparator.o
diff --git a/drivers/mfd/qcom-i2c-pmic.c b/drivers/mfd/qcom-i2c-pmic.c
new file mode 100644
index 0000000..590e4c1
--- /dev/null
+++ b/drivers/mfd/qcom-i2c-pmic.c
@@ -0,0 +1,681 @@
+/* Copyright (c) 2016-2017 The Linux Foundation. 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.
+ */
+
+#define pr_fmt(fmt) "I2C PMIC: %s: " fmt, __func__
+
+#include <linux/bitops.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#define I2C_INTR_STATUS_BASE 0x0550
+#define INT_RT_STS_OFFSET 0x10
+#define INT_SET_TYPE_OFFSET 0x11
+#define INT_POL_HIGH_OFFSET 0x12
+#define INT_POL_LOW_OFFSET 0x13
+#define INT_LATCHED_CLR_OFFSET 0x14
+#define INT_EN_SET_OFFSET 0x15
+#define INT_EN_CLR_OFFSET 0x16
+#define INT_LATCHED_STS_OFFSET 0x18
+#define INT_PENDING_STS_OFFSET 0x19
+#define INT_MID_SEL_OFFSET 0x1A
+#define INT_MID_SEL_MASK GENMASK(1, 0)
+#define INT_PRIORITY_OFFSET 0x1B
+#define INT_PRIORITY_BIT BIT(0)
+
+enum {
+ IRQ_SET_TYPE = 0,
+ IRQ_POL_HIGH,
+ IRQ_POL_LOW,
+ IRQ_LATCHED_CLR, /* not needed but makes life easy */
+ IRQ_EN_SET,
+ IRQ_MAX_REGS,
+};
+
+struct i2c_pmic_periph {
+ void *data;
+ u16 addr;
+ u8 cached[IRQ_MAX_REGS];
+ u8 synced[IRQ_MAX_REGS];
+ u8 wake;
+ struct mutex lock;
+};
+
+struct i2c_pmic {
+ struct device *dev;
+ struct regmap *regmap;
+ struct irq_domain *domain;
+ struct i2c_pmic_periph *periph;
+ struct pinctrl *pinctrl;
+ const char *pinctrl_name;
+ int num_periphs;
+};
+
+static void i2c_pmic_irq_bus_lock(struct irq_data *d)
+{
+ struct i2c_pmic_periph *periph = irq_data_get_irq_chip_data(d);
+
+ mutex_lock(&periph->lock);
+}
+
+static void i2c_pmic_sync_type_polarity(struct i2c_pmic *chip,
+ struct i2c_pmic_periph *periph)
+{
+ int rc;
+
+ /* did any irq type change? */
+ if (periph->cached[IRQ_SET_TYPE] ^ periph->synced[IRQ_SET_TYPE]) {
+ rc = regmap_write(chip->regmap,
+ periph->addr | INT_SET_TYPE_OFFSET,
+ periph->cached[IRQ_SET_TYPE]);
+ if (rc < 0) {
+ pr_err("Couldn't set periph 0x%04x irqs 0x%02x type rc=%d\n",
+ periph->addr, periph->cached[IRQ_SET_TYPE], rc);
+ return;
+ }
+
+ periph->synced[IRQ_SET_TYPE] = periph->cached[IRQ_SET_TYPE];
+ }
+
+ /* did any polarity high change? */
+ if (periph->cached[IRQ_POL_HIGH] ^ periph->synced[IRQ_POL_HIGH]) {
+ rc = regmap_write(chip->regmap,
+ periph->addr | INT_POL_HIGH_OFFSET,
+ periph->cached[IRQ_POL_HIGH]);
+ if (rc < 0) {
+ pr_err("Couldn't set periph 0x%04x irqs 0x%02x polarity high rc=%d\n",
+ periph->addr, periph->cached[IRQ_POL_HIGH], rc);
+ return;
+ }
+
+ periph->synced[IRQ_POL_HIGH] = periph->cached[IRQ_POL_HIGH];
+ }
+
+ /* did any polarity low change? */
+ if (periph->cached[IRQ_POL_LOW] ^ periph->synced[IRQ_POL_LOW]) {
+ rc = regmap_write(chip->regmap,
+ periph->addr | INT_POL_LOW_OFFSET,
+ periph->cached[IRQ_POL_LOW]);
+ if (rc < 0) {
+ pr_err("Couldn't set periph 0x%04x irqs 0x%02x polarity low rc=%d\n",
+ periph->addr, periph->cached[IRQ_POL_LOW], rc);
+ return;
+ }
+
+ periph->synced[IRQ_POL_LOW] = periph->cached[IRQ_POL_LOW];
+ }
+}
+
+static void i2c_pmic_sync_enable(struct i2c_pmic *chip,
+ struct i2c_pmic_periph *periph)
+{
+ u8 en_set, en_clr;
+ int rc;
+
+ /* determine which irqs were enabled and which were disabled */
+ en_clr = periph->synced[IRQ_EN_SET] & ~periph->cached[IRQ_EN_SET];
+ en_set = ~periph->synced[IRQ_EN_SET] & periph->cached[IRQ_EN_SET];
+
+ /* were any irqs disabled? */
+ if (en_clr) {
+ rc = regmap_write(chip->regmap,
+ periph->addr | INT_EN_CLR_OFFSET, en_clr);
+ if (rc < 0) {
+ pr_err("Couldn't disable periph 0x%04x irqs 0x%02x rc=%d\n",
+ periph->addr, en_clr, rc);
+ return;
+ }
+ }
+
+ /* were any irqs enabled? */
+ if (en_set) {
+ rc = regmap_write(chip->regmap,
+ periph->addr | INT_EN_SET_OFFSET, en_set);
+ if (rc < 0) {
+ pr_err("Couldn't enable periph 0x%04x irqs 0x%02x rc=%d\n",
+ periph->addr, en_set, rc);
+ return;
+ }
+ }
+
+ /* irq enabled status was written to hardware */
+ periph->synced[IRQ_EN_SET] = periph->cached[IRQ_EN_SET];
+}
+
+static void i2c_pmic_irq_bus_sync_unlock(struct irq_data *d)
+{
+ struct i2c_pmic_periph *periph = irq_data_get_irq_chip_data(d);
+ struct i2c_pmic *chip = periph->data;
+
+ i2c_pmic_sync_type_polarity(chip, periph);
+ i2c_pmic_sync_enable(chip, periph);
+ mutex_unlock(&periph->lock);
+}
+
+static void i2c_pmic_irq_disable(struct irq_data *d)
+{
+ struct i2c_pmic_periph *periph = irq_data_get_irq_chip_data(d);
+
+ periph->cached[IRQ_EN_SET] &= ~d->hwirq & 0xFF;
+}
+
+static void i2c_pmic_irq_enable(struct irq_data *d)
+{
+ struct i2c_pmic_periph *periph = irq_data_get_irq_chip_data(d);
+
+ periph->cached[IRQ_EN_SET] |= d->hwirq & 0xFF;
+}
+
+static int i2c_pmic_irq_set_type(struct irq_data *d, unsigned int irq_type)
+{
+ struct i2c_pmic_periph *periph = irq_data_get_irq_chip_data(d);
+
+ switch (irq_type) {
+ case IRQ_TYPE_EDGE_RISING:
+ periph->cached[IRQ_SET_TYPE] |= d->hwirq & 0xFF;
+ periph->cached[IRQ_POL_HIGH] |= d->hwirq & 0xFF;
+ periph->cached[IRQ_POL_LOW] &= ~d->hwirq & 0xFF;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ periph->cached[IRQ_SET_TYPE] |= d->hwirq & 0xFF;
+ periph->cached[IRQ_POL_HIGH] &= ~d->hwirq & 0xFF;
+ periph->cached[IRQ_POL_LOW] |= d->hwirq & 0xFF;
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ periph->cached[IRQ_SET_TYPE] |= d->hwirq & 0xFF;
+ periph->cached[IRQ_POL_HIGH] |= d->hwirq & 0xFF;
+ periph->cached[IRQ_POL_LOW] |= d->hwirq & 0xFF;
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ periph->cached[IRQ_SET_TYPE] &= ~d->hwirq & 0xFF;
+ periph->cached[IRQ_POL_HIGH] |= d->hwirq & 0xFF;
+ periph->cached[IRQ_POL_LOW] &= ~d->hwirq & 0xFF;
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ periph->cached[IRQ_SET_TYPE] &= ~d->hwirq & 0xFF;
+ periph->cached[IRQ_POL_HIGH] &= ~d->hwirq & 0xFF;
+ periph->cached[IRQ_POL_LOW] |= d->hwirq & 0xFF;
+ break;
+ default:
+ pr_err("irq type 0x%04x is not supported\n", irq_type);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int i2c_pmic_irq_set_wake(struct irq_data *d, unsigned int on)
+{
+ struct i2c_pmic_periph *periph = irq_data_get_irq_chip_data(d);
+
+ if (on)
+ periph->wake |= d->hwirq & 0xFF;
+ else
+ periph->wake &= ~d->hwirq & 0xFF;
+
+ return 0;
+}
+#else
+#define i2c_pmic_irq_set_wake NULL
+#endif
+
+static struct irq_chip i2c_pmic_irq_chip = {
+ .name = "i2c_pmic_irq_chip",
+ .irq_bus_lock = i2c_pmic_irq_bus_lock,
+ .irq_bus_sync_unlock = i2c_pmic_irq_bus_sync_unlock,
+ .irq_disable = i2c_pmic_irq_disable,
+ .irq_enable = i2c_pmic_irq_enable,
+ .irq_set_type = i2c_pmic_irq_set_type,
+ .irq_set_wake = i2c_pmic_irq_set_wake,
+};
+
+static struct i2c_pmic_periph *i2c_pmic_find_periph(struct i2c_pmic *chip,
+ irq_hw_number_t hwirq)
+{
+ int i;
+
+ for (i = 0; i < chip->num_periphs; i++)
+ if (chip->periph[i].addr == (hwirq & 0xFF00))
+ return &chip->periph[i];
+
+ pr_err_ratelimited("Couldn't find periph struct for hwirq 0x%04lx\n",
+ hwirq);
+ return NULL;
+}
+
+static int i2c_pmic_domain_map(struct irq_domain *d, unsigned int virq,
+ irq_hw_number_t hwirq)
+{
+ struct i2c_pmic *chip = d->host_data;
+ struct i2c_pmic_periph *periph = i2c_pmic_find_periph(chip, hwirq);
+
+ if (!periph)
+ return -ENODEV;
+
+ irq_set_chip_data(virq, periph);
+ irq_set_chip_and_handler(virq, &i2c_pmic_irq_chip, handle_level_irq);
+ irq_set_nested_thread(virq, 1);
+ irq_set_noprobe(virq);
+ return 0;
+}
+
+static int i2c_pmic_domain_xlate(struct irq_domain *d,
+ struct device_node *ctrlr, const u32 *intspec,
+ unsigned int intsize, unsigned long *out_hwirq,
+ unsigned int *out_type)
+{
+ if (intsize != 3)
+ return -EINVAL;
+
+ if (intspec[0] > 0xFF || intspec[1] > 0x7 ||
+ intspec[2] > IRQ_TYPE_SENSE_MASK)
+ return -EINVAL;
+
+ /*
+ * Interrupt specifiers are triplets
+ * <peripheral-address, irq-number, IRQ_TYPE_*>
+ *
+ * peripheral-address - The base address of the peripheral
+ * irq-number - The zero based bit position of the peripheral's
+ * interrupt registers corresponding to the irq
+ * where the LSB is 0 and the MSB is 7
+ * IRQ_TYPE_* - Please refer to linux/irq.h
+ */
+ *out_hwirq = intspec[0] << 8 | BIT(intspec[1]);
+ *out_type = intspec[2] & IRQ_TYPE_SENSE_MASK;
+
+ return 0;
+}
+
+static const struct irq_domain_ops i2c_pmic_domain_ops = {
+ .map = i2c_pmic_domain_map,
+ .xlate = i2c_pmic_domain_xlate,
+};
+
+static void i2c_pmic_irq_ack_now(struct i2c_pmic *chip, u16 hwirq)
+{
+ int rc;
+
+ rc = regmap_write(chip->regmap,
+ (hwirq & 0xFF00) | INT_LATCHED_CLR_OFFSET,
+ hwirq & 0xFF);
+ if (rc < 0)
+ pr_err_ratelimited("Couldn't ack 0x%04x rc=%d\n", hwirq, rc);
+}
+
+static void i2c_pmic_irq_disable_now(struct i2c_pmic *chip, u16 hwirq)
+{
+ struct i2c_pmic_periph *periph = i2c_pmic_find_periph(chip, hwirq);
+ int rc;
+
+ if (!periph)
+ return;
+
+ mutex_lock(&periph->lock);
+ periph->cached[IRQ_EN_SET] &= ~hwirq & 0xFF;
+
+ rc = regmap_write(chip->regmap,
+ (hwirq & 0xFF00) | INT_EN_CLR_OFFSET,
+ hwirq & 0xFF);
+ if (rc < 0) {
+ pr_err_ratelimited("Couldn't disable irq 0x%04x rc=%d\n",
+ hwirq, rc);
+ goto unlock;
+ }
+
+ periph->synced[IRQ_EN_SET] = periph->cached[IRQ_EN_SET];
+
+unlock:
+ mutex_unlock(&periph->lock);
+}
+
+static void i2c_pmic_periph_status_handler(struct i2c_pmic *chip,
+ u16 periph_address, u8 periph_status)
+{
+ unsigned int hwirq, virq;
+ int i;
+
+ while (periph_status) {
+ i = ffs(periph_status) - 1;
+ periph_status &= ~BIT(i);
+ hwirq = periph_address | BIT(i);
+ virq = irq_find_mapping(chip->domain, hwirq);
+ if (virq == 0) {
+ pr_err_ratelimited("Couldn't find mapping; disabling 0x%04x\n",
+ hwirq);
+ i2c_pmic_irq_disable_now(chip, hwirq);
+ continue;
+ }
+
+ handle_nested_irq(virq);
+ i2c_pmic_irq_ack_now(chip, hwirq);
+ }
+}
+
+static void i2c_pmic_summary_status_handler(struct i2c_pmic *chip,
+ struct i2c_pmic_periph *periph,
+ u8 summary_status)
+{
+ unsigned int periph_status;
+ int rc, i;
+
+ while (summary_status) {
+ i = ffs(summary_status) - 1;
+ summary_status &= ~BIT(i);
+
+ rc = regmap_read(chip->regmap,
+ periph[i].addr | INT_LATCHED_STS_OFFSET,
+ &periph_status);
+ if (rc < 0) {
+ pr_err_ratelimited("Couldn't read 0x%04x | INT_LATCHED_STS rc=%d\n",
+ periph[i].addr, rc);
+ continue;
+ }
+
+ i2c_pmic_periph_status_handler(chip, periph[i].addr,
+ periph_status);
+ }
+}
+
+static irqreturn_t i2c_pmic_irq_handler(int irq, void *dev_id)
+{
+ struct i2c_pmic *chip = dev_id;
+ struct i2c_pmic_periph *periph;
+ unsigned int summary_status;
+ int rc, i;
+
+ for (i = 0; i < DIV_ROUND_UP(chip->num_periphs, BITS_PER_BYTE); i++) {
+ rc = regmap_read(chip->regmap, I2C_INTR_STATUS_BASE + i,
+ &summary_status);
+ if (rc < 0) {
+ pr_err_ratelimited("Couldn't read I2C_INTR_STATUS%d rc=%d\n",
+ i, rc);
+ continue;
+ }
+
+ if (summary_status == 0)
+ continue;
+
+ periph = &chip->periph[i * 8];
+ i2c_pmic_summary_status_handler(chip, periph, summary_status);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int i2c_pmic_parse_dt(struct i2c_pmic *chip)
+{
+ struct device_node *node = chip->dev->of_node;
+ int rc, i;
+ u32 temp;
+
+ if (!node) {
+ pr_err("missing device tree\n");
+ return -EINVAL;
+ }
+
+ chip->num_periphs = of_property_count_u32_elems(node,
+ "qcom,periph-map");
+ if (chip->num_periphs < 0) {
+ pr_err("missing qcom,periph-map property rc=%d\n",
+ chip->num_periphs);
+ return chip->num_periphs;
+ }
+
+ if (chip->num_periphs == 0) {
+ pr_err("qcom,periph-map must contain at least one address\n");
+ return -EINVAL;
+ }
+
+ chip->periph = devm_kcalloc(chip->dev, chip->num_periphs,
+ sizeof(*chip->periph), GFP_KERNEL);
+ if (!chip->periph)
+ return -ENOMEM;
+
+ for (i = 0; i < chip->num_periphs; i++) {
+ rc = of_property_read_u32_index(node, "qcom,periph-map",
+ i, &temp);
+ if (rc < 0) {
+ pr_err("Couldn't read qcom,periph-map[%d] rc=%d\n",
+ i, rc);
+ return rc;
+ }
+
+ chip->periph[i].addr = (u16)(temp << 8);
+ chip->periph[i].data = chip;
+ mutex_init(&chip->periph[i].lock);
+ }
+
+ of_property_read_string(node, "pinctrl-names", &chip->pinctrl_name);
+
+ return rc;
+}
+
+#define MAX_I2C_RETRIES 3
+static int i2c_pmic_read(struct regmap *map, unsigned int reg, void *val,
+ size_t val_count)
+{
+ int rc, retries = 0;
+
+ do {
+ rc = regmap_bulk_read(map, reg, val, val_count);
+ } while (rc == -ENOTCONN && retries++ < MAX_I2C_RETRIES);
+
+ if (retries > 1)
+ pr_err("i2c_pmic_read failed for %d retries, rc = %d\n",
+ retries - 1, rc);
+
+ return rc;
+}
+
+static int i2c_pmic_determine_initial_status(struct i2c_pmic *chip)
+{
+ int rc, i;
+
+ for (i = 0; i < chip->num_periphs; i++) {
+ rc = i2c_pmic_read(chip->regmap,
+ chip->periph[i].addr | INT_SET_TYPE_OFFSET,
+ chip->periph[i].cached, IRQ_MAX_REGS);
+ if (rc < 0) {
+ pr_err("Couldn't read irq data rc=%d\n", rc);
+ return rc;
+ }
+
+ memcpy(chip->periph[i].synced, chip->periph[i].cached,
+ IRQ_MAX_REGS * sizeof(*chip->periph[i].synced));
+ }
+
+ return 0;
+}
+
+static struct regmap_config i2c_pmic_regmap_config = {
+ .reg_bits = 16,
+ .val_bits = 8,
+ .max_register = 0xFFFF,
+};
+
+static int i2c_pmic_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct i2c_pmic *chip;
+ int rc = 0;
+
+ chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->dev = &client->dev;
+ chip->regmap = devm_regmap_init_i2c(client, &i2c_pmic_regmap_config);
+ if (!chip->regmap)
+ return -ENODEV;
+
+ i2c_set_clientdata(client, chip);
+ if (!of_property_read_bool(chip->dev->of_node, "interrupt-controller"))
+ goto probe_children;
+
+ chip->domain = irq_domain_add_tree(client->dev.of_node,
+ &i2c_pmic_domain_ops, chip);
+ if (!chip->domain) {
+ rc = -ENOMEM;
+ goto cleanup;
+ }
+
+ rc = i2c_pmic_parse_dt(chip);
+ if (rc < 0) {
+ pr_err("Couldn't parse device tree rc=%d\n", rc);
+ goto cleanup;
+ }
+
+ rc = i2c_pmic_determine_initial_status(chip);
+ if (rc < 0) {
+ pr_err("Couldn't determine initial status rc=%d\n", rc);
+ goto cleanup;
+ }
+
+ if (chip->pinctrl_name) {
+ chip->pinctrl = devm_pinctrl_get_select(chip->dev,
+ chip->pinctrl_name);
+ if (IS_ERR(chip->pinctrl)) {
+ pr_err("Couldn't select %s pinctrl rc=%ld\n",
+ chip->pinctrl_name, PTR_ERR(chip->pinctrl));
+ rc = PTR_ERR(chip->pinctrl);
+ goto cleanup;
+ }
+ }
+
+ rc = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+ i2c_pmic_irq_handler,
+ IRQF_ONESHOT | IRQF_SHARED,
+ "i2c_pmic_stat_irq", chip);
+ if (rc < 0) {
+ pr_err("Couldn't request irq %d rc=%d\n", client->irq, rc);
+ goto cleanup;
+ }
+
+ enable_irq_wake(client->irq);
+
+probe_children:
+ of_platform_populate(chip->dev->of_node, NULL, NULL, chip->dev);
+ pr_info("I2C PMIC probe successful\n");
+ return rc;
+
+cleanup:
+ if (chip->domain)
+ irq_domain_remove(chip->domain);
+ i2c_set_clientdata(client, NULL);
+ return rc;
+}
+
+static int i2c_pmic_remove(struct i2c_client *client)
+{
+ struct i2c_pmic *chip = i2c_get_clientdata(client);
+
+ of_platform_depopulate(chip->dev);
+ if (chip->domain)
+ irq_domain_remove(chip->domain);
+ i2c_set_clientdata(client, NULL);
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int i2c_pmic_suspend(struct device *dev)
+{
+ struct i2c_pmic *chip = dev_get_drvdata(dev);
+ struct i2c_pmic_periph *periph;
+ int rc = 0, i;
+
+ for (i = 0; i < chip->num_periphs; i++) {
+ periph = &chip->periph[i];
+
+ rc = regmap_write(chip->regmap,
+ periph->addr | INT_EN_CLR_OFFSET, 0xFF);
+ if (rc < 0) {
+ pr_err_ratelimited("Couldn't clear 0x%04x irqs rc=%d\n",
+ periph->addr, rc);
+ continue;
+ }
+
+ rc = regmap_write(chip->regmap,
+ periph->addr | INT_EN_SET_OFFSET,
+ periph->wake);
+ if (rc < 0)
+ pr_err_ratelimited("Couldn't enable 0x%04x wake irqs 0x%02x rc=%d\n",
+ periph->addr, periph->wake, rc);
+ }
+
+ return rc;
+}
+
+static int i2c_pmic_resume(struct device *dev)
+{
+ struct i2c_pmic *chip = dev_get_drvdata(dev);
+ struct i2c_pmic_periph *periph;
+ int rc = 0, i;
+
+ for (i = 0; i < chip->num_periphs; i++) {
+ periph = &chip->periph[i];
+
+ rc = regmap_write(chip->regmap,
+ periph->addr | INT_EN_CLR_OFFSET, 0xFF);
+ if (rc < 0) {
+ pr_err("Couldn't clear 0x%04x irqs rc=%d\n",
+ periph->addr, rc);
+ continue;
+ }
+
+ rc = regmap_write(chip->regmap,
+ periph->addr | INT_EN_SET_OFFSET,
+ periph->synced[IRQ_EN_SET]);
+ if (rc < 0)
+ pr_err("Couldn't restore 0x%04x synced irqs 0x%02x rc=%d\n",
+ periph->addr, periph->synced[IRQ_EN_SET], rc);
+ }
+
+ return rc;
+}
+#endif
+static SIMPLE_DEV_PM_OPS(i2c_pmic_pm_ops, i2c_pmic_suspend, i2c_pmic_resume);
+
+static const struct of_device_id i2c_pmic_match_table[] = {
+ { .compatible = "qcom,i2c-pmic", },
+ { },
+};
+
+static const struct i2c_device_id i2c_pmic_id[] = {
+ { "i2c-pmic", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, i2c_pmic_id);
+
+static struct i2c_driver i2c_pmic_driver = {
+ .driver = {
+ .name = "i2c_pmic",
+ .owner = THIS_MODULE,
+ .pm = &i2c_pmic_pm_ops,
+ .of_match_table = i2c_pmic_match_table,
+ },
+ .probe = i2c_pmic_probe,
+ .remove = i2c_pmic_remove,
+ .id_table = i2c_pmic_id,
+};
+
+module_i2c_driver(i2c_pmic_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("i2c:i2c_pmic");
diff --git a/drivers/platform/msm/Kconfig b/drivers/platform/msm/Kconfig
index a08b100..7018007 100644
--- a/drivers/platform/msm/Kconfig
+++ b/drivers/platform/msm/Kconfig
@@ -93,4 +93,24 @@
default n
help
No-Data-Path BAM is used to improve BAM performance.
+
+config QPNP_COINCELL
+ tristate "QPNP coincell charger support"
+ depends on SPMI
+ help
+ This driver supports the QPNP coincell peripheral found inside of
+ Qualcomm Technologies, Inc. QPNP PMIC devices. The coincell charger
+ provides a means to charge a coincell battery or backup capacitor
+ which is used to maintain PMIC register state when the main battery is
+ removed from the mobile device.
+
+config QPNP_REVID
+ tristate "QPNP Revision ID Peripheral"
+ depends on SPMI
+ help
+ Say 'y' here to include support for the Qualcomm Technologies, Inc.
+ QPNP REVID peripheral. REVID prints out the PMIC type and revision
+ numbers in the kernel log along with the PMIC option status. The PMIC
+ type is mapped to a QTI chip part number and logged as well.
+
endmenu
diff --git a/drivers/platform/msm/Makefile b/drivers/platform/msm/Makefile
index 7eebfcb5..85c4673 100644
--- a/drivers/platform/msm/Makefile
+++ b/drivers/platform/msm/Makefile
@@ -5,3 +5,7 @@
obj-$(CONFIG_IPA) += ipa/
obj-$(CONFIG_IPA3) += ipa/
obj-$(CONFIG_SPS) += sps/
+
+obj-$(CONFIG_QPNP_COINCELL) += qpnp-coincell.o
+obj-$(CONFIG_QPNP_REVID) += qpnp-revid.o
+
diff --git a/drivers/platform/msm/qpnp-coincell.c b/drivers/platform/msm/qpnp-coincell.c
new file mode 100644
index 0000000..b427f43
--- /dev/null
+++ b/drivers/platform/msm/qpnp-coincell.c
@@ -0,0 +1,283 @@
+/* Copyright (c) 2013-2015, 2017, The Linux Foundation. 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.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/kernel.h>
+#include <linux/regmap.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+
+#define QPNP_COINCELL_DRIVER_NAME "qcom,qpnp-coincell"
+
+struct qpnp_coincell {
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ u16 base_addr;
+};
+
+#define QPNP_COINCELL_REG_TYPE 0x04
+#define QPNP_COINCELL_REG_SUBTYPE 0x05
+#define QPNP_COINCELL_REG_RSET 0x44
+#define QPNP_COINCELL_REG_VSET 0x45
+#define QPNP_COINCELL_REG_ENABLE 0x46
+
+#define QPNP_COINCELL_TYPE 0x02
+#define QPNP_COINCELL_SUBTYPE 0x20
+#define QPNP_COINCELL_ENABLE 0x80
+#define QPNP_COINCELL_DISABLE 0x00
+
+static const int qpnp_rset_map[] = {2100, 1700, 1200, 800};
+static const int qpnp_vset_map[] = {2500, 3200, 3100, 3000};
+
+static int qpnp_coincell_set_resistance(struct qpnp_coincell *chip, int rset)
+{
+ int i, rc;
+ u8 reg;
+
+ for (i = 0; i < ARRAY_SIZE(qpnp_rset_map); i++)
+ if (rset == qpnp_rset_map[i])
+ break;
+
+ if (i >= ARRAY_SIZE(qpnp_rset_map)) {
+ pr_err("invalid rset=%d value\n", rset);
+ return -EINVAL;
+ }
+
+ reg = i;
+ rc = regmap_write(chip->regmap,
+ chip->base_addr + QPNP_COINCELL_REG_RSET, reg);
+ if (rc)
+ dev_err(&chip->pdev->dev,
+ "%s: could not write to RSET register, rc=%d\n",
+ __func__, rc);
+
+ return rc;
+}
+
+static int qpnp_coincell_set_voltage(struct qpnp_coincell *chip, int vset)
+{
+ int i, rc;
+ u8 reg;
+
+ for (i = 0; i < ARRAY_SIZE(qpnp_vset_map); i++)
+ if (vset == qpnp_vset_map[i])
+ break;
+
+ if (i >= ARRAY_SIZE(qpnp_vset_map)) {
+ pr_err("invalid vset=%d value\n", vset);
+ return -EINVAL;
+ }
+
+ reg = i;
+ rc = regmap_write(chip->regmap,
+ chip->base_addr + QPNP_COINCELL_REG_VSET, reg);
+ if (rc)
+ dev_err(&chip->pdev->dev,
+ "%s: could not write to VSET register, rc=%d\n",
+ __func__, rc);
+
+ return rc;
+}
+
+static int qpnp_coincell_set_charge(struct qpnp_coincell *chip, bool enabled)
+{
+ int rc;
+ u8 reg;
+
+ reg = enabled ? QPNP_COINCELL_ENABLE : QPNP_COINCELL_DISABLE;
+ rc = regmap_write(chip->regmap,
+ chip->base_addr + QPNP_COINCELL_REG_ENABLE, reg);
+ if (rc)
+ dev_err(&chip->pdev->dev,
+ "%s: could not write to ENABLE register, rc=%d\n",
+ __func__, rc);
+
+ return rc;
+}
+
+static void qpnp_coincell_charger_show_state(struct qpnp_coincell *chip)
+{
+ int rc, rset, vset, temp;
+ bool enabled;
+ u8 reg[QPNP_COINCELL_REG_ENABLE - QPNP_COINCELL_REG_RSET + 1];
+
+ rc = regmap_bulk_read(chip->regmap,
+ chip->base_addr + QPNP_COINCELL_REG_RSET, reg,
+ ARRAY_SIZE(reg));
+ if (rc) {
+ dev_err(&chip->pdev->dev,
+ "%s: could not read RSET register, rc=%d\n",
+ __func__, rc);
+ return;
+ }
+
+ temp = reg[QPNP_COINCELL_REG_RSET - QPNP_COINCELL_REG_RSET];
+ if (temp >= ARRAY_SIZE(qpnp_rset_map)) {
+ dev_err(&chip->pdev->dev,
+ "unknown RSET=0x%02X register value\n",
+ temp);
+ return;
+ }
+ rset = qpnp_rset_map[temp];
+
+ temp = reg[QPNP_COINCELL_REG_VSET - QPNP_COINCELL_REG_RSET];
+ if (temp >= ARRAY_SIZE(qpnp_vset_map)) {
+ dev_err(&chip->pdev->dev,
+ "unknown VSET=0x%02X register value\n",
+ temp);
+ return;
+ }
+ vset = qpnp_vset_map[temp];
+
+ temp = reg[QPNP_COINCELL_REG_ENABLE - QPNP_COINCELL_REG_RSET];
+ enabled = temp & QPNP_COINCELL_ENABLE;
+
+ pr_info("enabled=%c, voltage=%d mV, resistance=%d ohm\n",
+ (enabled ? 'Y' : 'N'), vset, rset);
+}
+
+static int qpnp_coincell_check_type(struct qpnp_coincell *chip)
+{
+ int rc;
+ u8 type[2];
+
+ rc = regmap_bulk_read(chip->regmap,
+ chip->base_addr + QPNP_COINCELL_REG_TYPE, type,
+ 2);
+ if (rc) {
+ dev_err(&chip->pdev->dev,
+ "%s: could not read type register, rc=%d\n",
+ __func__, rc);
+ return rc;
+ }
+
+ if (type[0] != QPNP_COINCELL_TYPE || type[1] != QPNP_COINCELL_SUBTYPE) {
+ dev_err(&chip->pdev->dev,
+ "%s: invalid type=0x%02X or subtype=0x%02X register value\n",
+ __func__, type[0], type[1]);
+ return -ENODEV;
+ }
+
+ return rc;
+}
+
+static int qpnp_coincell_probe(struct platform_device *pdev)
+{
+ struct device_node *node = pdev->dev.of_node;
+ struct qpnp_coincell *chip;
+ unsigned int base;
+ u32 temp;
+ int rc = 0;
+
+ if (!node) {
+ dev_err(&pdev->dev, "%s: device node missing\n", __func__);
+ return -ENODEV;
+ }
+
+ chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!chip->regmap) {
+ dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+ return -EINVAL;
+ }
+ chip->pdev = pdev;
+
+ rc = of_property_read_u32(pdev->dev.of_node, "reg", &base);
+ if (rc < 0) {
+ dev_err(&pdev->dev,
+ "Couldn't find reg in node = %s rc = %d\n",
+ pdev->dev.of_node->full_name, rc);
+ return rc;
+ }
+ chip->base_addr = base;
+
+ rc = qpnp_coincell_check_type(chip);
+ if (rc)
+ return rc;
+
+ rc = of_property_read_u32(node, "qcom,rset-ohms", &temp);
+ if (!rc) {
+ rc = qpnp_coincell_set_resistance(chip, temp);
+ if (rc)
+ return rc;
+ }
+
+ rc = of_property_read_u32(node, "qcom,vset-millivolts", &temp);
+ if (!rc) {
+ rc = qpnp_coincell_set_voltage(chip, temp);
+ if (rc)
+ return rc;
+ }
+
+ rc = of_property_read_u32(node, "qcom,charge-enable", &temp);
+ if (!rc) {
+ rc = qpnp_coincell_set_charge(chip, temp);
+ if (rc)
+ return rc;
+ }
+
+ qpnp_coincell_charger_show_state(chip);
+
+ return 0;
+}
+
+static int qpnp_coincell_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static const struct of_device_id qpnp_coincell_match_table[] = {
+ { .compatible = QPNP_COINCELL_DRIVER_NAME, },
+ {}
+};
+
+static const struct platform_device_id qpnp_coincell_id[] = {
+ { QPNP_COINCELL_DRIVER_NAME, 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(spmi, qpnp_coincell_id);
+
+static struct platform_driver qpnp_coincell_driver = {
+ .driver = {
+ .name = QPNP_COINCELL_DRIVER_NAME,
+ .of_match_table = qpnp_coincell_match_table,
+ .owner = THIS_MODULE,
+ },
+ .probe = qpnp_coincell_probe,
+ .remove = qpnp_coincell_remove,
+ .id_table = qpnp_coincell_id,
+};
+
+static int __init qpnp_coincell_init(void)
+{
+ return platform_driver_register(&qpnp_coincell_driver);
+}
+
+static void __exit qpnp_coincell_exit(void)
+{
+ platform_driver_unregister(&qpnp_coincell_driver);
+}
+
+MODULE_DESCRIPTION("QPNP PMIC coincell charger driver");
+MODULE_LICENSE("GPL v2");
+
+module_init(qpnp_coincell_init);
+module_exit(qpnp_coincell_exit);
diff --git a/drivers/platform/msm/qpnp-revid.c b/drivers/platform/msm/qpnp-revid.c
new file mode 100644
index 0000000..9ea0b40
--- /dev/null
+++ b/drivers/platform/msm/qpnp-revid.c
@@ -0,0 +1,270 @@
+/* Copyright (c) 2013-2017, The Linux Foundation. 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.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/err.h>
+#include <linux/qpnp/qpnp-revid.h>
+#include <linux/of.h>
+
+#define REVID_REVISION1 0x0
+#define REVID_REVISION2 0x1
+#define REVID_REVISION3 0x2
+#define REVID_REVISION4 0x3
+#define REVID_TYPE 0x4
+#define REVID_SUBTYPE 0x5
+#define REVID_STATUS1 0x8
+#define REVID_SPARE_0 0x60
+#define REVID_FAB_ID 0xf2
+
+#define QPNP_REVID_DEV_NAME "qcom,qpnp-revid"
+
+static const char *const pmic_names[] = {
+ [0] = "Unknown PMIC",
+ [PM8941_SUBTYPE] = "PM8941",
+ [PM8841_SUBTYPE] = "PM8841",
+ [PM8019_SUBTYPE] = "PM8019",
+ [PM8226_SUBTYPE] = "PM8226",
+ [PM8110_SUBTYPE] = "PM8110",
+ [PMA8084_SUBTYPE] = "PMA8084",
+ [PMI8962_SUBTYPE] = "PMI8962",
+ [PMD9635_SUBTYPE] = "PMD9635",
+ [PM8994_SUBTYPE] = "PM8994",
+ [PMI8994_SUBTYPE] = "PMI8994",
+ [PM8916_SUBTYPE] = "PM8916",
+ [PM8004_SUBTYPE] = "PM8004",
+ [PM8909_SUBTYPE] = "PM8909",
+ [PM2433_SUBTYPE] = "PM2433",
+ [PMD9655_SUBTYPE] = "PMD9655",
+ [PM8950_SUBTYPE] = "PM8950",
+ [PMI8950_SUBTYPE] = "PMI8950",
+ [PMK8001_SUBTYPE] = "PMK8001",
+ [PMI8996_SUBTYPE] = "PMI8996",
+ [PM8998_SUBTYPE] = "PM8998",
+ [PMI8998_SUBTYPE] = "PMI8998",
+ [PM8005_SUBTYPE] = "PM8005",
+ [PM8937_SUBTYPE] = "PM8937",
+ [PM660L_SUBTYPE] = "PM660L",
+ [PM660_SUBTYPE] = "PM660",
+ [PMI8937_SUBTYPE] = "PMI8937",
+};
+
+struct revid_chip {
+ struct list_head link;
+ struct device_node *dev_node;
+ struct pmic_revid_data data;
+};
+
+static LIST_HEAD(revid_chips);
+static DEFINE_MUTEX(revid_chips_lock);
+
+static const struct of_device_id qpnp_revid_match_table[] = {
+ { .compatible = QPNP_REVID_DEV_NAME },
+ {}
+};
+
+static u8 qpnp_read_byte(struct regmap *regmap, u16 addr)
+{
+ int rc;
+ int val;
+
+ rc = regmap_read(regmap, addr, &val);
+ if (rc) {
+ pr_err("read failed rc=%d\n", rc);
+ return 0;
+ }
+ return (u8)val;
+}
+
+/**
+ * get_revid_data - Return the revision information of PMIC
+ * @dev_node: Pointer to the revid peripheral of the PMIC for which
+ * revision information is seeked
+ *
+ * CONTEXT: Should be called in non atomic context
+ *
+ * RETURNS: pointer to struct pmic_revid_data filled with the information
+ * about the PMIC revision
+ */
+struct pmic_revid_data *get_revid_data(struct device_node *dev_node)
+{
+ struct revid_chip *revid_chip;
+
+ if (!dev_node)
+ return ERR_PTR(-EINVAL);
+
+ mutex_lock(&revid_chips_lock);
+ list_for_each_entry(revid_chip, &revid_chips, link) {
+ if (dev_node == revid_chip->dev_node) {
+ mutex_unlock(&revid_chips_lock);
+ return &revid_chip->data;
+ }
+ }
+ mutex_unlock(&revid_chips_lock);
+ return ERR_PTR(-EINVAL);
+}
+EXPORT_SYMBOL(get_revid_data);
+
+#define PM8941_PERIPHERAL_SUBTYPE 0x01
+#define PM8226_PERIPHERAL_SUBTYPE 0x04
+#define PMD9655_PERIPHERAL_SUBTYPE 0x0F
+#define PMI8950_PERIPHERAL_SUBTYPE 0x11
+#define PMI8937_PERIPHERAL_SUBTYPE 0x37
+static size_t build_pmic_string(char *buf, size_t n, int sid,
+ u8 subtype, u8 rev1, u8 rev2, u8 rev3, u8 rev4)
+{
+ size_t pos = 0;
+ /*
+ * In early versions of PM8941 and PM8226, the major revision number
+ * started incrementing from 0 (eg 0 = v1.0, 1 = v2.0).
+ * Increment the major revision number here if the chip is an early
+ * version of PM8941 or PM8226.
+ */
+ if (((int)subtype == PM8941_PERIPHERAL_SUBTYPE
+ || (int)subtype == PM8226_PERIPHERAL_SUBTYPE)
+ && rev4 < 0x02)
+ rev4++;
+
+ pos += snprintf(buf + pos, n - pos, "PMIC@SID%d", sid);
+ if (subtype >= ARRAY_SIZE(pmic_names) || subtype == 0)
+ pos += snprintf(buf + pos, n - pos, ": %s (subtype: 0x%02X)",
+ pmic_names[0], subtype);
+ else
+ pos += snprintf(buf + pos, n - pos, ": %s",
+ pmic_names[subtype]);
+ pos += snprintf(buf + pos, n - pos, " v%d.%d", rev4, rev3);
+ if (rev2 || rev1)
+ pos += snprintf(buf + pos, n - pos, ".%d", rev2);
+ if (rev1)
+ pos += snprintf(buf + pos, n - pos, ".%d", rev1);
+ return pos;
+}
+
+#define PMIC_PERIPHERAL_TYPE 0x51
+#define PMIC_STRING_MAXLENGTH 80
+static int qpnp_revid_probe(struct platform_device *pdev)
+{
+ u8 rev1, rev2, rev3, rev4, pmic_type, pmic_subtype, pmic_status;
+ u8 option1, option2, option3, option4, spare0, fab_id;
+ unsigned int base;
+ int rc;
+ char pmic_string[PMIC_STRING_MAXLENGTH] = {'\0'};
+ struct revid_chip *revid_chip;
+ struct regmap *regmap;
+
+ regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!regmap) {
+ dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+ return -EINVAL;
+ }
+
+ rc = of_property_read_u32(pdev->dev.of_node, "reg", &base);
+ if (rc < 0) {
+ dev_err(&pdev->dev,
+ "Couldn't find reg in node = %s rc = %d\n",
+ pdev->dev.of_node->full_name, rc);
+ return rc;
+ }
+ pmic_type = qpnp_read_byte(regmap, base + REVID_TYPE);
+ if (pmic_type != PMIC_PERIPHERAL_TYPE) {
+ pr_err("Invalid REVID peripheral type: %02X\n", pmic_type);
+ return -EINVAL;
+ }
+
+ rev1 = qpnp_read_byte(regmap, base + REVID_REVISION1);
+ rev2 = qpnp_read_byte(regmap, base + REVID_REVISION2);
+ rev3 = qpnp_read_byte(regmap, base + REVID_REVISION3);
+ rev4 = qpnp_read_byte(regmap, base + REVID_REVISION4);
+
+ pmic_subtype = qpnp_read_byte(regmap, base + REVID_SUBTYPE);
+ if (pmic_subtype != PMD9655_PERIPHERAL_SUBTYPE)
+ pmic_status = qpnp_read_byte(regmap, base + REVID_STATUS1);
+ else
+ pmic_status = 0;
+
+ /* special case for PMI8937 */
+ if (pmic_subtype == PMI8950_PERIPHERAL_SUBTYPE) {
+ /* read spare register */
+ spare0 = qpnp_read_byte(regmap, base + REVID_SPARE_0);
+ if (spare0)
+ pmic_subtype = PMI8937_PERIPHERAL_SUBTYPE;
+ }
+
+ if (of_property_read_bool(pdev->dev.of_node, "qcom,fab-id-valid"))
+ fab_id = qpnp_read_byte(regmap, base + REVID_FAB_ID);
+ else
+ fab_id = -EINVAL;
+
+ revid_chip = devm_kzalloc(&pdev->dev, sizeof(struct revid_chip),
+ GFP_KERNEL);
+ if (!revid_chip)
+ return -ENOMEM;
+
+ revid_chip->dev_node = pdev->dev.of_node;
+ revid_chip->data.rev1 = rev1;
+ revid_chip->data.rev2 = rev2;
+ revid_chip->data.rev3 = rev3;
+ revid_chip->data.rev4 = rev4;
+ revid_chip->data.pmic_subtype = pmic_subtype;
+ revid_chip->data.pmic_type = pmic_type;
+ revid_chip->data.fab_id = fab_id;
+
+ if (pmic_subtype < ARRAY_SIZE(pmic_names))
+ revid_chip->data.pmic_name = pmic_names[pmic_subtype];
+ else
+ revid_chip->data.pmic_name = pmic_names[0];
+
+ mutex_lock(&revid_chips_lock);
+ list_add(&revid_chip->link, &revid_chips);
+ mutex_unlock(&revid_chips_lock);
+
+ option1 = pmic_status & 0x3;
+ option2 = (pmic_status >> 2) & 0x3;
+ option3 = (pmic_status >> 4) & 0x3;
+ option4 = (pmic_status >> 6) & 0x3;
+
+ build_pmic_string(pmic_string, PMIC_STRING_MAXLENGTH,
+ to_spmi_device(pdev->dev.parent)->usid,
+ pmic_subtype, rev1, rev2, rev3, rev4);
+ pr_info("%s options: %d, %d, %d, %d\n",
+ pmic_string, option1, option2, option3, option4);
+ return 0;
+}
+
+static struct platform_driver qpnp_revid_driver = {
+ .probe = qpnp_revid_probe,
+ .driver = {
+ .name = QPNP_REVID_DEV_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = qpnp_revid_match_table,
+ },
+};
+
+static int __init qpnp_revid_init(void)
+{
+ return platform_driver_register(&qpnp_revid_driver);
+}
+
+static void __exit qpnp_revid_exit(void)
+{
+ return platform_driver_unregister(&qpnp_revid_driver);
+}
+
+subsys_initcall(qpnp_revid_init);
+module_exit(qpnp_revid_exit);
+
+MODULE_DESCRIPTION("QPNP REVID DRIVER");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" QPNP_REVID_DEV_NAME);
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index bf01288..e7d13ae 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -329,6 +329,16 @@
To compile this driver as a module, choose M here: the module
will be called pwm-pxa.
+config PWM_QPNP
+ tristate "Qualcomm Technologies, Inc. QPNP LPG/PWM support"
+ depends on SPMI
+ help
+ This driver supports PWM/LPG devices in Qualcomm Technologies, Inc.
+ PMIC chips which comply with QPNP. QPNP is an SPMI based PMIC
+ implementation. These devices support Pulse Width Modulation output
+ with user generated patterns. They share a lookup table with size of
+ 64 entries.
+
config PWM_RCAR
tristate "Renesas R-Car PWM support"
depends on ARCH_RENESAS || COMPILE_TEST
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 1194c54..24c1baf 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -31,6 +31,7 @@
obj-$(CONFIG_PWM_PCA9685) += pwm-pca9685.o
obj-$(CONFIG_PWM_PUV3) += pwm-puv3.o
obj-$(CONFIG_PWM_PXA) += pwm-pxa.o
+obj-$(CONFIG_PWM_QPNP) += pwm-qpnp.o
obj-$(CONFIG_PWM_RCAR) += pwm-rcar.o
obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o
obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o
diff --git a/drivers/pwm/pwm-qpnp.c b/drivers/pwm/pwm-qpnp.c
new file mode 100644
index 0000000..86f08d2
--- /dev/null
+++ b/drivers/pwm/pwm-qpnp.c
@@ -0,0 +1,2218 @@
+/* Copyright (c) 2012-2017, The Linux Foundation. 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 Technologies, Inc. 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/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/radix-tree.h>
+#include <linux/qpnp/pwm.h>
+
+#define QPNP_LPG_DRIVER_NAME "qcom,qpnp-pwm"
+#define QPNP_LPG_CHANNEL_BASE "qpnp-lpg-channel-base"
+#define QPNP_LPG_LUT_BASE "qpnp-lpg-lut-base"
+
+#define QPNP_PWM_MODE_ONLY_SUB_TYPE 0x0B
+#define QPNP_LPG_CHAN_SUB_TYPE 0x2
+#define QPNP_LPG_S_CHAN_SUB_TYPE 0x11
+
+/* 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_SUB_TYPE 2
+#define QPNP_PWM_SIZE_MASK_SUB_TYPE 0x4
+#define QPNP_PWM_FREQ_CLK_SELECT_MASK_SUB_TYPE 0x03
+#define QPNP_PWM_SIZE_9_BIT_SUB_TYPE 0x01
+
+#define QPNP_SET_PWM_CLK_SUB_TYPE(val, clk, pwm_size) \
+do { \
+ val = (clk + 1) & QPNP_PWM_FREQ_CLK_SELECT_MASK_SUB_TYPE; \
+ val |= (((pwm_size > 6 ? QPNP_PWM_SIZE_9_BIT_SUB_TYPE : 0) << \
+ QPNP_PWM_SIZE_SHIFT_SUB_TYPE) & QPNP_PWM_SIZE_MASK_SUB_TYPE); \
+} while (0)
+
+#define QPNP_GET_PWM_SIZE_SUB_TYPE(reg) ((reg & QPNP_PWM_SIZE_MASK_SUB_TYPE) \
+ >> QPNP_PWM_SIZE_SHIFT_SUB_TYPE)
+
+#define QPNP_PWM_SIZE_SHIFT 4
+#define QPNP_PWM_SIZE_MASK 0x30
+#define QPNP_PWM_FREQ_CLK_SELECT_MASK 0x03
+#define QPNP_MIN_PWM_BIT_SIZE 6
+#define QPNP_MAX_PWM_BIT_SIZE 9
+#define QPNP_PWM_SIZES_SUPPORTED 10
+
+#define QPNP_SET_PWM_CLK(val, clk, pwm_size) \
+do { \
+ val = (clk + 1) & QPNP_PWM_FREQ_CLK_SELECT_MASK; \
+ val |= (((pwm_size - QPNP_MIN_PWM_BIT_SIZE) << \
+ 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
+
+/* LPG Control for PWM_SYNC */
+#define QPNP_PWM_SYNC_VALUE 0x01
+#define QPNP_PWM_SYNC_MASK 0x01
+
+/* LPG Control for RAMP_CONTROL */
+#define QPNP_RAMP_START_MASK 0x01
+
+#define QPNP_ENABLE_LUT_V0(value) (value |= QPNP_RAMP_START_MASK)
+#define QPNP_DISABLE_LUT_V0(value) (value &= ~QPNP_RAMP_START_MASK)
+#define QPNP_ENABLE_LUT_V1(value, id) (value |= BIT(id))
+
+/* 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
+
+/* LPG DTEST */
+#define QPNP_LPG_DTEST_LINE_MAX 4
+#define QPNP_LPG_DTEST_OUTPUT_MAX 5
+#define QPNP_LPG_DTEST_OUTPUT_MASK 0x07
+
+/* PWM DTEST */
+#define QPNP_PWM_DTEST_LINE_MAX 2
+#define QPNP_PWM_DTEST_OUTPUT_MAX 2
+#define QPNP_PWM_DTEST_OUTPUT_MASK 0x03
+
+#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_BASE_OFFSET 0x40
+#define SPMI_LPG_REVISION2_OFFSET 0x1
+#define SPMI_LPG_REV1_RAMP_CONTROL_OFFSET 0x86
+#define SPMI_LPG_SUB_TYPE_OFFSET 0x5
+#define SPMI_LPG_PWM_SYNC 0x7
+#define SPMI_LPG_REG_ADDR(b, n) (b + SPMI_LPG_REG_BASE_OFFSET + (n))
+#define SPMI_MAX_BUF_LEN 8
+
+#define QPNP_PWM_LUT_NOT_SUPPORTED 0x1
+
+/* Supported PWM sizes */
+#define QPNP_PWM_SIZE_6_BIT 6
+#define QPNP_PWM_SIZE_7_BIT 7
+#define QPNP_PWM_SIZE_8_BIT 8
+#define QPNP_PWM_SIZE_9_BIT 9
+
+#define QPNP_PWM_SIZE_6_9_BIT 0x9
+#define QPNP_PWM_SIZE_7_8_BIT 0x6
+#define QPNP_PWM_SIZE_6_7_9_BIT 0xB
+
+/*
+ * Registers that don't need to be cached are defined below from an offset
+ * of SPMI_LPG_REG_BASE_OFFSET.
+ */
+#define QPNP_LPG_SEC_ACCESS 0x90
+#define QPNP_LPG_DTEST 0xA2
+
+/* Supported time levels */
+enum time_level {
+ LVL_NSEC,
+ LVL_USEC,
+};
+
+/* LPG revisions */
+enum qpnp_lpg_revision {
+ QPNP_LPG_REVISION_0 = 0x0,
+ QPNP_LPG_REVISION_1 = 0x1,
+};
+
+/* LPG LUT MODE STATE */
+enum qpnp_lut_state {
+ QPNP_LUT_ENABLE = 0x0,
+ QPNP_LUT_DISABLE = 0x1,
+};
+
+/* PWM MODE STATE */
+enum qpnp_pwm_state {
+ QPNP_PWM_ENABLE = 0x0,
+ QPNP_PWM_DISABLE = 0x1,
+};
+
+/* 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,
+ },
+};
+
+struct qpnp_lut_config {
+ u8 *duty_pct_list;
+ int list_len;
+ int ramp_index;
+ 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;
+};
+
+struct _qpnp_pwm_config {
+ int pwm_value;
+ int pwm_period; /* in microseconds */
+ int pwm_duty; /* in microseconds */
+ struct pwm_period_config period;
+ int supported_sizes;
+ int force_pwm_size;
+};
+
+/* Public facing structure */
+struct qpnp_pwm_chip {
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ struct pwm_chip chip;
+ bool enabled;
+ struct _qpnp_pwm_config pwm_config;
+ struct qpnp_lpg_config lpg_config;
+ spinlock_t lpg_lock;
+ enum qpnp_lpg_revision revision;
+ u8 sub_type;
+ u32 flags;
+ u8 qpnp_lpg_registers[QPNP_TOTAL_LPG_SPMI_REGISTERS];
+ int channel_id;
+ const char *channel_owner;
+ u32 dtest_line;
+ u32 dtest_output;
+ bool in_test_mode;
+};
+
+/* Internal functions */
+static inline struct qpnp_pwm_chip *qpnp_pwm_from_pwm_dev(
+ struct pwm_device *pwm)
+{
+ return container_of(pwm->chip, struct qpnp_pwm_chip, chip);
+}
+
+static inline struct qpnp_pwm_chip *qpnp_pwm_from_pwm_chip(
+ struct pwm_chip *chip)
+{
+ return container_of(chip, struct qpnp_pwm_chip, chip);
+}
+
+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 int qpnp_set_control(struct qpnp_pwm_chip *chip, bool pwm_hi,
+ bool pwm_lo, bool pwm_out, bool pwm_src, bool ramp_gen)
+{
+ int value;
+
+ value = (ramp_gen << QPNP_PWM_EN_RAMP_GEN_SHIFT) |
+ (pwm_src << QPNP_PWM_SRC_SELECT_SHIFT) |
+ (pwm_lo << QPNP_EN_PWM_LO_SHIFT) |
+ (pwm_hi << QPNP_EN_PWM_HIGH_SHIFT);
+ if (chip->sub_type != QPNP_LPG_S_CHAN_SUB_TYPE)
+ value |= (pwm_out << QPNP_EN_PWM_OUTPUT_SHIFT);
+ return value;
+}
+
+#define QPNP_ENABLE_LUT_CONTROL(chip) \
+ qpnp_set_control((chip), 0, 0, 0, 0, 1)
+#define QPNP_ENABLE_PWM_CONTROL(chip) \
+ qpnp_set_control((chip), 0, 0, 0, 1, 0)
+#define QPNP_ENABLE_PWM_MODE(chip) \
+ qpnp_set_control((chip), 1, 1, 1, 1, 0)
+#define QPNP_ENABLE_PWM_MODE_GPLED_CHANNEL(chip) \
+ qpnp_set_control((chip), 1, 1, 1, 1, 1)
+#define QPNP_ENABLE_LPG_MODE(chip) \
+ qpnp_set_control((chip), 1, 1, 1, 0, 1)
+#define QPNP_DISABLE_PWM_MODE(chip) \
+ qpnp_set_control((chip), 0, 0, 0, 1, 0)
+#define QPNP_DISABLE_LPG_MODE(chip) \
+ qpnp_set_control((chip), 0, 0, 0, 0, 1)
+#define QPNP_IS_PWM_CONFIG_SELECTED(val) (val & QPNP_PWM_SRC_SELECT_MASK)
+
+#define QPNP_ENABLE_PWM_MODE_ONLY_SUB_TYPE 0x80
+#define QPNP_DISABLE_PWM_MODE_ONLY_SUB_TYPE 0x0
+#define QPNP_PWM_MODE_ONLY_ENABLE_DISABLE_MASK_SUB_TYPE 0x80
+
+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, int s_idx, int size)
+{
+ l_params->start_idx = s_idx;
+ l_params->idx_len = 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 addr,
+ u16 size, struct qpnp_pwm_chip *chip)
+{
+ qpnp_lpg_save(reg, mask, value);
+
+ return regmap_bulk_write(chip->regmap, addr, 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(enum time_level tm_lvl,
+ unsigned int period_value,
+ struct qpnp_pwm_chip *chip)
+{
+ 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;
+ int supported_sizes = chip->pwm_config.supported_sizes;
+ int force_pwm_size = chip->pwm_config.force_pwm_size;
+ struct pwm_period_config *period = &chip->pwm_config.period;
+
+ /* PWM Period / N */
+ if (supported_sizes == QPNP_PWM_SIZE_7_8_BIT)
+ n = 7;
+ else
+ n = 6;
+
+ if (tm_lvl == LVL_USEC) {
+ if (period_value < ((unsigned int)(-1) / NSEC_PER_USEC)) {
+ period_n = (period_value * NSEC_PER_USEC) >> n;
+ } else {
+ if (supported_sizes == QPNP_PWM_SIZE_7_8_BIT)
+ n = 8;
+ else
+ n = 9;
+ period_n = (period_value >> n) * NSEC_PER_USEC;
+ }
+ } else {
+ period_n = period_value >> n;
+ }
+
+ if (force_pwm_size != 0) {
+ if (n < force_pwm_size)
+ period_n = period_n >> (force_pwm_size - n);
+ else
+ period_n = period_n << (n - force_pwm_size);
+ n = force_pwm_size;
+ pr_info("LPG channel '%d' pwm size is forced to=%d\n",
+ chip->channel_id, n);
+ }
+
+ min_err = last_err = (unsigned int)(-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;
+ }
+ }
+ }
+
+ /* Adapt to optimal pwm size, the higher the resolution the better */
+ if (!force_pwm_size) {
+ if (supported_sizes == QPNP_PWM_SIZE_7_8_BIT) {
+ if (n == 7 && best_m >= 1) {
+ n += 1;
+ best_m -= 1;
+ }
+ } else if (n == 6) {
+ if (best_m >= 3) {
+ n += 3;
+ best_m -= 3;
+ } else if (best_m >= 1 && (
+ chip->sub_type != QPNP_PWM_MODE_ONLY_SUB_TYPE &&
+ chip->sub_type != QPNP_LPG_S_CHAN_SUB_TYPE)) {
+ n += 1;
+ best_m -= 1;
+ }
+ }
+ }
+
+ 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 _qpnp_pwm_config *pwm_config,
+ unsigned int period_value,
+ unsigned int duty_value)
+{
+ unsigned int max_pwm_value, tmp;
+
+ /* Figure out pwm_value with overflow handling */
+ tmp = 1 << (sizeof(tmp) * 8 - pwm_config->period.pwm_size);
+ if (duty_value < tmp) {
+ tmp = duty_value << pwm_config->period.pwm_size;
+ pwm_config->pwm_value = tmp / period_value;
+ } else {
+ tmp = period_value >> pwm_config->period.pwm_size;
+ pwm_config->pwm_value = duty_value / 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;
+ pr_debug("pwm_value: %d\n", pwm_config->pwm_value);
+}
+
+static int qpnp_lpg_change_table(struct qpnp_pwm_chip *chip,
+ int duty_pct[], int raw_value)
+{
+ unsigned int pwm_value, max_pwm_value;
+ struct qpnp_lut_config *lut = &chip->lpg_config.lut_config;
+ int i, pwm_size, rc = 0;
+ int burst_size = SPMI_MAX_BUF_LEN;
+ int list_len = lut->list_len << 1;
+ int offset = (lut->lo_index << 1) - 2;
+
+ pwm_size = QPNP_GET_PWM_SIZE(
+ chip->qpnp_lpg_registers[QPNP_LPG_PWM_SIZE_CLK]) +
+ QPNP_MIN_PWM_BIT_SIZE;
+
+ max_pwm_value = (1 << pwm_size) - 1;
+
+ if (unlikely(lut->list_len != (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_len);
+ pr_err("However, index size is: %d\n",
+ (lut->hi_index - lut->lo_index + 1));
+ return -EINVAL;
+ }
+
+ for (i = 0; i < lut->list_len; 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;
+
+ if (chip->pwm_config.supported_sizes == QPNP_PWM_SIZE_7_8_BIT) {
+ lut->duty_pct_list[i] = pwm_value;
+ } else {
+ 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;
+ }
+ }
+
+ /*
+ * For the Keypad Backlight Lookup Table (KPDBL_LUT),
+ * offset is lo_index.
+ */
+ if (chip->pwm_config.supported_sizes == QPNP_PWM_SIZE_7_8_BIT)
+ offset = lut->lo_index;
+
+ /* Write with max allowable burst mode, each entry is of two bytes */
+ for (i = 0; i < list_len; i += burst_size) {
+ if (i + burst_size >= list_len)
+ burst_size = list_len - i;
+ rc = regmap_bulk_write(chip->regmap,
+ chip->lpg_config.lut_base_addr + offset + i,
+ lut->duty_pct_list + i,
+ burst_size);
+ }
+
+ return rc;
+}
+
+static void qpnp_lpg_save_period(struct qpnp_pwm_chip *chip)
+{
+ u8 mask, val;
+ struct _qpnp_pwm_config *pwm_config = &chip->pwm_config;
+
+ if (chip->sub_type == QPNP_PWM_MODE_ONLY_SUB_TYPE) {
+ QPNP_SET_PWM_CLK_SUB_TYPE(val, pwm_config->period.clk,
+ pwm_config->period.pwm_size);
+ mask = QPNP_PWM_SIZE_MASK_SUB_TYPE |
+ QPNP_PWM_FREQ_CLK_SELECT_MASK_SUB_TYPE;
+ } else {
+ 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 qpnp_pwm_chip *chip)
+{
+ unsigned int max_pwm_value;
+ int pwm_size;
+ u8 mask, value;
+ struct _qpnp_pwm_config *pwm_config = &chip->pwm_config;
+ struct qpnp_lpg_config *lpg_config = &chip->lpg_config;
+ int rc;
+
+ if (chip->sub_type == QPNP_PWM_MODE_ONLY_SUB_TYPE)
+ pwm_size = QPNP_GET_PWM_SIZE_SUB_TYPE(
+ chip->qpnp_lpg_registers[QPNP_LPG_PWM_SIZE_CLK]) ?
+ QPNP_MAX_PWM_BIT_SIZE : QPNP_MIN_PWM_BIT_SIZE;
+ else
+ pwm_size = QPNP_GET_PWM_SIZE(
+ chip->qpnp_lpg_registers[QPNP_LPG_PWM_SIZE_CLK]) +
+ QPNP_MIN_PWM_BIT_SIZE;
+
+ 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;
+
+ pr_debug("pwm_lsb value:%d\n", value & mask);
+ rc = qpnp_lpg_save_and_write(value, mask,
+ &chip->qpnp_lpg_registers[QPNP_PWM_VALUE_LSB],
+ SPMI_LPG_REG_ADDR(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;
+
+ pr_debug("pwm_msb value:%d\n", value);
+ rc = qpnp_lpg_save_and_write(value, mask,
+ &chip->qpnp_lpg_registers[QPNP_PWM_VALUE_MSB],
+ SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+ QPNP_PWM_VALUE_MSB), 1, chip);
+ if (rc)
+ return rc;
+
+ if (chip->sub_type == QPNP_PWM_MODE_ONLY_SUB_TYPE ||
+ chip->sub_type == QPNP_LPG_S_CHAN_SUB_TYPE) {
+ value = QPNP_PWM_SYNC_VALUE & QPNP_PWM_SYNC_MASK;
+ rc = regmap_write(chip->regmap,
+ SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+ SPMI_LPG_PWM_SYNC),
+ value);
+ }
+
+ return rc;
+}
+
+static int qpnp_lpg_configure_pattern(struct qpnp_pwm_chip *chip)
+{
+ struct qpnp_lpg_config *lpg_config = &chip->lpg_config;
+ struct qpnp_lut_config *lut_config = &lpg_config->lut_config;
+ 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,
+ &chip->qpnp_lpg_registers[QPNP_LPG_PATTERN_CONFIG],
+ SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+ QPNP_LPG_PATTERN_CONFIG), 1, chip);
+}
+
+static int qpnp_lpg_glitch_removal(struct qpnp_pwm_chip *chip, bool enable)
+{
+ struct qpnp_lpg_config *lpg_config = &chip->lpg_config;
+ u8 value, mask;
+
+ qpnp_set_pwm_type_config(&value, enable ? 1 : 0, 0, 0, 0);
+
+ mask = QPNP_EN_GLITCH_REMOVAL_MASK | QPNP_EN_FULL_SCALE_MASK |
+ QPNP_EN_PHASE_STAGGER_MASK | QPNP_PHASE_STAGGER_MASK;
+
+ pr_debug("pwm_type_config: %d\n", value);
+ return qpnp_lpg_save_and_write(value, mask,
+ &chip->qpnp_lpg_registers[QPNP_LPG_PWM_TYPE_CONFIG],
+ SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+ QPNP_LPG_PWM_TYPE_CONFIG), 1, chip);
+}
+
+static int qpnp_lpg_configure_pwm(struct qpnp_pwm_chip *chip)
+{
+ struct qpnp_lpg_config *lpg_config = &chip->lpg_config;
+ int rc;
+
+ pr_debug("pwm_size_clk: %d\n",
+ chip->qpnp_lpg_registers[QPNP_LPG_PWM_SIZE_CLK]);
+ rc = regmap_write(chip->regmap,
+ SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+ QPNP_LPG_PWM_SIZE_CLK),
+ *&chip->qpnp_lpg_registers[QPNP_LPG_PWM_SIZE_CLK]);
+
+ if (rc)
+ return rc;
+
+ pr_debug("pwm_freq_prediv_clk: %d\n",
+ chip->qpnp_lpg_registers[QPNP_LPG_PWM_FREQ_PREDIV_CLK]);
+ rc = regmap_write(chip->regmap,
+ SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+ QPNP_LPG_PWM_FREQ_PREDIV_CLK),
+ *&chip->qpnp_lpg_registers[QPNP_LPG_PWM_FREQ_PREDIV_CLK]);
+ if (rc)
+ return rc;
+
+ /* Disable glitch removal when LPG/PWM is configured */
+ rc = qpnp_lpg_glitch_removal(chip, false);
+ if (rc) {
+ pr_err("Error in disabling glitch control, rc=%d\n", rc);
+ return rc;
+ }
+ return rc;
+}
+
+static int qpnp_configure_pwm_control(struct qpnp_pwm_chip *chip)
+{
+ struct qpnp_lpg_config *lpg_config = &chip->lpg_config;
+ u8 value, mask;
+
+ if (chip->sub_type == QPNP_PWM_MODE_ONLY_SUB_TYPE)
+ return 0;
+
+ value = QPNP_ENABLE_PWM_CONTROL(chip);
+
+ mask = QPNP_EN_PWM_HIGH_MASK | QPNP_EN_PWM_LO_MASK |
+ QPNP_PWM_SRC_SELECT_MASK | QPNP_PWM_EN_RAMP_GEN_MASK;
+ if (chip->sub_type != QPNP_LPG_S_CHAN_SUB_TYPE)
+ mask |= QPNP_EN_PWM_OUTPUT_MASK;
+
+ return qpnp_lpg_save_and_write(value, mask,
+ &chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL],
+ SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+ QPNP_ENABLE_CONTROL), 1, chip);
+
+}
+
+static int qpnp_configure_lpg_control(struct qpnp_pwm_chip *chip)
+{
+ struct qpnp_lpg_config *lpg_config = &chip->lpg_config;
+ u8 value, mask;
+
+ value = QPNP_ENABLE_LUT_CONTROL(chip);
+
+ mask = QPNP_EN_PWM_HIGH_MASK | QPNP_EN_PWM_LO_MASK |
+ QPNP_PWM_SRC_SELECT_MASK | QPNP_PWM_EN_RAMP_GEN_MASK;
+ if (chip->sub_type != QPNP_LPG_S_CHAN_SUB_TYPE)
+ mask |= QPNP_EN_PWM_OUTPUT_MASK;
+
+ return qpnp_lpg_save_and_write(value, mask,
+ &chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL],
+ SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+ QPNP_ENABLE_CONTROL), 1, chip);
+
+}
+
+static int qpnp_lpg_configure_ramp_step_duration(struct qpnp_pwm_chip *chip)
+{
+ struct qpnp_lpg_config *lpg_config = &chip->lpg_config;
+ struct qpnp_lut_config lut_config = lpg_config->lut_config;
+ 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,
+ &chip->qpnp_lpg_registers[QPNP_RAMP_STEP_DURATION_LSB],
+ SPMI_LPG_REG_ADDR(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,
+ &chip->qpnp_lpg_registers[QPNP_RAMP_STEP_DURATION_MSB],
+ SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+ QPNP_RAMP_STEP_DURATION_MSB), 1, chip);
+}
+
+static int qpnp_lpg_configure_pause(struct qpnp_pwm_chip *chip)
+{
+ struct qpnp_lpg_config *lpg_config = &chip->lpg_config;
+ struct qpnp_lut_config lut_config = lpg_config->lut_config;
+ 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,
+ &chip->qpnp_lpg_registers[QPNP_PAUSE_HI_MULTIPLIER_LSB],
+ SPMI_LPG_REG_ADDR(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,
+ &chip->qpnp_lpg_registers[QPNP_PAUSE_HI_MULTIPLIER_MSB],
+ SPMI_LPG_REG_ADDR(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,
+ &chip->qpnp_lpg_registers[QPNP_PAUSE_HI_MULTIPLIER_LSB],
+ SPMI_LPG_REG_ADDR(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,
+ &chip->qpnp_lpg_registers[QPNP_PAUSE_HI_MULTIPLIER_MSB],
+ SPMI_LPG_REG_ADDR(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,
+ &chip->qpnp_lpg_registers[QPNP_PAUSE_LO_MULTIPLIER_LSB],
+ SPMI_LPG_REG_ADDR(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,
+ &chip->qpnp_lpg_registers[QPNP_PAUSE_LO_MULTIPLIER_MSB],
+ SPMI_LPG_REG_ADDR(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,
+ &chip->qpnp_lpg_registers[QPNP_PAUSE_LO_MULTIPLIER_LSB],
+ SPMI_LPG_REG_ADDR(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,
+ &chip->qpnp_lpg_registers[QPNP_PAUSE_LO_MULTIPLIER_MSB],
+ SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+ QPNP_PAUSE_LO_MULTIPLIER_MSB), 1, chip);
+ return rc;
+ }
+
+ return rc;
+}
+
+static int qpnp_lpg_configure_index(struct qpnp_pwm_chip *chip)
+{
+ struct qpnp_lpg_config *lpg_config = &chip->lpg_config;
+ struct qpnp_lut_config lut_config = lpg_config->lut_config;
+ u8 value, mask;
+ int rc = 0;
+
+ value = lut_config.hi_index;
+ mask = QPNP_HI_INDEX_MASK;
+
+ rc = qpnp_lpg_save_and_write(value, mask,
+ &chip->qpnp_lpg_registers[QPNP_HI_INDEX],
+ SPMI_LPG_REG_ADDR(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,
+ &chip->qpnp_lpg_registers[QPNP_LO_INDEX],
+ SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+ QPNP_LO_INDEX), 1, chip);
+
+ return rc;
+}
+
+static int qpnp_lpg_change_lut(struct qpnp_pwm_chip *chip)
+{
+ int rc;
+
+ rc = qpnp_lpg_configure_pattern(chip);
+ if (rc) {
+ pr_err("Failed to configure LUT pattern");
+ return rc;
+ }
+ rc = qpnp_lpg_configure_pwm(chip);
+ if (rc) {
+ pr_err("Failed to configure LUT pattern");
+ return rc;
+ }
+ rc = qpnp_configure_lpg_control(chip);
+ if (rc) {
+ pr_err("Failed to configure pause registers");
+ return rc;
+ }
+ rc = qpnp_lpg_configure_ramp_step_duration(chip);
+ if (rc) {
+ pr_err("Failed to configure duty time");
+ return rc;
+ }
+ rc = qpnp_lpg_configure_pause(chip);
+ if (rc) {
+ pr_err("Failed to configure pause registers");
+ return rc;
+ }
+ rc = qpnp_lpg_configure_index(chip);
+ if (rc) {
+ pr_err("Failed to configure index registers");
+ return rc;
+ }
+ return rc;
+}
+
+static int qpnp_dtest_config(struct qpnp_pwm_chip *chip, bool enable)
+{
+ struct qpnp_lpg_config *lpg_config = &chip->lpg_config;
+ u8 value;
+ u8 mask;
+ u16 addr;
+ int rc = 0;
+
+ value = 0xA5;
+
+ addr = SPMI_LPG_REG_ADDR(lpg_config->base_addr, QPNP_LPG_SEC_ACCESS);
+
+ rc = regmap_write(chip->regmap, addr, value);
+
+ if (rc) {
+ pr_err("Couldn't set the access for test mode\n");
+ return rc;
+ }
+
+ addr = SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+ QPNP_LPG_DTEST + chip->dtest_line - 1);
+
+ if (chip->sub_type == QPNP_PWM_MODE_ONLY_SUB_TYPE)
+ mask = QPNP_PWM_DTEST_OUTPUT_MASK;
+ else
+ mask = QPNP_LPG_DTEST_OUTPUT_MASK;
+
+ if (enable)
+ value = chip->dtest_output & mask;
+ else
+ value = 0;
+
+ pr_debug("Setting TEST mode for channel %d addr:%x value: %x\n",
+ chip->channel_id, addr, value);
+
+ rc = regmap_write(chip->regmap, addr, value);
+
+ return rc;
+}
+
+static int qpnp_lpg_configure_lut_state(struct qpnp_pwm_chip *chip,
+ enum qpnp_lut_state state)
+{
+ struct qpnp_lpg_config *lpg_config = &chip->lpg_config;
+ u8 value1, value2, mask1, mask2;
+ u8 *reg1, *reg2;
+ u16 addr, addr1;
+ int rc;
+ bool test_enable;
+
+ value1 = chip->qpnp_lpg_registers[QPNP_RAMP_CONTROL];
+ reg1 = &chip->qpnp_lpg_registers[QPNP_RAMP_CONTROL];
+ reg2 = &chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL];
+ mask2 = QPNP_EN_PWM_HIGH_MASK | QPNP_EN_PWM_LO_MASK |
+ QPNP_PWM_SRC_SELECT_MASK | QPNP_PWM_EN_RAMP_GEN_MASK;
+ if (chip->sub_type != QPNP_LPG_S_CHAN_SUB_TYPE)
+ mask2 |= QPNP_EN_PWM_OUTPUT_MASK;
+
+ if (chip->sub_type == QPNP_LPG_CHAN_SUB_TYPE
+ && chip->revision == QPNP_LPG_REVISION_0) {
+ if (state == QPNP_LUT_ENABLE) {
+ QPNP_ENABLE_LUT_V0(value1);
+ value2 = QPNP_ENABLE_LPG_MODE(chip);
+ } else {
+ QPNP_DISABLE_LUT_V0(value1);
+ value2 = QPNP_DISABLE_LPG_MODE(chip);
+ }
+ mask1 = QPNP_RAMP_START_MASK;
+ addr1 = SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+ QPNP_RAMP_CONTROL);
+ } else if ((chip->sub_type == QPNP_LPG_CHAN_SUB_TYPE
+ && chip->revision == QPNP_LPG_REVISION_1)
+ || chip->sub_type == QPNP_LPG_S_CHAN_SUB_TYPE) {
+ if (state == QPNP_LUT_ENABLE) {
+ QPNP_ENABLE_LUT_V1(value1,
+ lpg_config->lut_config.ramp_index);
+ value2 = QPNP_ENABLE_LPG_MODE(chip);
+ } else {
+ value2 = QPNP_DISABLE_LPG_MODE(chip);
+ }
+ mask1 = value1;
+ addr1 = lpg_config->lut_base_addr +
+ SPMI_LPG_REV1_RAMP_CONTROL_OFFSET;
+ } else {
+ pr_err("Unsupported LPG subtype 0x%02x, revision 0x%02x\n",
+ chip->sub_type, chip->revision);
+ return -EINVAL;
+ }
+
+ addr = SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+ QPNP_ENABLE_CONTROL);
+
+ if (chip->in_test_mode) {
+ test_enable = (state == QPNP_LUT_ENABLE) ? 1 : 0;
+ rc = qpnp_dtest_config(chip, test_enable);
+ if (rc)
+ pr_err("Failed to configure TEST mode\n");
+ }
+
+ rc = qpnp_lpg_save_and_write(value2, mask2, reg2,
+ addr, 1, chip);
+ if (rc)
+ return rc;
+
+ if (state == QPNP_LUT_ENABLE
+ || (chip->sub_type == QPNP_LPG_CHAN_SUB_TYPE
+ && chip->revision == QPNP_LPG_REVISION_0))
+ rc = qpnp_lpg_save_and_write(value1, mask1, reg1,
+ addr1, 1, chip);
+ return rc;
+}
+
+static inline int qpnp_enable_pwm_mode(struct qpnp_pwm_chip *chip)
+{
+ if (chip->pwm_config.supported_sizes == QPNP_PWM_SIZE_7_8_BIT)
+ return QPNP_ENABLE_PWM_MODE_GPLED_CHANNEL(chip);
+ return QPNP_ENABLE_PWM_MODE(chip);
+}
+
+static int qpnp_lpg_configure_pwm_state(struct qpnp_pwm_chip *chip,
+ enum qpnp_pwm_state state)
+{
+ struct qpnp_lpg_config *lpg_config = &chip->lpg_config;
+ u8 value, mask;
+ int rc;
+ bool test_enable;
+
+ if (chip->sub_type == QPNP_PWM_MODE_ONLY_SUB_TYPE) {
+ if (state == QPNP_PWM_ENABLE)
+ value = QPNP_ENABLE_PWM_MODE_ONLY_SUB_TYPE;
+ else
+ value = QPNP_DISABLE_PWM_MODE_ONLY_SUB_TYPE;
+
+ mask = QPNP_PWM_MODE_ONLY_ENABLE_DISABLE_MASK_SUB_TYPE;
+ } else {
+ if (state == QPNP_PWM_ENABLE)
+ value = qpnp_enable_pwm_mode(chip);
+ else
+ value = QPNP_DISABLE_PWM_MODE(chip);
+
+ mask = QPNP_EN_PWM_HIGH_MASK | QPNP_EN_PWM_LO_MASK |
+ QPNP_PWM_SRC_SELECT_MASK | QPNP_PWM_EN_RAMP_GEN_MASK;
+ if (chip->sub_type != QPNP_LPG_S_CHAN_SUB_TYPE)
+ mask |= QPNP_EN_PWM_OUTPUT_MASK;
+ }
+
+ if (chip->in_test_mode) {
+ test_enable = (state == QPNP_PWM_ENABLE) ? 1 : 0;
+ rc = qpnp_dtest_config(chip, test_enable);
+ if (rc)
+ pr_err("Failed to configure TEST mode\n");
+ }
+
+ pr_debug("pwm_enable_control: %d\n", value);
+ rc = qpnp_lpg_save_and_write(value, mask,
+ &chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL],
+ SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+ QPNP_ENABLE_CONTROL), 1, chip);
+ if (rc)
+ goto out;
+
+ /*
+ * Due to LPG hardware bug, in the PWM mode, having enabled PWM,
+ * We have to write PWM values one more time.
+ */
+ if (state == QPNP_PWM_ENABLE)
+ return qpnp_lpg_save_pwm_value(chip);
+
+out:
+ return rc;
+}
+
+static int _pwm_config(struct qpnp_pwm_chip *chip,
+ enum time_level tm_lvl,
+ int duty_value, int period_value)
+{
+ int rc;
+ struct _qpnp_pwm_config *pwm_config = &chip->pwm_config;
+ struct pwm_period_config *period = &pwm_config->period;
+
+ pwm_config->pwm_duty = (tm_lvl == LVL_USEC) ? duty_value :
+ duty_value / NSEC_PER_USEC;
+ qpnp_lpg_calc_pwm_value(pwm_config, period_value, duty_value);
+ rc = qpnp_lpg_save_pwm_value(chip);
+ if (rc)
+ goto out;
+ rc = qpnp_lpg_configure_pwm(chip);
+ if (rc)
+ goto out;
+ rc = qpnp_configure_pwm_control(chip);
+ if (rc)
+ goto out;
+
+ if (!rc && chip->enabled) {
+ rc = qpnp_lpg_configure_pwm_state(chip, QPNP_PWM_ENABLE);
+ if (rc) {
+ pr_err("Error in configuring pwm state, rc=%d\n", rc);
+ return rc;
+ }
+
+ /* Enable the glitch removal after PWM is enabled */
+ rc = qpnp_lpg_glitch_removal(chip, true);
+ if (rc) {
+ pr_err("Error in enabling glitch control, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ pr_debug("duty/period=%u/%u %s: pwm_value=%d (of %d)\n",
+ (unsigned int)duty_value, (unsigned int)period_value,
+ (tm_lvl == LVL_USEC) ? "usec" : "nsec",
+ pwm_config->pwm_value, 1 << period->pwm_size);
+
+out:
+ return rc;
+}
+
+static int _pwm_lut_config(struct qpnp_pwm_chip *chip, int period_us,
+ int duty_pct[], struct lut_params lut_params)
+{
+ struct qpnp_lpg_config *lpg_config;
+ struct qpnp_lut_config *lut_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 = &chip->pwm_config;
+ lpg_config = &chip->lpg_config;
+ lut_config = &lpg_config->lut_config;
+ period = &pwm_config->period;
+
+ 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_len = len;
+ lut_config->lo_index = start_idx + 1;
+ lut_config->hi_index = start_idx + len;
+
+ rc = qpnp_lpg_change_table(chip, 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_MAX_PAUSE_CNT)
+ lut_config->lut_pause_lo_cnt = PM_PWM_MAX_PAUSE_CNT;
+
+ 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_MAX_PAUSE_CNT)
+ lut_config->lut_pause_hi_cnt = PM_PWM_MAX_PAUSE_CNT;
+
+ 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);
+
+ rc = qpnp_lpg_change_lut(chip);
+
+ if (!rc && chip->enabled)
+ rc = qpnp_lpg_configure_lut_state(chip, QPNP_LUT_ENABLE);
+
+ return rc;
+}
+
+static int _pwm_enable(struct qpnp_pwm_chip *chip)
+{
+ int rc = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->lpg_lock, flags);
+
+ if (QPNP_IS_PWM_CONFIG_SELECTED(
+ chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL]) ||
+ chip->flags & QPNP_PWM_LUT_NOT_SUPPORTED) {
+ rc = qpnp_lpg_configure_pwm_state(chip, QPNP_PWM_ENABLE);
+ } else if (!(chip->flags & QPNP_PWM_LUT_NOT_SUPPORTED)) {
+ rc = qpnp_lpg_configure_lut_state(chip, QPNP_LUT_ENABLE);
+ }
+
+ if (!rc)
+ chip->enabled = true;
+
+ spin_unlock_irqrestore(&chip->lpg_lock, flags);
+
+ return rc;
+}
+
+/* APIs */
+/**
+ * qpnp_pwm_free - free a PWM device
+ * @pwm_chip: the PWM chip
+ * @pwm: the PWM device
+ */
+static void qpnp_pwm_free(struct pwm_chip *pwm_chip,
+ struct pwm_device *pwm)
+{
+ struct qpnp_pwm_chip *chip = qpnp_pwm_from_pwm_chip(pwm_chip);
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->lpg_lock, flags);
+
+ qpnp_lpg_configure_pwm_state(chip, QPNP_PWM_DISABLE);
+ if (!(chip->flags & QPNP_PWM_LUT_NOT_SUPPORTED))
+ qpnp_lpg_configure_lut_state(chip, QPNP_LUT_DISABLE);
+
+ chip->enabled = false;
+ spin_unlock_irqrestore(&chip->lpg_lock, flags);
+}
+
+/**
+ * qpnp_pwm_config - change a PWM device configuration
+ * @pwm: the PWM device
+ * @period_ns: period in nanoseconds
+ * @duty_ns: duty cycle in nanoseconds
+ */
+static int qpnp_pwm_config(struct pwm_chip *pwm_chip,
+ struct pwm_device *pwm, int duty_ns, int period_ns)
+{
+ int rc;
+ unsigned long flags;
+ struct qpnp_pwm_chip *chip = qpnp_pwm_from_pwm_chip(pwm_chip);
+ int prev_period_us = chip->pwm_config.pwm_period;
+
+ if ((unsigned int)period_ns < PM_PWM_PERIOD_MIN * NSEC_PER_USEC) {
+ pr_err("Invalid pwm handle or parameters\n");
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&chip->lpg_lock, flags);
+
+ if (prev_period_us > INT_MAX / NSEC_PER_USEC ||
+ prev_period_us * NSEC_PER_USEC != period_ns) {
+ qpnp_lpg_calc_period(LVL_NSEC, period_ns, chip);
+ qpnp_lpg_save_period(chip);
+ pwm->state.period = period_ns;
+ chip->pwm_config.pwm_period = period_ns / NSEC_PER_USEC;
+ }
+
+ rc = _pwm_config(chip, LVL_NSEC, duty_ns, period_ns);
+
+ spin_unlock_irqrestore(&chip->lpg_lock, flags);
+
+ if (rc)
+ pr_err("Failed to configure PWM mode\n");
+
+ return rc;
+}
+
+/**
+ * qpnp_pwm_enable - start a PWM output toggling
+ * @pwm_chip: the PWM chip
+ * @pwm: the PWM device
+ */
+static int qpnp_pwm_enable(struct pwm_chip *pwm_chip,
+ struct pwm_device *pwm)
+{
+ int rc;
+ struct qpnp_pwm_chip *chip = qpnp_pwm_from_pwm_chip(pwm_chip);
+
+ rc = _pwm_enable(chip);
+ if (rc)
+ pr_err("Failed to enable PWM channel: %d\n", chip->channel_id);
+
+ return rc;
+}
+
+/**
+ * qpnp_pwm_disable - stop a PWM output toggling
+ * @pwm_chip: the PWM chip
+ * @pwm: the PWM device
+ */
+static void qpnp_pwm_disable(struct pwm_chip *pwm_chip,
+ struct pwm_device *pwm)
+{
+
+ struct qpnp_pwm_chip *chip = qpnp_pwm_from_pwm_chip(pwm_chip);
+ unsigned long flags;
+ int rc = 0;
+
+ spin_lock_irqsave(&chip->lpg_lock, flags);
+
+ if (QPNP_IS_PWM_CONFIG_SELECTED(
+ chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL]) ||
+ chip->flags & QPNP_PWM_LUT_NOT_SUPPORTED)
+ rc = qpnp_lpg_configure_pwm_state(chip,
+ QPNP_PWM_DISABLE);
+ else if (!(chip->flags & QPNP_PWM_LUT_NOT_SUPPORTED))
+ rc = qpnp_lpg_configure_lut_state(chip,
+ QPNP_LUT_DISABLE);
+
+ if (!rc)
+ chip->enabled = false;
+
+ spin_unlock_irqrestore(&chip->lpg_lock, flags);
+
+ if (rc)
+ pr_err("Failed to disable PWM channel: %d\n",
+ chip->channel_id);
+}
+
+static int _pwm_change_mode(struct qpnp_pwm_chip *chip, enum pm_pwm_mode mode)
+{
+ int rc;
+
+ if (mode)
+ rc = qpnp_configure_lpg_control(chip);
+ else
+ rc = qpnp_configure_pwm_control(chip);
+
+ if (rc)
+ pr_err("Failed to change the mode\n");
+ return rc;
+}
+
+/**
+ * pwm_change_mode - Change the PWM mode configuration
+ * @pwm: the PWM device
+ * @mode: Mode selection value
+ */
+int pwm_change_mode(struct pwm_device *pwm, enum pm_pwm_mode mode)
+{
+ int rc;
+ unsigned long flags;
+ struct qpnp_pwm_chip *chip;
+
+ if (pwm == NULL || IS_ERR(pwm) || pwm->chip == NULL) {
+ pr_err("Invalid pwm handle or no pwm_chip\n");
+ return -EINVAL;
+ }
+
+ if (mode < PM_PWM_MODE_PWM || mode > PM_PWM_MODE_LPG) {
+ pr_err("Invalid mode value\n");
+ return -EINVAL;
+ }
+
+ chip = qpnp_pwm_from_pwm_dev(pwm);
+
+ spin_lock_irqsave(&chip->lpg_lock, flags);
+ rc = _pwm_change_mode(chip, mode);
+ spin_unlock_irqrestore(&chip->lpg_lock, flags);
+
+ return rc;
+}
+EXPORT_SYMBOL(pwm_change_mode);
+
+/**
+ * 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_pwm_chip *chip;
+ unsigned long flags;
+ int rc = 0;
+
+ if (pwm == NULL || IS_ERR(pwm) || period == NULL)
+ return -EINVAL;
+ if (pwm->chip == NULL)
+ return -ENODEV;
+
+ chip = qpnp_pwm_from_pwm_dev(pwm);
+ pwm_config = &chip->pwm_config;
+ lpg_config = &chip->lpg_config;
+
+ spin_lock_irqsave(&chip->lpg_lock, flags);
+
+ 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(chip);
+
+ rc = regmap_write(chip->regmap,
+ SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+ QPNP_LPG_PWM_SIZE_CLK),
+ *&chip->qpnp_lpg_registers[QPNP_LPG_PWM_SIZE_CLK]);
+
+ if (rc) {
+ pr_err("Write failed: QPNP_LPG_PWM_SIZE_CLK register, rc: %d\n",
+ rc);
+ goto out_unlock;
+ }
+
+ rc = regmap_write(chip->regmap,
+ SPMI_LPG_REG_ADDR(lpg_config->base_addr,
+ QPNP_LPG_PWM_FREQ_PREDIV_CLK),
+ *&chip->qpnp_lpg_registers[QPNP_LPG_PWM_FREQ_PREDIV_CLK]);
+ if (rc) {
+ pr_err("Failed to write to QPNP_LPG_PWM_FREQ_PREDIV_CLK\n");
+ pr_err("register, rc = %d\n", rc);
+ }
+
+out_unlock:
+ spin_unlock_irqrestore(&chip->lpg_lock, flags);
+ 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;
+ struct qpnp_pwm_chip *chip;
+ unsigned long flags;
+ int rc = 0;
+
+ if (pwm == NULL || IS_ERR(pwm)) {
+ pr_err("Invalid parameter passed\n");
+ return -EINVAL;
+ }
+
+ if (pwm->chip == NULL) {
+ pr_err("Invalid device handle\n");
+ return -ENODEV;
+ }
+
+ chip = qpnp_pwm_from_pwm_dev(pwm);
+ lpg_config = &chip->lpg_config;
+ pwm_config = &chip->pwm_config;
+
+ spin_lock_irqsave(&chip->lpg_lock, flags);
+
+ if (pwm_config->pwm_value == pwm_value)
+ goto out_unlock;
+
+ pwm_config->pwm_value = pwm_value;
+
+ rc = qpnp_lpg_save_pwm_value(chip);
+
+ if (rc)
+ pr_err("Could not update PWM value for channel %d rc=%d\n",
+ chip->channel_id, rc);
+
+out_unlock:
+ spin_unlock_irqrestore(&chip->lpg_lock, flags);
+ return rc;
+}
+EXPORT_SYMBOL(pwm_config_pwm_value);
+
+/**
+ * pwm_config_us - change a PWM device configuration
+ * @pwm: the PWM device
+ * @period_us: period in microseconds
+ * @duty_us: duty cycle in microseconds
+ */
+int pwm_config_us(struct pwm_device *pwm, int duty_us, int period_us)
+{
+ int rc;
+ unsigned long flags;
+ struct qpnp_pwm_chip *chip;
+
+ if (pwm == NULL || IS_ERR(pwm) ||
+ duty_us > period_us ||
+ (unsigned int)period_us > PM_PWM_PERIOD_MAX ||
+ (unsigned int)period_us < PM_PWM_PERIOD_MIN) {
+ pr_err("Invalid pwm handle or parameters\n");
+ return -EINVAL;
+ }
+
+ chip = qpnp_pwm_from_pwm_dev(pwm);
+
+ spin_lock_irqsave(&chip->lpg_lock, flags);
+
+ if (chip->pwm_config.pwm_period != period_us) {
+ qpnp_lpg_calc_period(LVL_USEC, period_us, chip);
+ qpnp_lpg_save_period(chip);
+ chip->pwm_config.pwm_period = period_us;
+ if ((unsigned int)period_us >
+ (unsigned int)(-1) / NSEC_PER_USEC)
+ pwm->state.period = 0;
+ else
+ pwm->state.period
+ = (unsigned int)period_us * NSEC_PER_USEC;
+ }
+
+ rc = _pwm_config(chip, LVL_USEC, duty_us, period_us);
+
+ spin_unlock_irqrestore(&chip->lpg_lock, flags);
+
+ if (rc)
+ pr_err("Failed to configure PWM mode\n");
+
+ return rc;
+}
+EXPORT_SYMBOL(pwm_config_us);
+
+/**
+ * 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)
+{
+ unsigned long flags;
+ struct qpnp_pwm_chip *chip;
+ 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;
+ }
+
+ chip = qpnp_pwm_from_pwm_dev(pwm);
+
+ if (chip->flags & QPNP_PWM_LUT_NOT_SUPPORTED) {
+ pr_err("LUT mode isn't supported\n");
+ return -EINVAL;
+ }
+
+ if ((lut_params.start_idx + lut_params.idx_len) >
+ chip->lpg_config.lut_size) {
+ pr_err("Exceed LUT limit\n");
+ return -EINVAL;
+ }
+
+ if ((unsigned int)period_us > PM_PWM_PERIOD_MAX ||
+ (unsigned int)period_us < PM_PWM_PERIOD_MIN) {
+ pr_err("Period out of range\n");
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&chip->lpg_lock, flags);
+
+ if (chip->pwm_config.pwm_period != period_us) {
+ qpnp_lpg_calc_period(LVL_USEC, period_us, chip);
+ qpnp_lpg_save_period(chip);
+ chip->pwm_config.pwm_period = period_us;
+ }
+
+ rc = _pwm_lut_config(chip, period_us, duty_pct, lut_params);
+
+ spin_unlock_irqrestore(&chip->lpg_lock, flags);
+
+ if (rc)
+ pr_err("Failed to configure LUT\n");
+
+ return rc;
+}
+EXPORT_SYMBOL(pwm_lut_config);
+
+static int qpnp_parse_pwm_dt_config(struct device_node *of_pwm_node,
+ struct device_node *of_parent, struct qpnp_pwm_chip *chip)
+{
+ int rc, period;
+
+ rc = of_property_read_u32(of_parent, "qcom,period", (u32 *)&period);
+ if (rc) {
+ pr_err("node is missing PWM Period prop");
+ return rc;
+ }
+
+ rc = of_property_read_u32(of_pwm_node, "qcom,duty",
+ &chip->pwm_config.pwm_duty);
+ if (rc) {
+ pr_err("node is missing PWM Duty prop");
+ return rc;
+ }
+
+ if (period < chip->pwm_config.pwm_duty || period > PM_PWM_PERIOD_MAX ||
+ period < PM_PWM_PERIOD_MIN) {
+ pr_err("Invalid pwm period(%d) or duty(%d)\n", period,
+ chip->pwm_config.pwm_duty);
+ return -EINVAL;
+ }
+
+ qpnp_lpg_calc_period(LVL_USEC, period, chip);
+ qpnp_lpg_save_period(chip);
+ chip->pwm_config.pwm_period = period;
+
+ rc = _pwm_config(chip, LVL_USEC, chip->pwm_config.pwm_duty, period);
+
+ return rc;
+}
+
+static int qpnp_parse_lpg_dt_config(struct device_node *of_lpg_node,
+ struct device_node *of_parent, struct qpnp_pwm_chip *chip)
+{
+ int rc, period, list_size, start_idx, *duty_pct_list;
+ struct qpnp_lpg_config *lpg_config = &chip->lpg_config;
+ struct qpnp_lut_config *lut_config = &lpg_config->lut_config;
+ struct lut_params lut_params;
+
+ rc = of_property_read_u32(of_parent, "qcom,period", &period);
+ if (rc) {
+ pr_err("node is missing PWM Period prop\n");
+ return rc;
+ }
+
+ if (!of_get_property(of_lpg_node, "qcom,duty-percents", &list_size)) {
+ pr_err("node is missing duty-pct list\n");
+ return rc;
+ }
+
+ rc = of_property_read_u32(of_lpg_node, "cell-index", &start_idx);
+ if (rc) {
+ pr_err("Missing start index\n");
+ return rc;
+ }
+
+ list_size /= sizeof(u32);
+
+ if (list_size + start_idx > lpg_config->lut_size) {
+ pr_err("duty pct list size overflows\n");
+ return -EINVAL;
+ }
+
+ duty_pct_list = kcalloc(list_size, sizeof(*duty_pct_list), GFP_KERNEL);
+ if (!duty_pct_list)
+ return -ENOMEM;
+
+ rc = of_property_read_u32_array(of_lpg_node, "qcom,duty-percents",
+ duty_pct_list, list_size);
+ if (rc) {
+ pr_err("invalid or missing property: qcom,duty-pcts-list\n");
+ goto out;
+ }
+
+ /* Read optional properties */
+ rc = of_property_read_u32(of_lpg_node, "qcom,ramp-step-duration",
+ &lut_config->ramp_step_ms);
+ if (rc && rc != -EINVAL)
+ goto out;
+
+ rc = of_property_read_u32(of_lpg_node, "qcom,lpg-lut-pause-hi",
+ &lut_config->lut_pause_hi_cnt);
+ if (rc && rc != -EINVAL)
+ goto out;
+
+ rc = of_property_read_u32(of_lpg_node, "qcom,lpg-lut-pause-lo",
+ &lut_config->lut_pause_lo_cnt);
+ if (rc && rc != -EINVAL)
+ goto out;
+
+ rc = of_property_read_u32(of_lpg_node, "qcom,lpg-lut-ramp-direction",
+ (u32 *)&lut_config->ramp_direction);
+ if (rc && rc != -EINVAL)
+ goto out;
+
+ rc = of_property_read_u32(of_lpg_node, "qcom,lpg-lut-pattern-repeat",
+ (u32 *)&lut_config->pattern_repeat);
+ if (rc && rc != -EINVAL)
+ goto out;
+
+ rc = of_property_read_u32(of_lpg_node, "qcom,lpg-lut-ramp-toggle",
+ (u32 *)&lut_config->ramp_toggle);
+ if (rc && rc != -EINVAL)
+ goto out;
+
+ rc = of_property_read_u32(of_lpg_node, "qcom,lpg-lut-enable-pause-hi",
+ (u32 *)&lut_config->enable_pause_hi);
+ if (rc && rc != -EINVAL)
+ goto out;
+
+ rc = of_property_read_u32(of_lpg_node, "qcom,lpg-lut-enable-pause-lo",
+ (u32 *)&lut_config->enable_pause_lo);
+ if (rc && rc != -EINVAL)
+ goto out;
+ rc = 0;
+
+ qpnp_set_lut_params(&lut_params, lut_config, start_idx, list_size);
+
+ _pwm_lut_config(chip, period, duty_pct_list, lut_params);
+
+out:
+ kfree(duty_pct_list);
+ return rc;
+}
+
+static int qpnp_lpg_get_rev_subtype(struct qpnp_pwm_chip *chip)
+{
+ int rc;
+ uint val;
+
+ rc = regmap_read(chip->regmap,
+ chip->lpg_config.base_addr + SPMI_LPG_SUB_TYPE_OFFSET,
+ &val);
+
+ if (rc) {
+ pr_err("Couldn't read subtype rc: %d\n", rc);
+ goto out;
+ }
+ chip->sub_type = (u8)val;
+
+ rc = regmap_read(chip->regmap,
+ chip->lpg_config.base_addr + SPMI_LPG_REVISION2_OFFSET,
+ &val);
+
+ if (rc) {
+ pr_err("Couldn't read revision2 rc: %d\n", rc);
+ goto out;
+ }
+ chip->revision = (u8)val;
+
+ if (chip->revision < QPNP_LPG_REVISION_0 ||
+ chip->revision > QPNP_LPG_REVISION_1) {
+ pr_err("Unknown LPG revision detected, rev:%d\n",
+ chip->revision);
+ rc = -EINVAL;
+ goto out;
+ }
+
+ if (chip->sub_type != QPNP_PWM_MODE_ONLY_SUB_TYPE
+ && chip->sub_type != QPNP_LPG_CHAN_SUB_TYPE
+ && chip->sub_type != QPNP_LPG_S_CHAN_SUB_TYPE) {
+ pr_err("Unknown LPG/PWM subtype detected, subtype:%d\n",
+ chip->sub_type);
+ rc = -EINVAL;
+ }
+out:
+ pr_debug("LPG rev 0x%02x subtype 0x%02x rc: %d\n", chip->revision,
+ chip->sub_type, rc);
+ return rc;
+}
+
+/* Fill in lpg device elements based on values found in device tree. */
+static int qpnp_parse_dt_config(struct platform_device *pdev,
+ struct qpnp_pwm_chip *chip)
+{
+ int rc, enable, lut_entry_size, list_size, i;
+ const char *label;
+ const __be32 *prop;
+ u32 size;
+ struct device_node *node;
+ int found_pwm_subnode = 0;
+ int found_lpg_subnode = 0;
+ struct device_node *of_node = pdev->dev.of_node;
+ struct qpnp_lpg_config *lpg_config = &chip->lpg_config;
+ struct qpnp_lut_config *lut_config = &lpg_config->lut_config;
+ struct _qpnp_pwm_config *pwm_config = &chip->pwm_config;
+ int force_pwm_size = 0;
+ int pwm_size_list[QPNP_PWM_SIZES_SUPPORTED];
+
+ rc = of_property_read_u32(of_node, "qcom,channel-id",
+ &chip->channel_id);
+ if (rc) {
+ dev_err(&pdev->dev, "%s: node is missing LPG channel id\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ if (!of_get_property(of_node, "qcom,supported-sizes", &list_size)) {
+ pr_err("Missing qcom,supported-size list\n");
+ return -EINVAL;
+ }
+
+ list_size /= sizeof(u32);
+ if (list_size > QPNP_PWM_SIZES_SUPPORTED) {
+ pr_err(" qcom,supported-size list is too big\n");
+ return -EINVAL;
+ }
+
+ rc = of_property_read_u32_array(of_node, "qcom,supported-sizes",
+ pwm_size_list, list_size);
+
+ if (rc) {
+ pr_err("Invalid qcom,supported-size property\n");
+ return rc;
+ }
+
+ for (i = 0; i < list_size; i++) {
+ pwm_config->supported_sizes |=
+ (1 << (pwm_size_list[i] - QPNP_MIN_PWM_BIT_SIZE));
+ }
+
+ if (!(pwm_config->supported_sizes == QPNP_PWM_SIZE_6_9_BIT ||
+ pwm_config->supported_sizes == QPNP_PWM_SIZE_7_8_BIT ||
+ pwm_config->supported_sizes == QPNP_PWM_SIZE_6_7_9_BIT)) {
+ pr_err("PWM sizes list qcom,supported-size is not proper\n");
+ return -EINVAL;
+ }
+
+ /*
+ * For cetrain LPG channels PWM size can be forced. So that
+ * for every requested pwm period closest pwm frequency is
+ * selected in qpnp_lpg_calc_period() for the forced pwm size.
+ */
+ rc = of_property_read_u32(of_node, "qcom,force-pwm-size",
+ &force_pwm_size);
+ if (pwm_config->supported_sizes == QPNP_PWM_SIZE_7_8_BIT) {
+ if (!(force_pwm_size == QPNP_PWM_SIZE_7_BIT ||
+ force_pwm_size == QPNP_PWM_SIZE_8_BIT))
+ force_pwm_size = 0;
+ } else if (chip->sub_type == QPNP_PWM_MODE_ONLY_SUB_TYPE) {
+ if (!(force_pwm_size == QPNP_PWM_SIZE_6_BIT ||
+ force_pwm_size == QPNP_PWM_SIZE_9_BIT))
+ force_pwm_size = 0;
+ } else if (pwm_config->supported_sizes == QPNP_PWM_SIZE_6_7_9_BIT) {
+ if (!(force_pwm_size == QPNP_PWM_SIZE_6_BIT ||
+ force_pwm_size == QPNP_PWM_SIZE_7_BIT ||
+ force_pwm_size == QPNP_PWM_SIZE_9_BIT))
+ force_pwm_size = 0;
+ }
+
+ pwm_config->force_pwm_size = force_pwm_size;
+
+
+ prop = of_get_address_by_name(pdev->dev.of_node, QPNP_LPG_CHANNEL_BASE,
+ 0, 0);
+ if (!prop) {
+ dev_err(&pdev->dev, "Couldnt find channel's base addr rc %d\n",
+ rc);
+ return rc;
+ }
+ lpg_config->base_addr = be32_to_cpu(*prop);
+
+ rc = qpnp_lpg_get_rev_subtype(chip);
+ if (rc)
+ return rc;
+
+ prop = of_get_address_by_name(pdev->dev.of_node, QPNP_LPG_LUT_BASE,
+ 0, 0);
+ if (!prop) {
+ chip->flags |= QPNP_PWM_LUT_NOT_SUPPORTED;
+ } else {
+ lpg_config->lut_base_addr = be32_to_cpu(*prop);
+ rc = of_property_read_u32(of_node, "qcom,lpg-lut-size", &size);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "Error reading qcom,lpg-lut-size, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ /*
+ * Each entry of LUT is of 2 bytes for generic LUT and of 1 byte
+ * for KPDBL/GLED LUT.
+ */
+ lpg_config->lut_size = size >> 1;
+ lut_entry_size = sizeof(u16);
+
+ if (pwm_config->supported_sizes == QPNP_PWM_SIZE_7_8_BIT) {
+ lpg_config->lut_size = size;
+ lut_entry_size = sizeof(u8);
+ }
+
+ lut_config->duty_pct_list = kcalloc(lpg_config->lut_size,
+ lut_entry_size, GFP_KERNEL);
+ if (!lut_config->duty_pct_list)
+ return -ENOMEM;
+
+ rc = of_property_read_u32(of_node, "qcom,ramp-index",
+ &lut_config->ramp_index);
+ if (rc) {
+ pr_err("Missing LPG qcom,ramp-index property\n");
+ kfree(lut_config->duty_pct_list);
+ return rc;
+ }
+ }
+
+ rc = of_property_read_u32(of_node, "qcom,dtest-line",
+ &chip->dtest_line);
+ if (rc) {
+ chip->in_test_mode = 0;
+ } else {
+ rc = of_property_read_u32(of_node, "qcom,dtest-output",
+ &chip->dtest_output);
+ if (rc) {
+ pr_err("Missing DTEST output configuration\n");
+ return rc;
+ }
+ chip->in_test_mode = 1;
+ }
+
+ if (chip->in_test_mode) {
+ if ((chip->sub_type == QPNP_PWM_MODE_ONLY_SUB_TYPE) &&
+ (chip->dtest_line > QPNP_PWM_DTEST_LINE_MAX ||
+ chip->dtest_output > QPNP_PWM_DTEST_OUTPUT_MAX)) {
+ pr_err("DTEST line/output values are improper for PWM channel %d\n",
+ chip->channel_id);
+ return -EINVAL;
+ } else if (chip->dtest_line > QPNP_LPG_DTEST_LINE_MAX ||
+ chip->dtest_output > QPNP_LPG_DTEST_OUTPUT_MAX) {
+ pr_err("DTEST line/output values are improper for LPG channel %d\n",
+ chip->channel_id);
+ return -EINVAL;
+ }
+ }
+
+ for_each_child_of_node(of_node, node) {
+ rc = of_property_read_string(node, "label", &label);
+ if (rc) {
+ dev_err(&pdev->dev, "%s: Missing label property\n",
+ __func__);
+ goto out;
+ }
+ if (!strcmp(label, "pwm")) {
+ rc = qpnp_parse_pwm_dt_config(node, of_node, chip);
+ if (rc)
+ goto out;
+ found_pwm_subnode = 1;
+ } else if (!strcmp(label, "lpg") &&
+ !(chip->flags & QPNP_PWM_LUT_NOT_SUPPORTED)) {
+ rc = qpnp_parse_lpg_dt_config(node, of_node, chip);
+ if (rc)
+ goto out;
+ found_lpg_subnode = 1;
+ } else {
+ dev_err(&pdev->dev,
+ "%s: Invalid value for lable prop",
+ __func__);
+ }
+ }
+
+ rc = of_property_read_u32(of_node, "qcom,mode-select", &enable);
+ if (rc)
+ goto read_opt_props;
+
+ if ((enable == PM_PWM_MODE_PWM && found_pwm_subnode == 0) ||
+ (enable == PM_PWM_MODE_LPG && found_lpg_subnode == 0)) {
+ dev_err(&pdev->dev, "%s: Invalid mode select\n", __func__);
+ rc = -EINVAL;
+ goto out;
+ }
+
+ _pwm_change_mode(chip, enable);
+ _pwm_enable(chip);
+
+read_opt_props:
+ /* Initialize optional config parameters from DT if provided */
+ of_property_read_string(node, "qcom,channel-owner",
+ &chip->channel_owner);
+
+ return 0;
+
+out:
+ kfree(lut_config->duty_pct_list);
+ return rc;
+}
+
+static struct pwm_ops qpnp_pwm_ops = {
+ .enable = qpnp_pwm_enable,
+ .disable = qpnp_pwm_disable,
+ .config = qpnp_pwm_config,
+ .free = qpnp_pwm_free,
+ .owner = THIS_MODULE,
+};
+
+static int qpnp_pwm_probe(struct platform_device *pdev)
+{
+ struct qpnp_pwm_chip *pwm_chip;
+ int rc;
+
+ pwm_chip = kzalloc(sizeof(*pwm_chip), GFP_KERNEL);
+ if (pwm_chip == NULL)
+ return -ENOMEM;
+
+ pwm_chip->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!pwm_chip->regmap) {
+ dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+ return -EINVAL;
+ }
+
+ spin_lock_init(&pwm_chip->lpg_lock);
+
+ pwm_chip->pdev = pdev;
+ dev_set_drvdata(&pdev->dev, pwm_chip);
+
+ rc = qpnp_parse_dt_config(pdev, pwm_chip);
+
+ if (rc) {
+ pr_err("Failed parsing DT parameters, rc=%d\n", rc);
+ goto failed_config;
+ }
+
+ pwm_chip->chip.dev = &pdev->dev;
+ pwm_chip->chip.ops = &qpnp_pwm_ops;
+ pwm_chip->chip.base = -1;
+ pwm_chip->chip.npwm = 1;
+
+ rc = pwmchip_add(&pwm_chip->chip);
+ if (rc < 0) {
+ pr_err("pwmchip_add() failed: %d\n", rc);
+ goto failed_insert;
+ }
+
+ if (pwm_chip->channel_owner)
+ pwm_chip->chip.pwms[0].label = pwm_chip->channel_owner;
+
+ pr_debug("PWM device channel:%d probed successfully\n",
+ pwm_chip->channel_id);
+ return 0;
+
+failed_insert:
+ kfree(pwm_chip->lpg_config.lut_config.duty_pct_list);
+failed_config:
+ dev_set_drvdata(&pdev->dev, NULL);
+ kfree(pwm_chip);
+ return rc;
+}
+
+static int qpnp_pwm_remove(struct platform_device *pdev)
+{
+ struct qpnp_pwm_chip *pwm_chip;
+ struct qpnp_lpg_config *lpg_config;
+
+ pwm_chip = dev_get_drvdata(&pdev->dev);
+
+ dev_set_drvdata(&pdev->dev, NULL);
+
+ if (pwm_chip) {
+ lpg_config = &pwm_chip->lpg_config;
+ pwmchip_remove(&pwm_chip->chip);
+ kfree(lpg_config->lut_config.duty_pct_list);
+ kfree(pwm_chip);
+ }
+
+ return 0;
+}
+
+static const struct of_device_id spmi_match_table[] = {
+ { .compatible = QPNP_LPG_DRIVER_NAME, },
+ {}
+};
+
+static const struct platform_device_id qpnp_lpg_id[] = {
+ { QPNP_LPG_DRIVER_NAME, 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(spmi, qpnp_lpg_id);
+
+static struct platform_driver qpnp_lpg_driver = {
+ .driver = {
+ .name = QPNP_LPG_DRIVER_NAME,
+ .of_match_table = spmi_match_table,
+ .owner = THIS_MODULE,
+ },
+ .probe = qpnp_pwm_probe,
+ .remove = qpnp_pwm_remove,
+ .id_table = qpnp_lpg_id,
+};
+
+/**
+ * qpnp_lpg_init() - register spmi driver for qpnp-lpg
+ */
+int __init qpnp_lpg_init(void)
+{
+ return platform_driver_register(&qpnp_lpg_driver);
+}
+
+static void __exit qpnp_lpg_exit(void)
+{
+ platform_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);
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 96a816d..534a1d7 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -988,6 +988,35 @@
the higher ones by BHS. This driver allows for configuration of
the rail between the LDO/BHS as well as the LDO voltage.
+config REGULATOR_QPNP_LABIBB
+ tristate "Qualcomm Technologies, Inc. QPNP LAB/IBB regulator support"
+ depends on SPMI
+ help
+ This driver supports voltage regulators in Qualcomm Technologies, Inc.
+ PMIC chips which comply with QPNP LAB/IBB regulators. QPNP LAB and IBB
+ are SPMI based PMIC implementations. LAB regulator can be used as a
+ regular positive boost regulator. IBB can be used as a regular
+ negative boost regulator. LAB/IBB regulators can also be used
+ together for LCD or AMOLED.
+
+config REGULATOR_QPNP_LCDB
+ tristate "Qualcomm Technologies, Inc. QPNP LCDB support"
+ depends on SPMI
+ help
+ Supports the LCDB module in the Qualcomm Technologies, Inc.
+ QPNP PMICs. Exposes regulators to control the positive and
+ negative voltage bias for the LCD display panel. It also
+ allows configurability for the various bias-voltage parameters.
+
+config REGULATOR_QPNP_OLEDB
+ tristate "Qualcomm Technologies, Inc. QPNP OLEDB regulator support"
+ depends on SPMI
+ help
+ This driver supports the OLEDB (AVDD bias) signal for AMOLED panel in
+ Qualcomm Technologies, Inc. QPNP PMICs. It exposes the OLED voltage
+ configuration via the regulator framework. The configurable range of
+ this bias is 5 V to 8.1 V.
+
config REGULATOR_QPNP
tristate "Qualcomm Technologies, Inc. QPNP regulator support"
depends on SPMI
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 1a96c07..bf30b8d 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -123,6 +123,9 @@
obj-$(CONFIG_REGULATOR_KRYO) += kryo-regulator.o
obj-$(CONFIG_REGULATOR_MEM_ACC) += mem-acc-regulator.o
obj-$(CONFIG_REGULATOR_MSM_GFX_LDO) += msm_gfx_ldo.o
+obj-$(CONFIG_REGULATOR_QPNP_LABIBB) += qpnp-labibb-regulator.o
+obj-$(CONFIG_REGULATOR_QPNP_LCDB) += qpnp-lcdb-regulator.o
+obj-$(CONFIG_REGULATOR_QPNP_OLEDB) += qpnp-oledb-regulator.o
obj-$(CONFIG_REGULATOR_QPNP) += qpnp-regulator.o
obj-$(CONFIG_REGULATOR_RPMH) += rpmh-regulator.o
obj-$(CONFIG_REGULATOR_STUB) += stub-regulator.o
diff --git a/drivers/regulator/qpnp-labibb-regulator.c b/drivers/regulator/qpnp-labibb-regulator.c
new file mode 100644
index 0000000..cf8f000
--- /dev/null
+++ b/drivers/regulator/qpnp-labibb-regulator.c
@@ -0,0 +1,3877 @@
+/* Copyright (c) 2014-2017, The Linux Foundation. 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.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/regmap.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/string.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/qpnp/qpnp-revid.h>
+
+#define QPNP_LABIBB_REGULATOR_DRIVER_NAME "qcom,qpnp-labibb-regulator"
+
+#define REG_REVISION_2 0x01
+#define REG_PERPH_TYPE 0x04
+
+#define QPNP_LAB_TYPE 0x24
+#define QPNP_IBB_TYPE 0x20
+
+/* Common register value for LAB/IBB */
+#define REG_LAB_IBB_LCD_MODE 0x0
+#define REG_LAB_IBB_AMOLED_MODE BIT(7)
+#define REG_LAB_IBB_SEC_ACCESS 0xD0
+#define REG_LAB_IBB_SEC_UNLOCK_CODE 0xA5
+
+/* LAB register offset definitions */
+#define REG_LAB_STATUS1 0x08
+#define REG_LAB_SWIRE_PGM_CTL 0x40
+#define REG_LAB_VOLTAGE 0x41
+#define REG_LAB_RING_SUPPRESSION_CTL 0x42
+#define REG_LAB_LCD_AMOLED_SEL 0x44
+#define REG_LAB_MODULE_RDY 0x45
+#define REG_LAB_ENABLE_CTL 0x46
+#define REG_LAB_PD_CTL 0x47
+#define REG_LAB_CLK_DIV 0x48
+#define REG_LAB_IBB_EN_RDY 0x49
+#define REG_LAB_CURRENT_LIMIT 0x4B
+#define REG_LAB_CURRENT_SENSE 0x4C
+#define REG_LAB_PS_CTL 0x50
+#define REG_LAB_RDSON_MNGMNT 0x53
+#define REG_LAB_PRECHARGE_CTL 0x5E
+#define REG_LAB_SOFT_START_CTL 0x5F
+#define REG_LAB_SPARE_CTL 0x60
+#define REG_LAB_PFM_CTL 0x62
+
+/* LAB registers for PM660A */
+#define REG_LAB_VOUT_DEFAULT 0x44
+#define REG_LAB_SW_HIGH_PSRR_CTL 0x70
+#define REG_LAB_LDO_PD_CTL 0x78
+#define REG_LAB_VPH_ENVELOP_CTL 0x7E
+
+/* LAB register bits definitions */
+
+/* REG_LAB_STATUS1 */
+#define LAB_STATUS1_VREG_OK_MASK BIT(7)
+#define LAB_STATUS1_VREG_OK BIT(7)
+
+/* REG_LAB_SWIRE_PGM_CTL */
+#define LAB_EN_SWIRE_PGM_VOUT BIT(7)
+#define LAB_EN_SWIRE_PGM_PD BIT(6)
+
+/* REG_LAB_VOLTAGE */
+#define LAB_VOLTAGE_OVERRIDE_EN BIT(7)
+#define LAB_VOLTAGE_SET_MASK GENMASK(3, 0)
+
+/* REG_LAB_RING_SUPPRESSION_CTL */
+#define LAB_RING_SUPPRESSION_CTL_EN BIT(7)
+
+/* REG_LAB_MODULE_RDY */
+#define LAB_MODULE_RDY_EN BIT(7)
+
+/* REG_LAB_ENABLE_CTL */
+#define LAB_ENABLE_CTL_EN BIT(7)
+
+/* REG_LAB_PD_CTL */
+#define LAB_PD_CTL_STRONG_PULL BIT(0)
+#define LAB_PD_CTL_STRENGTH_MASK BIT(0)
+#define LAB_PD_CTL_DISABLE_PD BIT(1)
+#define LAB_PD_CTL_EN_MASK BIT(1)
+
+/* REG_LAB_IBB_EN_RDY */
+#define LAB_IBB_EN_RDY_EN BIT(7)
+
+/* REG_LAB_CURRENT_LIMIT */
+#define LAB_CURRENT_LIMIT_MASK GENMASK(2, 0)
+#define LAB_CURRENT_LIMIT_EN_BIT BIT(7)
+#define LAB_OVERRIDE_CURRENT_MAX_BIT BIT(3)
+
+/* REG_LAB_CURRENT_SENSE */
+#define LAB_CURRENT_SENSE_GAIN_MASK GENMASK(1, 0)
+
+/* REG_LAB_PS_CTL */
+#define LAB_PS_THRESH_MASK GENMASK(1, 0)
+#define LAB_PS_CTL_EN BIT(7)
+
+/* REG_LAB_RDSON_MNGMNT */
+#define LAB_RDSON_MNGMNT_NFET_SLEW_EN BIT(5)
+#define LAB_RDSON_MNGMNT_PFET_SLEW_EN BIT(4)
+#define LAB_RDSON_MNGMNT_NFET_MASK GENMASK(3, 2)
+#define LAB_RDSON_MNGMNT_NFET_SHIFT 2
+#define LAB_RDSON_MNGMNT_PFET_MASK GENMASK(1, 0)
+#define LAB_RDSON_NFET_SW_SIZE_QUARTER 0x0
+#define LAB_RDSON_PFET_SW_SIZE_QUARTER 0x0
+
+/* REG_LAB_PRECHARGE_CTL */
+#define LAB_FAST_PRECHARGE_CTL_EN BIT(2)
+#define LAB_MAX_PRECHARGE_TIME_MASK GENMASK(1, 0)
+
+/* REG_LAB_SOFT_START_CTL */
+#define LAB_SOFT_START_CTL_MASK GENMASK(1, 0)
+
+/* REG_LAB_SPARE_CTL */
+#define LAB_SPARE_TOUCH_WAKE_BIT BIT(3)
+#define LAB_SPARE_DISABLE_SCP_BIT BIT(0)
+
+/* REG_LAB_PFM_CTL */
+#define LAB_PFM_EN_BIT BIT(7)
+
+/* REG_LAB_SW_HIGH_PSRR_CTL */
+#define LAB_EN_SW_HIGH_PSRR_MODE BIT(7)
+#define LAB_SW_HIGH_PSRR_REQ BIT(0)
+
+/* REG_LAB_VPH_ENVELOP_CTL */
+#define LAB_VREF_HIGH_PSRR_SEL_MASK GENMASK(7, 6)
+#define LAB_SEL_HW_HIGH_PSRR_SRC_MASK GENMASK(1, 0)
+#define LAB_SEL_HW_HIGH_PSRR_SRC_SHIFT 6
+
+/* IBB register offset definitions */
+#define REG_IBB_REVISION4 0x03
+#define REG_IBB_STATUS1 0x08
+#define REG_IBB_VOLTAGE 0x41
+#define REG_IBB_RING_SUPPRESSION_CTL 0x42
+#define REG_IBB_LCD_AMOLED_SEL 0x44
+#define REG_IBB_MODULE_RDY 0x45
+#define REG_IBB_ENABLE_CTL 0x46
+#define REG_IBB_PD_CTL 0x47
+#define REG_IBB_CLK_DIV 0x48
+#define REG_IBB_CURRENT_LIMIT 0x4B
+#define REG_IBB_PS_CTL 0x50
+#define REG_IBB_RDSON_MNGMNT 0x53
+#define REG_IBB_NONOVERLAP_TIME_1 0x56
+#define REG_IBB_NONOVERLAP_TIME_2 0x57
+#define REG_IBB_PWRUP_PWRDN_CTL_1 0x58
+#define REG_IBB_PWRUP_PWRDN_CTL_2 0x59
+#define REG_IBB_SOFT_START_CTL 0x5F
+#define REG_IBB_SWIRE_CTL 0x5A
+#define REG_IBB_OUTPUT_SLEW_CTL 0x5D
+#define REG_IBB_SPARE_CTL 0x60
+#define REG_IBB_NLIMIT_DAC 0x61
+
+/* IBB registers for PM660A */
+#define REG_IBB_DEFAULT_VOLTAGE 0x40
+#define REG_IBB_FLOAT_CTL 0x43
+#define REG_IBB_VREG_OK_CTL 0x55
+#define REG_IBB_VOUT_MIN_MAGNITUDE 0x5C
+#define REG_IBB_PFM_CTL 0x62
+#define REG_IBB_SMART_PS_CTL 0x65
+#define REG_IBB_ADAPT_DEAD_TIME 0x67
+
+/* IBB register bits definition */
+
+/* REG_IBB_STATUS1 */
+#define IBB_STATUS1_VREG_OK_MASK BIT(7)
+#define IBB_STATUS1_VREG_OK BIT(7)
+
+/* REG_IBB_VOLTAGE */
+#define IBB_VOLTAGE_OVERRIDE_EN BIT(7)
+#define IBB_VOLTAGE_SET_MASK GENMASK(5, 0)
+
+/* REG_IBB_CLK_DIV */
+#define IBB_CLK_DIV_OVERRIDE_EN BIT(7)
+#define IBB_CLK_DIV_MASK GENMASK(3, 0)
+
+/* REG_IBB_RING_SUPPRESSION_CTL */
+#define IBB_RING_SUPPRESSION_CTL_EN BIT(7)
+
+/* REG_IBB_FLOAT_CTL */
+#define IBB_FLOAT_EN BIT(0)
+#define IBB_SMART_FLOAT_EN BIT(7)
+
+/* REG_IBB_MIN_MAGNITUDE */
+#define IBB_MIN_VOLTAGE_0P8_V BIT(3)
+
+/* REG_IBB_MODULE_RDY */
+#define IBB_MODULE_RDY_EN BIT(7)
+
+/* REG_IBB_ENABLE_CTL */
+#define IBB_ENABLE_CTL_MASK (BIT(7) | BIT(6))
+#define IBB_ENABLE_CTL_SWIRE_RDY BIT(6)
+#define IBB_ENABLE_CTL_MODULE_EN BIT(7)
+
+/* REG_IBB_PD_CTL */
+#define IBB_PD_CTL_HALF_STRENGTH BIT(0)
+#define IBB_PD_CTL_STRENGTH_MASK BIT(0)
+#define IBB_PD_CTL_EN BIT(7)
+#define IBB_SWIRE_PD_UPD BIT(1)
+#define IBB_PD_CTL_EN_MASK BIT(7)
+
+/* REG_IBB_CURRENT_LIMIT */
+#define IBB_CURRENT_LIMIT_MASK GENMASK(4, 0)
+#define IBB_CURRENT_LIMIT_DEBOUNCE_SHIFT 5
+#define IBB_CURRENT_LIMIT_DEBOUNCE_MASK GENMASK(6, 5)
+#define IBB_CURRENT_LIMIT_EN BIT(7)
+#define IBB_ILIMIT_COUNT_CYC8 0
+#define IBB_CURRENT_MAX_500MA 0xA
+
+/* REG_IBB_PS_CTL */
+#define IBB_PS_CTL_EN 0x85
+
+/* REG_IBB_SMART_PS_CTL */
+#define IBB_SMART_PS_CTL_EN BIT(7)
+#define IBB_NUM_SWIRE_PULSE_WAIT 0x5
+
+/* REG_IBB_OUTPUT_SLEW_CTL */
+#define IBB_SLEW_CTL_EN BIT(7)
+#define IBB_SLEW_RATE_SPEED_FAST_EN BIT(6)
+#define IBB_SLEW_RATE_TRANS_TIME_FAST_SHIFT 3
+#define IBB_SLEW_RATE_TRANS_TIME_FAST_MASK GENMASK(5, 3)
+#define IBB_SLEW_RATE_TRANS_TIME_SLOW_MASK GENMASK(2, 0)
+
+/* REG_IBB_VREG_OK_CTL */
+#define IBB_VREG_OK_EN_OVERLOAD_BLANK BIT(7)
+#define IBB_VREG_OK_OVERLOAD_DEB_SHIFT 5
+#define IBB_VREG_OK_OVERLOAD_DEB_MASK GENMASK(6, 5)
+
+/* REG_IBB_RDSON_MNGMNT */
+#define IBB_NFET_SLEW_EN BIT(7)
+#define IBB_PFET_SLEW_EN BIT(6)
+#define IBB_OVERRIDE_NFET_SW_SIZE BIT(5)
+#define IBB_OVERRIDE_PFET_SW_SIZE BIT(2)
+#define IBB_NFET_SW_SIZE_MASK GENMASK(3, 2)
+#define IBB_PFET_SW_SIZE_MASK GENMASK(1, 0)
+
+/* REG_IBB_NONOVERLAP_TIME_1 */
+#define IBB_OVERRIDE_NONOVERLAP BIT(6)
+#define IBB_NONOVERLAP_NFET_MASK GENMASK(2, 0)
+#define IBB_NFET_GATE_DELAY_2 0x3
+
+/* REG_IBB_NONOVERLAP_TIME_2 */
+#define IBB_N2P_MUX_SEL BIT(0)
+
+/* REG_IBB_SOFT_START_CTL */
+#define IBB_SOFT_START_CHARGING_RESISTOR_16K 0x3
+
+/* REG_IBB_SPARE_CTL */
+#define IBB_BYPASS_PWRDN_DLY2_BIT BIT(5)
+#define IBB_POFF_CTL_MASK BIT(4)
+#define IBB_FASTER_PFET_OFF BIT(4)
+#define IBB_FAST_STARTUP BIT(3)
+
+/* REG_IBB_SWIRE_CTL */
+#define IBB_SWIRE_VOUT_UPD_EN BIT(6)
+#define IBB_OUTPUT_VOLTAGE_AT_ONE_PULSE_MASK GENMASK(5, 0)
+#define MAX_OUTPUT_EDGE_VOLTAGE_MV 6300
+#define MAX_OUTPUT_PULSE_VOLTAGE_MV 7700
+#define MIN_OUTPUT_PULSE_VOLTAGE_MV 1400
+#define OUTPUT_VOLTAGE_STEP_MV 100
+
+/* REG_IBB_NLIMIT_DAC */
+#define IBB_DEFAULT_NLIMIT_DAC 0x5
+
+/* REG_IBB_PFM_CTL */
+#define IBB_PFM_ENABLE BIT(7)
+#define IBB_PFM_PEAK_CURRENT_BIT_SHIFT 1
+#define IBB_PFM_PEAK_CURRENT_MASK GENMASK(3, 1)
+#define IBB_PFM_HYSTERESIS_BIT_SHIFT 4
+#define IBB_PFM_HYSTERESIS_MASK GENMASK(5, 4)
+
+/* REG_IBB_PWRUP_PWRDN_CTL_1 */
+#define IBB_PWRUP_PWRDN_CTL_1_DLY1_BITS 2
+#define IBB_PWRUP_PWRDN_CTL_1_DLY1_MASK GENMASK(5, 4)
+#define IBB_PWRUP_PWRDN_CTL_1_DLY1_SHIFT 4
+#define IBB_PWRUP_PWRDN_CTL_1_EN_DLY2 BIT(3)
+#define IBB_PWRUP_PWRDN_CTL_1_DLY2_MASK GENMASK(1, 0)
+#define IBB_PWRUP_PWRDN_CTL_1_LAB_VREG_OK BIT(7)
+#define IBB_PWRUP_PWRDN_CTL_1_EN_DLY1 BIT(6)
+#define PWRUP_PWRDN_CTL_1_DISCHARGE_EN BIT(2)
+
+/* REG_IBB_PWRUP_PWRDN_CTL_2 */
+#define IBB_DIS_DLY_MASK GENMASK(1, 0)
+#define IBB_WAIT_MBG_OK BIT(2)
+
+/* Constants */
+#define SWIRE_DEFAULT_2ND_CMD_DLY_MS 20
+#define SWIRE_DEFAULT_IBB_PS_ENABLE_DLY_MS 200
+#define IBB_HW_DEFAULT_SLEW_RATE 12000
+
+/**
+ * enum qpnp_labibb_mode - working mode of LAB/IBB regulators
+ * %QPNP_LABIBB_LCD_MODE: configure LAB and IBB regulators
+ * together to provide power supply for LCD
+ * %QPNP_LABIBB_AMOLED_MODE: configure LAB and IBB regulators
+ * together to provide power supply for AMOLED
+ * %QPNP_LABIBB_MAX_MODE max number of configureable modes
+ * supported by qpnp_labibb_regulator
+ */
+enum qpnp_labibb_mode {
+ QPNP_LABIBB_LCD_MODE,
+ QPNP_LABIBB_AMOLED_MODE,
+ QPNP_LABIBB_MAX_MODE,
+};
+
+/**
+ * IBB_SW_CONTROL_EN: Specifies IBB is enabled through software.
+ * IBB_SW_CONTROL_DIS: Specifies IBB is disabled through software.
+ * IBB_HW_CONTROL: Specifies IBB is controlled through SWIRE (hardware).
+ */
+enum ibb_mode {
+ IBB_SW_CONTROL_EN,
+ IBB_SW_CONTROL_DIS,
+ IBB_HW_CONTROL,
+ IBB_HW_SW_CONTROL,
+};
+
+static const int ibb_dischg_res_table[] = {
+ 300,
+ 64,
+ 32,
+ 16,
+};
+
+static const int ibb_pwrup_dly_table[] = {
+ 1000,
+ 2000,
+ 4000,
+ 8000,
+};
+
+static const int ibb_pwrdn_dly_table[] = {
+ 1000,
+ 2000,
+ 4000,
+ 8000,
+};
+
+static const int lab_clk_div_table[] = {
+ 3200,
+ 2740,
+ 2400,
+ 2130,
+ 1920,
+ 1750,
+ 1600,
+ 1480,
+ 1370,
+ 1280,
+ 1200,
+ 1130,
+ 1070,
+ 1010,
+ 960,
+ 910,
+};
+
+static const int ibb_clk_div_table[] = {
+ 3200,
+ 2740,
+ 2400,
+ 2130,
+ 1920,
+ 1750,
+ 1600,
+ 1480,
+ 1370,
+ 1280,
+ 1200,
+ 1130,
+ 1070,
+ 1010,
+ 960,
+ 910,
+};
+
+static const int lab_current_limit_table[] = {
+ 200,
+ 400,
+ 600,
+ 800,
+ 1000,
+ 1200,
+ 1400,
+ 1600,
+};
+
+static const char * const lab_current_sense_table[] = {
+ "0.5x",
+ "1x",
+ "1.5x",
+ "2x"
+};
+
+static const int ibb_current_limit_table[] = {
+ 0,
+ 50,
+ 100,
+ 150,
+ 200,
+ 250,
+ 300,
+ 350,
+ 400,
+ 450,
+ 500,
+ 550,
+ 600,
+ 650,
+ 700,
+ 750,
+ 800,
+ 850,
+ 900,
+ 950,
+ 1000,
+ 1050,
+ 1100,
+ 1150,
+ 1200,
+ 1250,
+ 1300,
+ 1350,
+ 1400,
+ 1450,
+ 1500,
+ 1550,
+};
+
+static const int ibb_output_slew_ctl_table[] = {
+ 100,
+ 200,
+ 500,
+ 1000,
+ 2000,
+ 10000,
+ 12000,
+ 15000
+};
+
+static const int ibb_debounce_table[] = {
+ 8,
+ 16,
+ 32,
+ 64,
+};
+
+static const int ibb_overload_debounce_table[] = {
+ 1,
+ 2,
+ 4,
+ 8
+};
+
+static const int ibb_vreg_ok_deb_table[] = {
+ 4,
+ 8,
+ 16,
+ 32
+};
+
+static const int lab_ps_thresh_table_v1[] = {
+ 20,
+ 30,
+ 40,
+ 50,
+};
+
+static const int lab_ps_thresh_table_v2[] = {
+ 50,
+ 60,
+ 70,
+ 80,
+};
+
+static const int lab_soft_start_table[] = {
+ 200,
+ 400,
+ 600,
+ 800,
+};
+
+static const int lab_rdson_nfet_table[] = {
+ 25,
+ 50,
+ 75,
+ 100,
+};
+
+static const int lab_rdson_pfet_table[] = {
+ 25,
+ 50,
+ 75,
+ 100,
+};
+
+static const int lab_max_precharge_table[] = {
+ 200,
+ 300,
+ 400,
+ 500,
+};
+
+static const int ibb_pfm_peak_curr_table[] = {
+ 150,
+ 200,
+ 250,
+ 300,
+ 350,
+ 400,
+ 450,
+ 500
+};
+
+static const int ibb_pfm_hysteresis_table[] = {
+ 0,
+ 25,
+ 50,
+ 0
+};
+
+static const int lab_vref_high_psrr_table[] = {
+ 350,
+ 400,
+ 450,
+ 500
+};
+
+struct lab_regulator {
+ struct regulator_desc rdesc;
+ struct regulator_dev *rdev;
+ struct mutex lab_mutex;
+
+ int lab_vreg_ok_irq;
+ int curr_volt;
+ int min_volt;
+
+ int step_size;
+ int slew_rate;
+ int soft_start;
+
+ int vreg_enabled;
+};
+
+struct ibb_regulator {
+ struct regulator_desc rdesc;
+ struct regulator_dev *rdev;
+ struct mutex ibb_mutex;
+
+ int curr_volt;
+ int min_volt;
+
+ int step_size;
+ int slew_rate;
+ int soft_start;
+
+ u32 pwrup_dly;
+ u32 pwrdn_dly;
+
+ int vreg_enabled;
+ int num_swire_trans;
+};
+
+struct qpnp_labibb {
+ struct device *dev;
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ struct pmic_revid_data *pmic_rev_id;
+ u16 lab_base;
+ u16 ibb_base;
+ u8 lab_dig_major;
+ u8 ibb_dig_major;
+ struct lab_regulator lab_vreg;
+ struct ibb_regulator ibb_vreg;
+ const struct ibb_ver_ops *ibb_ver_ops;
+ const struct lab_ver_ops *lab_ver_ops;
+ struct mutex bus_mutex;
+ enum qpnp_labibb_mode mode;
+ bool standalone;
+ bool ttw_en;
+ bool in_ttw_mode;
+ bool ibb_settings_saved;
+ bool swire_control;
+ bool pbs_control;
+ bool ttw_force_lab_on;
+ bool skip_2nd_swire_cmd;
+ bool pfm_enable;
+ u32 swire_2nd_cmd_delay;
+ u32 swire_ibb_ps_enable_delay;
+};
+
+struct ibb_ver_ops {
+ int (*set_default_voltage)(struct qpnp_labibb *labibb,
+ bool use_default);
+ int (*set_voltage)(struct qpnp_labibb *labibb, int min_uV, int max_uV);
+ int (*sel_mode)(struct qpnp_labibb *labibb, bool is_ibb);
+ int (*get_mode)(struct qpnp_labibb *labibb);
+ int (*set_clk_div)(struct qpnp_labibb *labibb, u8 val);
+ int (*smart_ps_config)(struct qpnp_labibb *labibb, bool enable,
+ int num_swire_trans, int neg_curr_limit);
+ int (*soft_start_ctl)(struct qpnp_labibb *labibb,
+ struct device_node *of_node);
+ int (*voltage_at_one_pulse)(struct qpnp_labibb *labibb, u32 volt);
+};
+
+struct lab_ver_ops {
+ const char *ver_str;
+ int (*set_default_voltage)(struct qpnp_labibb *labibb,
+ bool default_pres);
+ int (*ps_ctl)(struct qpnp_labibb *labibb,
+ u32 thresh, bool enable);
+};
+
+enum ibb_settings_index {
+ IBB_PD_CTL = 0,
+ IBB_CURRENT_LIMIT,
+ IBB_RDSON_MNGMNT,
+ IBB_PWRUP_PWRDN_CTL_1,
+ IBB_PWRUP_PWRDN_CTL_2,
+ IBB_NLIMIT_DAC,
+ IBB_PS_CTL,
+ IBB_SOFT_START_CTL,
+ IBB_SETTINGS_MAX,
+};
+
+enum lab_settings_index {
+ LAB_SOFT_START_CTL = 0,
+ LAB_PS_CTL,
+ LAB_RDSON_MNGMNT,
+ LAB_SETTINGS_MAX,
+};
+
+struct settings {
+ u16 address;
+ u8 value;
+ bool sec_access;
+};
+
+#define SETTING(_id, _sec_access) \
+ [_id] = { \
+ .address = REG_##_id, \
+ .sec_access = _sec_access, \
+ }
+
+static struct settings ibb_settings[IBB_SETTINGS_MAX] = {
+ SETTING(IBB_PD_CTL, false),
+ SETTING(IBB_CURRENT_LIMIT, true),
+ SETTING(IBB_RDSON_MNGMNT, false),
+ SETTING(IBB_PWRUP_PWRDN_CTL_1, true),
+ SETTING(IBB_PWRUP_PWRDN_CTL_2, true),
+ SETTING(IBB_NLIMIT_DAC, false),
+ SETTING(IBB_PS_CTL, false),
+ SETTING(IBB_SOFT_START_CTL, false),
+};
+
+static struct settings lab_settings[LAB_SETTINGS_MAX] = {
+ SETTING(LAB_SOFT_START_CTL, false),
+ SETTING(LAB_PS_CTL, false),
+ SETTING(LAB_RDSON_MNGMNT, false),
+};
+
+static int
+qpnp_labibb_read(struct qpnp_labibb *labibb, u16 address,
+ u8 *val, int count)
+{
+ int rc = 0;
+ struct platform_device *pdev = labibb->pdev;
+
+ mutex_lock(&(labibb->bus_mutex));
+ rc = regmap_bulk_read(labibb->regmap, address, val, count);
+ if (rc < 0)
+ pr_err("SPMI read failed address=0x%02x sid=0x%02x rc=%d\n",
+ address, to_spmi_device(pdev->dev.parent)->usid, rc);
+
+ mutex_unlock(&(labibb->bus_mutex));
+ return rc;
+}
+
+static int
+qpnp_labibb_write(struct qpnp_labibb *labibb, u16 address,
+ u8 *val, int count)
+{
+ int rc = 0;
+ struct platform_device *pdev = labibb->pdev;
+
+ mutex_lock(&(labibb->bus_mutex));
+ if (address == 0) {
+ pr_err("address cannot be zero address=0x%02x sid=0x%02x rc=%d\n",
+ address, to_spmi_device(pdev->dev.parent)->usid, rc);
+ rc = -EINVAL;
+ goto error;
+ }
+
+ rc = regmap_bulk_write(labibb->regmap, address, val, count);
+ if (rc < 0)
+ pr_err("write failed address=0x%02x sid=0x%02x rc=%d\n",
+ address, to_spmi_device(pdev->dev.parent)->usid, rc);
+
+error:
+ mutex_unlock(&(labibb->bus_mutex));
+ return rc;
+}
+
+static int
+qpnp_labibb_masked_write(struct qpnp_labibb *labibb, u16 address,
+ u8 mask, u8 val)
+{
+ int rc = 0;
+ struct platform_device *pdev = labibb->pdev;
+
+ mutex_lock(&(labibb->bus_mutex));
+ if (address == 0) {
+ pr_err("address cannot be zero address=0x%02x sid=0x%02x\n",
+ address, to_spmi_device(pdev->dev.parent)->usid);
+ rc = -EINVAL;
+ goto error;
+ }
+
+ rc = regmap_update_bits(labibb->regmap, address, mask, val);
+ if (rc < 0)
+ pr_err("spmi write failed: addr=%03X, rc=%d\n", address, rc);
+
+error:
+ mutex_unlock(&(labibb->bus_mutex));
+ return rc;
+}
+
+static int qpnp_labibb_sec_write(struct qpnp_labibb *labibb, u16 base,
+ u8 offset, u8 val)
+{
+ int rc = 0;
+ u8 sec_val = REG_LAB_IBB_SEC_UNLOCK_CODE;
+ struct platform_device *pdev = labibb->pdev;
+
+ mutex_lock(&(labibb->bus_mutex));
+ if (base == 0) {
+ pr_err("base cannot be zero base=0x%02x sid=0x%02x\n",
+ base, to_spmi_device(pdev->dev.parent)->usid);
+ rc = -EINVAL;
+ goto error;
+ }
+
+ rc = regmap_write(labibb->regmap, base + REG_LAB_IBB_SEC_ACCESS,
+ sec_val);
+ if (rc < 0) {
+ pr_err("register %x failed rc = %d\n",
+ base + REG_LAB_IBB_SEC_ACCESS, rc);
+ goto error;
+ }
+
+ rc = regmap_write(labibb->regmap, base + offset, val);
+ if (rc < 0)
+ pr_err("failed: addr=%03X, rc=%d\n",
+ base + offset, rc);
+
+error:
+ mutex_unlock(&(labibb->bus_mutex));
+ return rc;
+}
+
+static int qpnp_labibb_sec_masked_write(struct qpnp_labibb *labibb, u16 base,
+ u8 offset, u8 mask, u8 val)
+{
+ int rc = 0;
+ u8 sec_val = REG_LAB_IBB_SEC_UNLOCK_CODE;
+ struct platform_device *pdev = labibb->pdev;
+
+ mutex_lock(&(labibb->bus_mutex));
+ if (base == 0) {
+ pr_err("base cannot be zero base=0x%02x sid=0x%02x\n",
+ base, to_spmi_device(pdev->dev.parent)->usid);
+ rc = -EINVAL;
+ goto error;
+ }
+
+ rc = regmap_write(labibb->regmap, base + REG_LAB_IBB_SEC_ACCESS,
+ sec_val);
+ if (rc < 0) {
+ pr_err("register %x failed rc = %d\n",
+ base + REG_LAB_IBB_SEC_ACCESS, rc);
+ goto error;
+ }
+
+ rc = regmap_update_bits(labibb->regmap, base + offset, mask, val);
+ if (rc < 0)
+ pr_err("spmi write failed: addr=%03X, rc=%d\n", base, rc);
+
+error:
+ mutex_unlock(&(labibb->bus_mutex));
+ return rc;
+}
+
+static int qpnp_ibb_smart_ps_config_v1(struct qpnp_labibb *labibb, bool enable,
+ int num_swire_trans, int neg_curr_limit)
+{
+ return 0;
+}
+
+static int qpnp_ibb_smart_ps_config_v2(struct qpnp_labibb *labibb, bool enable,
+ int num_swire_trans, int neg_curr_limit)
+{
+ u8 val;
+ int rc = 0;
+
+ if (enable) {
+ val = IBB_NUM_SWIRE_PULSE_WAIT;
+ rc = qpnp_labibb_write(labibb,
+ labibb->ibb_base + REG_IBB_PS_CTL, &val, 1);
+ if (rc < 0) {
+ pr_err("write register %x failed rc = %d\n",
+ REG_IBB_PS_CTL, rc);
+ return rc;
+ }
+ }
+
+ val = enable ? IBB_SMART_PS_CTL_EN : IBB_NUM_SWIRE_PULSE_WAIT;
+ if (num_swire_trans)
+ val |= num_swire_trans;
+ else
+ val |= IBB_NUM_SWIRE_PULSE_WAIT;
+
+ rc = qpnp_labibb_write(labibb,
+ labibb->ibb_base + REG_IBB_SMART_PS_CTL, &val, 1);
+ if (rc < 0) {
+ pr_err("write register %x failed rc = %d\n",
+ REG_IBB_SMART_PS_CTL, rc);
+ return rc;
+ }
+
+ val = enable ? (neg_curr_limit ? neg_curr_limit :
+ IBB_DEFAULT_NLIMIT_DAC) : IBB_DEFAULT_NLIMIT_DAC;
+
+ rc = qpnp_labibb_write(labibb,
+ labibb->ibb_base + REG_IBB_NLIMIT_DAC, &val, 1);
+ if (rc < 0)
+ pr_err("write register %x failed rc = %d\n",
+ REG_IBB_NLIMIT_DAC, rc);
+
+ return rc;
+}
+
+static int qpnp_labibb_sel_mode_v1(struct qpnp_labibb *labibb, bool is_ibb)
+{
+ int rc = 0;
+ u8 val;
+ u16 base;
+
+ val = (labibb->mode == QPNP_LABIBB_LCD_MODE) ? REG_LAB_IBB_LCD_MODE :
+ REG_LAB_IBB_AMOLED_MODE;
+
+ base = is_ibb ? labibb->ibb_base : labibb->lab_base;
+
+ rc = qpnp_labibb_sec_write(labibb, base, REG_LAB_LCD_AMOLED_SEL,
+ val);
+ if (rc < 0)
+ pr_err("register %x failed rc = %d\n",
+ REG_LAB_LCD_AMOLED_SEL, rc);
+
+ return rc;
+}
+
+static int qpnp_labibb_sel_mode_v2(struct qpnp_labibb *labibb, bool is_ibb)
+{
+ return 0;
+}
+
+static int qpnp_ibb_get_mode_v1(struct qpnp_labibb *labibb)
+{
+ int rc = 0;
+ u8 val;
+
+ rc = qpnp_labibb_read(labibb, labibb->ibb_base + REG_IBB_LCD_AMOLED_SEL,
+ &val, 1);
+ if (rc < 0)
+ return rc;
+
+ if (val == REG_LAB_IBB_AMOLED_MODE)
+ labibb->mode = QPNP_LABIBB_AMOLED_MODE;
+ else
+ labibb->mode = QPNP_LABIBB_LCD_MODE;
+
+ return 0;
+}
+
+static int qpnp_ibb_get_mode_v2(struct qpnp_labibb *labibb)
+{
+ labibb->mode = QPNP_LABIBB_AMOLED_MODE;
+
+ return 0;
+}
+
+static int qpnp_ibb_set_clk_div_v1(struct qpnp_labibb *labibb, u8 val)
+{
+ int rc = 0;
+
+ rc = qpnp_labibb_write(labibb, labibb->ibb_base + REG_IBB_CLK_DIV,
+ &val, 1);
+
+ return rc;
+}
+
+static int qpnp_ibb_set_clk_div_v2(struct qpnp_labibb *labibb, u8 val)
+{
+ int rc = 0;
+
+ val |= IBB_CLK_DIV_OVERRIDE_EN;
+ rc = qpnp_labibb_masked_write(labibb, labibb->ibb_base +
+ REG_IBB_CLK_DIV, IBB_CLK_DIV_MASK |
+ IBB_CLK_DIV_OVERRIDE_EN, val);
+
+ return rc;
+}
+
+static int qpnp_ibb_soft_start_ctl_v1(struct qpnp_labibb *labibb,
+ struct device_node *of_node)
+{
+ int rc = 0;
+ u8 val;
+ u32 tmp;
+
+ rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-soft-start",
+ &(labibb->ibb_vreg.soft_start));
+ if (rc < 0) {
+ pr_err("qcom,qpnp-ibb-soft-start is missing, rc = %d\n",
+ rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-discharge-resistor",
+ &tmp);
+ if (!rc) {
+ for (val = 0; val < ARRAY_SIZE(ibb_dischg_res_table); val++) {
+ if (ibb_dischg_res_table[val] == tmp)
+ break;
+ }
+
+ if (val == ARRAY_SIZE(ibb_dischg_res_table)) {
+ pr_err("Invalid value in qcom,qpnp-ibb-discharge-resistor\n");
+ return -EINVAL;
+ }
+
+ rc = qpnp_labibb_write(labibb, labibb->ibb_base +
+ REG_IBB_SOFT_START_CTL, &val, 1);
+ if (rc < 0) {
+ pr_err("write to register %x failed rc = %d\n",
+ REG_IBB_SOFT_START_CTL, rc);
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+static int qpnp_ibb_soft_start_ctl_v2(struct qpnp_labibb *labibb,
+ struct device_node *of_node)
+{
+ return 0;
+}
+
+static int qpnp_ibb_vreg_ok_ctl(struct qpnp_labibb *labibb,
+ struct device_node *of_node)
+{
+ u8 val = 0;
+ int rc = 0, i = 0;
+ u32 tmp;
+
+ if (labibb->pmic_rev_id->pmic_subtype != PM660L_SUBTYPE)
+ return rc;
+
+ val |= IBB_VREG_OK_EN_OVERLOAD_BLANK;
+
+ rc = of_property_read_u32(of_node,
+ "qcom,qpnp-ibb-overload-debounce", &tmp);
+ if (rc < 0) {
+ pr_err("failed to read qcom,qpnp-ibb-overload-debounce rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(ibb_overload_debounce_table); i++)
+ if (ibb_overload_debounce_table[i] == tmp)
+ break;
+
+ if (i == ARRAY_SIZE(ibb_overload_debounce_table)) {
+ pr_err("Invalid value in qcom,qpnp-ibb-overload-debounce\n");
+ return -EINVAL;
+ }
+ val |= i << IBB_VREG_OK_OVERLOAD_DEB_SHIFT;
+
+ rc = of_property_read_u32(of_node,
+ "qcom,qpnp-ibb-vreg-ok-debounce", &tmp);
+ if (rc < 0) {
+ pr_err("failed to read qcom,qpnp-ibb-vreg-ok-debounce rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(ibb_vreg_ok_deb_table); i++)
+ if (ibb_vreg_ok_deb_table[i] == tmp)
+ break;
+
+ if (i == ARRAY_SIZE(ibb_vreg_ok_deb_table)) {
+ pr_err("Invalid value in qcom,qpnp-ibb-vreg-ok-debounce\n");
+ return -EINVAL;
+ }
+ val |= i;
+
+ rc = qpnp_labibb_write(labibb, labibb->ibb_base +
+ REG_IBB_VREG_OK_CTL,
+ &val, 1);
+ if (rc < 0)
+ pr_err("write to register %x failed rc = %d\n",
+ REG_IBB_VREG_OK_CTL, rc);
+
+ return rc;
+}
+
+static int qpnp_ibb_set_default_voltage_v1(struct qpnp_labibb *labibb,
+ bool use_default)
+{
+ u8 val;
+ int rc = 0;
+
+ if (!use_default) {
+ if (labibb->ibb_vreg.curr_volt < labibb->ibb_vreg.min_volt) {
+ pr_err("qcom,qpnp-ibb-init-voltage %d is less than the the minimum voltage %d",
+ labibb->ibb_vreg.curr_volt, labibb->ibb_vreg.min_volt);
+ return -EINVAL;
+ }
+
+ val = DIV_ROUND_UP(labibb->ibb_vreg.curr_volt -
+ labibb->ibb_vreg.min_volt,
+ labibb->ibb_vreg.step_size);
+ if (val > IBB_VOLTAGE_SET_MASK) {
+ pr_err("qcom,qpnp-lab-init-voltage %d is larger than the max supported voltage %ld",
+ labibb->ibb_vreg.curr_volt,
+ labibb->ibb_vreg.min_volt +
+ labibb->ibb_vreg.step_size *
+ IBB_VOLTAGE_SET_MASK);
+ return -EINVAL;
+ }
+
+ labibb->ibb_vreg.curr_volt = val * labibb->ibb_vreg.step_size +
+ labibb->ibb_vreg.min_volt;
+ val |= IBB_VOLTAGE_OVERRIDE_EN;
+ } else {
+ val = 0;
+ }
+
+ rc = qpnp_labibb_masked_write(labibb, labibb->ibb_base +
+ REG_IBB_VOLTAGE, IBB_VOLTAGE_SET_MASK |
+ IBB_VOLTAGE_OVERRIDE_EN, val);
+ if (rc < 0)
+ pr_err("write to register %x failed rc = %d\n", REG_IBB_VOLTAGE,
+ rc);
+
+ return rc;
+}
+
+static int qpnp_ibb_set_default_voltage_v2(struct qpnp_labibb *labibb,
+ bool use_default)
+{
+ int rc = 0;
+ u8 val;
+
+ val = DIV_ROUND_UP(labibb->ibb_vreg.curr_volt,
+ labibb->ibb_vreg.step_size);
+ if (val > IBB_VOLTAGE_SET_MASK) {
+ pr_err("Invalid qcom,qpnp-ibb-init-voltage property %d",
+ labibb->ibb_vreg.curr_volt);
+ return -EINVAL;
+ }
+
+ labibb->ibb_vreg.curr_volt = val * labibb->ibb_vreg.step_size;
+
+ rc = qpnp_labibb_write(labibb, labibb->ibb_base +
+ REG_IBB_DEFAULT_VOLTAGE, &val, 1);
+ if (rc < 0)
+ pr_err("write to register %x failed rc = %d\n",
+ REG_IBB_DEFAULT_VOLTAGE, rc);
+
+ return rc;
+}
+
+static int qpnp_ibb_set_voltage_v1(struct qpnp_labibb *labibb,
+ int min_uV, int max_uV)
+{
+ int rc, new_uV;
+ u8 val;
+
+ if (min_uV < labibb->ibb_vreg.min_volt) {
+ pr_err("min_uV %d is less than min_volt %d", min_uV,
+ labibb->ibb_vreg.min_volt);
+ return -EINVAL;
+ }
+
+ val = DIV_ROUND_UP(min_uV - labibb->ibb_vreg.min_volt,
+ labibb->ibb_vreg.step_size);
+ new_uV = val * labibb->ibb_vreg.step_size + labibb->ibb_vreg.min_volt;
+
+ if (new_uV > max_uV) {
+ pr_err("unable to set voltage %d (min:%d max:%d)\n", new_uV,
+ min_uV, max_uV);
+ return -EINVAL;
+ }
+
+ rc = qpnp_labibb_masked_write(labibb, labibb->ibb_base +
+ REG_IBB_VOLTAGE,
+ IBB_VOLTAGE_SET_MASK |
+ IBB_VOLTAGE_OVERRIDE_EN,
+ val | IBB_VOLTAGE_OVERRIDE_EN);
+
+ if (rc < 0) {
+ pr_err("write to register %x failed rc = %d\n", REG_IBB_VOLTAGE,
+ rc);
+ return rc;
+ }
+
+ if (new_uV > labibb->ibb_vreg.curr_volt) {
+ val = DIV_ROUND_UP(new_uV - labibb->ibb_vreg.curr_volt,
+ labibb->ibb_vreg.step_size);
+ udelay(val * labibb->ibb_vreg.slew_rate);
+ }
+ labibb->ibb_vreg.curr_volt = new_uV;
+
+ return 0;
+}
+
+static int qpnp_ibb_set_voltage_v2(struct qpnp_labibb *labibb,
+ int min_uV, int max_uV)
+{
+ int rc, new_uV;
+ u8 val;
+
+ val = DIV_ROUND_UP(min_uV, labibb->ibb_vreg.step_size);
+ new_uV = val * labibb->ibb_vreg.step_size;
+
+ if (new_uV > max_uV) {
+ pr_err("unable to set voltage %d (min:%d max:%d)\n", new_uV,
+ min_uV, max_uV);
+ return -EINVAL;
+ }
+
+ rc = qpnp_labibb_write(labibb, labibb->ibb_base +
+ REG_IBB_VOLTAGE, &val, 1);
+ if (rc < 0) {
+ pr_err("write to register %x failed rc = %d\n", REG_IBB_VOLTAGE,
+ rc);
+ return rc;
+ }
+
+ if (new_uV > labibb->ibb_vreg.curr_volt) {
+ val = DIV_ROUND_UP(new_uV - labibb->ibb_vreg.curr_volt,
+ labibb->ibb_vreg.step_size);
+ udelay(val * labibb->ibb_vreg.slew_rate);
+ }
+ labibb->ibb_vreg.curr_volt = new_uV;
+
+ return 0;
+}
+
+static int qpnp_ibb_output_voltage_at_one_pulse_v1(struct qpnp_labibb *labibb,
+ u32 volt)
+{
+ int rc = 0;
+ u8 val;
+
+ /*
+ * Set the output voltage 100mV lower as the IBB HW module
+ * counts one pulse less in SWIRE mode.
+ */
+ val = DIV_ROUND_UP((volt - MIN_OUTPUT_PULSE_VOLTAGE_MV),
+ OUTPUT_VOLTAGE_STEP_MV) - 1;
+ rc = qpnp_labibb_masked_write(labibb, labibb->ibb_base +
+ REG_IBB_SWIRE_CTL,
+ IBB_OUTPUT_VOLTAGE_AT_ONE_PULSE_MASK,
+ val);
+ if (rc < 0)
+ pr_err("write register %x failed rc = %d\n",
+ REG_IBB_SWIRE_CTL, rc);
+
+ return rc;
+}
+
+static int qpnp_ibb_output_voltage_at_one_pulse_v2(struct qpnp_labibb *labibb,
+ u32 volt)
+{
+ int rc = 0;
+ u8 val;
+
+ val = DIV_ROUND_UP(volt, OUTPUT_VOLTAGE_STEP_MV);
+
+ rc = qpnp_labibb_masked_write(labibb, labibb->ibb_base +
+ REG_IBB_SWIRE_CTL,
+ IBB_OUTPUT_VOLTAGE_AT_ONE_PULSE_MASK,
+ val);
+ if (rc < 0)
+ pr_err("qpnp_labiibb_write register %x failed rc = %d\n",
+ REG_IBB_SWIRE_CTL, rc);
+
+ return rc;
+}
+
+static const struct ibb_ver_ops ibb_ops_v1 = {
+ .set_default_voltage = qpnp_ibb_set_default_voltage_v1,
+ .set_voltage = qpnp_ibb_set_voltage_v1,
+ .sel_mode = qpnp_labibb_sel_mode_v1,
+ .get_mode = qpnp_ibb_get_mode_v1,
+ .set_clk_div = qpnp_ibb_set_clk_div_v1,
+ .smart_ps_config = qpnp_ibb_smart_ps_config_v1,
+ .soft_start_ctl = qpnp_ibb_soft_start_ctl_v1,
+ .voltage_at_one_pulse = qpnp_ibb_output_voltage_at_one_pulse_v1,
+};
+
+static const struct ibb_ver_ops ibb_ops_v2 = {
+ .set_default_voltage = qpnp_ibb_set_default_voltage_v2,
+ .set_voltage = qpnp_ibb_set_voltage_v2,
+ .sel_mode = qpnp_labibb_sel_mode_v2,
+ .get_mode = qpnp_ibb_get_mode_v2,
+ .set_clk_div = qpnp_ibb_set_clk_div_v2,
+ .smart_ps_config = qpnp_ibb_smart_ps_config_v2,
+ .soft_start_ctl = qpnp_ibb_soft_start_ctl_v2,
+ .voltage_at_one_pulse = qpnp_ibb_output_voltage_at_one_pulse_v2,
+};
+
+static int qpnp_lab_set_default_voltage_v1(struct qpnp_labibb *labibb,
+ bool default_pres)
+{
+ u8 val;
+ int rc = 0;
+
+ if (!default_pres) {
+ if (labibb->lab_vreg.curr_volt < labibb->lab_vreg.min_volt) {
+ pr_err("qcom,qpnp-lab-init-voltage %d is less than the the minimum voltage %d",
+ labibb->lab_vreg.curr_volt,
+ labibb->lab_vreg.min_volt);
+ return -EINVAL;
+ }
+
+ val = DIV_ROUND_UP(labibb->lab_vreg.curr_volt -
+ labibb->lab_vreg.min_volt,
+ labibb->lab_vreg.step_size);
+ if (val > LAB_VOLTAGE_SET_MASK) {
+ pr_err("qcom,qpnp-lab-init-voltage %d is larger than the max supported voltage %ld",
+ labibb->lab_vreg.curr_volt,
+ labibb->lab_vreg.min_volt +
+ labibb->lab_vreg.step_size *
+ LAB_VOLTAGE_SET_MASK);
+ return -EINVAL;
+ }
+
+ labibb->lab_vreg.curr_volt = val * labibb->lab_vreg.step_size +
+ labibb->lab_vreg.min_volt;
+ val |= LAB_VOLTAGE_OVERRIDE_EN;
+
+ } else {
+ val = 0;
+ }
+
+ rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
+ REG_LAB_VOLTAGE, LAB_VOLTAGE_SET_MASK |
+ LAB_VOLTAGE_OVERRIDE_EN, val);
+
+ if (rc < 0)
+ pr_err("write to register %x failed rc = %d\n", REG_LAB_VOLTAGE,
+ rc);
+
+ return rc;
+}
+
+static int qpnp_lab_set_default_voltage_v2(struct qpnp_labibb *labibb,
+ bool default_pres)
+{
+ int rc = 0;
+ u8 val;
+
+ val = DIV_ROUND_UP((labibb->lab_vreg.curr_volt
+ - labibb->lab_vreg.min_volt), labibb->lab_vreg.step_size);
+
+ rc = qpnp_labibb_write(labibb, labibb->lab_base +
+ REG_LAB_VOUT_DEFAULT, &val, 1);
+ if (rc < 0)
+ pr_err("write to register %x failed rc = %d\n",
+ REG_LAB_VOUT_DEFAULT, rc);
+
+ return rc;
+}
+
+static int qpnp_lab_ps_ctl_v1(struct qpnp_labibb *labibb,
+ u32 thresh, bool enable)
+{
+ int rc = 0;
+ u8 val;
+
+ if (enable) {
+ for (val = 0; val < ARRAY_SIZE(lab_ps_thresh_table_v1); val++)
+ if (lab_ps_thresh_table_v1[val] == thresh)
+ break;
+
+ if (val == ARRAY_SIZE(lab_ps_thresh_table_v1)) {
+ pr_err("Invalid value in qcom,qpnp-lab-ps-threshold\n");
+ return -EINVAL;
+ }
+
+ val |= LAB_PS_CTL_EN;
+ } else {
+ val = 0;
+ }
+
+ rc = qpnp_labibb_write(labibb, labibb->lab_base +
+ REG_LAB_PS_CTL, &val, 1);
+
+ if (rc < 0)
+ pr_err("write register %x failed rc = %d\n",
+ REG_LAB_PS_CTL, rc);
+
+ return rc;
+}
+
+static int qpnp_lab_ps_ctl_v2(struct qpnp_labibb *labibb,
+ u32 thresh, bool enable)
+{
+ int rc = 0;
+ u8 val;
+
+ if (enable) {
+ for (val = 0; val < ARRAY_SIZE(lab_ps_thresh_table_v2); val++)
+ if (lab_ps_thresh_table_v2[val] == thresh)
+ break;
+
+ if (val == ARRAY_SIZE(lab_ps_thresh_table_v2)) {
+ pr_err("Invalid value in qcom,qpnp-lab-ps-threshold\n");
+ return -EINVAL;
+ }
+
+ val |= LAB_PS_CTL_EN;
+ } else {
+ val = 0;
+ }
+
+ rc = qpnp_labibb_write(labibb, labibb->lab_base +
+ REG_LAB_PS_CTL, &val, 1);
+
+ if (rc < 0)
+ pr_err("write register %x failed rc = %d\n",
+ REG_LAB_PS_CTL, rc);
+
+ return rc;
+}
+
+static const struct lab_ver_ops lab_ops_v1 = {
+ .set_default_voltage = qpnp_lab_set_default_voltage_v1,
+ .ps_ctl = qpnp_lab_ps_ctl_v1,
+};
+
+static const struct lab_ver_ops lab_ops_v2 = {
+ .set_default_voltage = qpnp_lab_set_default_voltage_v2,
+ .ps_ctl = qpnp_lab_ps_ctl_v2,
+};
+
+static int qpnp_labibb_get_matching_idx(const char *val)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(lab_current_sense_table); i++)
+ if (!strcmp(lab_current_sense_table[i], val))
+ return i;
+
+ return -EINVAL;
+}
+
+static int qpnp_ibb_set_mode(struct qpnp_labibb *labibb, enum ibb_mode mode)
+{
+ int rc;
+ u8 val;
+
+ if (mode == IBB_SW_CONTROL_EN)
+ val = IBB_ENABLE_CTL_MODULE_EN;
+ else if (mode == IBB_HW_CONTROL)
+ val = IBB_ENABLE_CTL_SWIRE_RDY;
+ else if (mode == IBB_HW_SW_CONTROL)
+ val = IBB_ENABLE_CTL_MODULE_EN | IBB_ENABLE_CTL_SWIRE_RDY;
+ else if (mode == IBB_SW_CONTROL_DIS)
+ val = 0;
+ else
+ return -EINVAL;
+
+ rc = qpnp_labibb_masked_write(labibb,
+ labibb->ibb_base + REG_IBB_ENABLE_CTL,
+ IBB_ENABLE_CTL_MASK, val);
+ if (rc < 0)
+ pr_err("Unable to configure IBB_ENABLE_CTL rc=%d\n", rc);
+
+ return rc;
+}
+
+static int qpnp_ibb_ps_config(struct qpnp_labibb *labibb, bool enable)
+{
+ u8 val;
+ int rc;
+
+ val = enable ? IBB_PS_CTL_EN : IBB_NUM_SWIRE_PULSE_WAIT;
+ rc = qpnp_labibb_write(labibb, labibb->ibb_base + REG_IBB_PS_CTL,
+ &val, 1);
+ if (rc < 0) {
+ pr_err("write register %x failed rc = %d\n",
+ REG_IBB_PS_CTL, rc);
+ return rc;
+ }
+
+ val = enable ? 0 : IBB_DEFAULT_NLIMIT_DAC;
+ rc = qpnp_labibb_write(labibb, labibb->ibb_base + REG_IBB_NLIMIT_DAC,
+ &val, 1);
+ if (rc < 0)
+ pr_err("write register %x failed rc = %d\n",
+ REG_IBB_NLIMIT_DAC, rc);
+ return rc;
+}
+
+static int qpnp_lab_dt_init(struct qpnp_labibb *labibb,
+ struct device_node *of_node)
+{
+ int rc = 0;
+ u8 i, val, mask;
+ u32 tmp;
+
+ /*
+ * Do not configure LCD_AMOLED_SEL for pmi8998 as it will be done by
+ * GPIO selector.
+ */
+ if (labibb->pmic_rev_id->pmic_subtype != PMI8998_SUBTYPE) {
+ rc = labibb->ibb_ver_ops->sel_mode(labibb, 0);
+ if (rc < 0)
+ return rc;
+ }
+
+ val = 0;
+ if (of_property_read_bool(of_node, "qcom,qpnp-lab-full-pull-down"))
+ val |= LAB_PD_CTL_STRONG_PULL;
+
+ if (!of_property_read_bool(of_node, "qcom,qpnp-lab-pull-down-enable"))
+ val |= LAB_PD_CTL_DISABLE_PD;
+
+ mask = LAB_PD_CTL_EN_MASK | LAB_PD_CTL_STRENGTH_MASK;
+ rc = qpnp_labibb_masked_write(labibb, labibb->lab_base + REG_LAB_PD_CTL,
+ mask, val);
+
+ if (rc < 0) {
+ pr_err("write to register %x failed rc = %d\n",
+ REG_LAB_PD_CTL, rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(of_node,
+ "qcom,qpnp-lab-switching-clock-frequency", &tmp);
+ if (!rc) {
+ for (val = 0; val < ARRAY_SIZE(lab_clk_div_table); val++)
+ if (lab_clk_div_table[val] == tmp)
+ break;
+
+ if (val == ARRAY_SIZE(lab_clk_div_table)) {
+ pr_err("Invalid value in qpnp-lab-switching-clock-frequency\n");
+ return -EINVAL;
+ }
+
+ rc = qpnp_labibb_write(labibb, labibb->lab_base +
+ REG_LAB_CLK_DIV, &val, 1);
+ if (rc < 0) {
+ pr_err("write to register %x failed rc = %d\n",
+ REG_LAB_CLK_DIV, rc);
+ return rc;
+ }
+ }
+
+ if (of_property_read_bool(of_node,
+ "qcom,qpnp-lab-limit-max-current-enable")) {
+ val = LAB_CURRENT_LIMIT_EN_BIT;
+
+ rc = of_property_read_u32(of_node,
+ "qcom,qpnp-lab-limit-maximum-current", &tmp);
+
+ if (rc < 0) {
+ pr_err("get qcom,qpnp-lab-limit-maximum-current failed rc = %d\n",
+ rc);
+ return rc;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(lab_current_limit_table); i++)
+ if (lab_current_limit_table[i] == tmp)
+ break;
+
+ if (i == ARRAY_SIZE(lab_current_limit_table)) {
+ pr_err("Invalid value in qcom,qpnp-lab-limit-maximum-current\n");
+ return -EINVAL;
+ }
+
+ val |= i;
+ rc = qpnp_labibb_write(labibb, labibb->lab_base +
+ REG_LAB_CURRENT_LIMIT, &val, 1);
+ if (rc < 0) {
+ pr_err("write to register %x failed rc = %d\n",
+ REG_LAB_CURRENT_LIMIT, rc);
+ return rc;
+ }
+ }
+
+ if (of_property_read_bool(of_node,
+ "qcom,qpnp-lab-ring-suppression-enable")) {
+ val = LAB_RING_SUPPRESSION_CTL_EN;
+ rc = qpnp_labibb_write(labibb, labibb->lab_base +
+ REG_LAB_RING_SUPPRESSION_CTL, &val, 1);
+ if (rc < 0) {
+ pr_err("write to register %x failed rc = %d\n",
+ REG_LAB_RING_SUPPRESSION_CTL, rc);
+ return rc;
+ }
+ }
+
+ if (of_property_read_bool(of_node, "qcom,qpnp-lab-ps-enable")) {
+
+ rc = of_property_read_u32(of_node,
+ "qcom,qpnp-lab-ps-threshold", &tmp);
+
+ if (rc < 0) {
+ pr_err("get qcom,qpnp-lab-ps-threshold failed rc = %d\n",
+ rc);
+ return rc;
+ }
+ rc = labibb->lab_ver_ops->ps_ctl(labibb, tmp, true);
+ if (rc < 0)
+ return rc;
+ } else {
+ rc = labibb->lab_ver_ops->ps_ctl(labibb, tmp, false);
+ if (rc < 0)
+ return rc;
+ }
+
+ val = 0;
+ mask = 0;
+ rc = of_property_read_u32(of_node, "qcom,qpnp-lab-pfet-size", &tmp);
+ if (!rc) {
+ for (val = 0; val < ARRAY_SIZE(lab_rdson_pfet_table); val++)
+ if (tmp == lab_rdson_pfet_table[val])
+ break;
+
+ if (val == ARRAY_SIZE(lab_rdson_pfet_table)) {
+ pr_err("Invalid value in qcom,qpnp-lab-pfet-size\n");
+ return -EINVAL;
+ }
+ val |= LAB_RDSON_MNGMNT_PFET_SLEW_EN;
+ mask |= LAB_RDSON_MNGMNT_PFET_MASK |
+ LAB_RDSON_MNGMNT_PFET_SLEW_EN;
+ }
+
+ rc = of_property_read_u32(of_node, "qcom,qpnp-lab-nfet-size",
+ &tmp);
+ if (!rc) {
+ for (i = 0; i < ARRAY_SIZE(lab_rdson_nfet_table); i++)
+ if (tmp == lab_rdson_nfet_table[i])
+ break;
+
+ if (i == ARRAY_SIZE(lab_rdson_nfet_table)) {
+ pr_err("Invalid value in qcom,qpnp-lab-nfet-size\n");
+ return -EINVAL;
+ }
+
+ val |= i << LAB_RDSON_MNGMNT_NFET_SHIFT;
+ val |= LAB_RDSON_MNGMNT_NFET_SLEW_EN;
+ mask |= LAB_RDSON_MNGMNT_NFET_MASK |
+ LAB_RDSON_MNGMNT_NFET_SLEW_EN;
+ }
+
+ rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
+ REG_LAB_RDSON_MNGMNT, mask, val);
+ if (rc < 0) {
+ pr_err("write to register %x failed rc = %d\n",
+ REG_LAB_RDSON_MNGMNT, rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(of_node, "qcom,qpnp-lab-init-voltage",
+ &(labibb->lab_vreg.curr_volt));
+ if (rc < 0) {
+ pr_err("get qcom,qpnp-lab-init-voltage failed, rc = %d\n",
+ rc);
+ return rc;
+ }
+
+ if (of_property_read_bool(of_node,
+ "qcom,qpnp-lab-use-default-voltage"))
+ rc = labibb->lab_ver_ops->set_default_voltage(labibb, true);
+ else
+ rc = labibb->lab_ver_ops->set_default_voltage(labibb, false);
+
+ if (rc < 0)
+ return rc;
+
+ if (of_property_read_bool(of_node,
+ "qcom,qpnp-lab-enable-sw-high-psrr")) {
+ val = LAB_EN_SW_HIGH_PSRR_MODE;
+
+ rc = qpnp_labibb_write(labibb, labibb->lab_base +
+ REG_LAB_SW_HIGH_PSRR_CTL, &val, 1);
+ if (rc < 0) {
+ pr_err("write to register %x failed rc = %d\n",
+ REG_LAB_SW_HIGH_PSRR_CTL, rc);
+ return rc;
+ }
+ }
+
+ rc = of_property_read_u32(of_node,
+ "qcom,qpnp-lab-ldo-pulldown-enable", (u32 *)&val);
+ if (!rc) {
+ rc = qpnp_labibb_write(labibb, labibb->lab_base +
+ REG_LAB_LDO_PD_CTL, &val, 1);
+ if (rc < 0) {
+ pr_err("write to register %x failed rc = %d\n",
+ REG_LAB_LDO_PD_CTL, rc);
+ return rc;
+ }
+ }
+
+ rc = of_property_read_u32(of_node,
+ "qcom,qpnp-lab-high-psrr-src-select", &tmp);
+ if (!rc) {
+ val = tmp;
+
+ rc = of_property_read_u32(of_node,
+ "qcom,qpnp-lab-vref-high-psrr-select", &tmp);
+ if (rc < 0) {
+ pr_err("get qcom,qpnp-lab-vref-high-psrr-select failed rc = %d\n",
+ rc);
+ return rc;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(lab_vref_high_psrr_table); i++)
+ if (lab_vref_high_psrr_table[i] == tmp)
+ break;
+
+ if (i == ARRAY_SIZE(lab_vref_high_psrr_table)) {
+ pr_err("Invalid value in qpnp-lab-vref-high-psrr-selct\n");
+ return -EINVAL;
+ }
+ val |= (i << LAB_SEL_HW_HIGH_PSRR_SRC_SHIFT);
+
+ rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
+ REG_LAB_VPH_ENVELOP_CTL,
+ LAB_VREF_HIGH_PSRR_SEL_MASK |
+ LAB_SEL_HW_HIGH_PSRR_SRC_MASK,
+ val);
+
+ if (rc < 0) {
+ pr_err("write to register %x failed rc = %d\n",
+ REG_LAB_VPH_ENVELOP_CTL, rc);
+ return rc;
+ }
+ }
+
+ if (labibb->swire_control) {
+ rc = qpnp_ibb_set_mode(labibb, IBB_HW_CONTROL);
+ if (rc < 0) {
+ pr_err("Unable to set SWIRE_RDY rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+#define LAB_CURRENT_MAX_1600MA 0x7
+#define LAB_CURRENT_MAX_400MA 0x1
+static int qpnp_lab_pfm_disable(struct qpnp_labibb *labibb)
+{
+ int rc = 0;
+ u8 val, mask;
+
+ mutex_lock(&(labibb->lab_vreg.lab_mutex));
+ if (!labibb->pfm_enable) {
+ pr_debug("PFM already disabled\n");
+ goto out;
+ }
+
+ val = 0;
+ mask = LAB_PFM_EN_BIT;
+ rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
+ REG_LAB_PFM_CTL, mask, val);
+ if (rc < 0) {
+ pr_err("Write register %x failed rc = %d\n",
+ REG_LAB_PFM_CTL, rc);
+ goto out;
+ }
+
+ val = LAB_CURRENT_MAX_1600MA;
+ mask = LAB_OVERRIDE_CURRENT_MAX_BIT | LAB_CURRENT_LIMIT_MASK;
+ rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
+ REG_LAB_CURRENT_LIMIT, mask, val);
+ if (rc < 0) {
+ pr_err("Write register %x failed rc = %d\n",
+ REG_LAB_CURRENT_LIMIT, rc);
+ goto out;
+ }
+
+ labibb->pfm_enable = false;
+out:
+ mutex_unlock(&(labibb->lab_vreg.lab_mutex));
+ return rc;
+}
+
+static int qpnp_lab_pfm_enable(struct qpnp_labibb *labibb)
+{
+ int rc = 0;
+ u8 val, mask;
+
+ mutex_lock(&(labibb->lab_vreg.lab_mutex));
+ if (labibb->pfm_enable) {
+ pr_debug("PFM already enabled\n");
+ goto out;
+ }
+
+ /* Wait for ~100uS */
+ usleep_range(100, 105);
+
+ val = LAB_OVERRIDE_CURRENT_MAX_BIT | LAB_CURRENT_MAX_400MA;
+ mask = LAB_OVERRIDE_CURRENT_MAX_BIT | LAB_CURRENT_LIMIT_MASK;
+ rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
+ REG_LAB_CURRENT_LIMIT, mask, val);
+ if (rc < 0) {
+ pr_err("Write register %x failed rc = %d\n",
+ REG_LAB_CURRENT_LIMIT, rc);
+ goto out;
+ }
+
+ /* Wait for ~100uS */
+ usleep_range(100, 105);
+
+ val = LAB_PFM_EN_BIT;
+ mask = LAB_PFM_EN_BIT;
+ rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
+ REG_LAB_PFM_CTL, mask, val);
+ if (rc < 0) {
+ pr_err("Write register %x failed rc = %d\n",
+ REG_LAB_PFM_CTL, rc);
+ goto out;
+ }
+
+ labibb->pfm_enable = true;
+out:
+ mutex_unlock(&(labibb->lab_vreg.lab_mutex));
+ return rc;
+}
+
+static int qpnp_labibb_restore_settings(struct qpnp_labibb *labibb)
+{
+ int rc, i;
+
+ for (i = 0; i < ARRAY_SIZE(ibb_settings); i++) {
+ if (ibb_settings[i].sec_access)
+ rc = qpnp_labibb_sec_write(labibb, labibb->ibb_base,
+ ibb_settings[i].address,
+ ibb_settings[i].value);
+ else
+ rc = qpnp_labibb_write(labibb, labibb->ibb_base +
+ ibb_settings[i].address,
+ &ibb_settings[i].value, 1);
+
+ if (rc < 0) {
+ pr_err("write to register %x failed rc = %d\n",
+ ibb_settings[i].address, rc);
+ return rc;
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(lab_settings); i++) {
+ if (lab_settings[i].sec_access)
+ rc = qpnp_labibb_sec_write(labibb, labibb->lab_base,
+ lab_settings[i].address,
+ lab_settings[i].value);
+ else
+ rc = qpnp_labibb_write(labibb, labibb->lab_base +
+ lab_settings[i].address,
+ &lab_settings[i].value, 1);
+
+ if (rc < 0) {
+ pr_err("write to register %x failed rc = %d\n",
+ lab_settings[i].address, rc);
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+static int qpnp_labibb_save_settings(struct qpnp_labibb *labibb)
+{
+ int rc, i;
+
+ for (i = 0; i < ARRAY_SIZE(ibb_settings); i++) {
+ rc = qpnp_labibb_read(labibb, labibb->ibb_base +
+ ibb_settings[i].address, &ibb_settings[i].value, 1);
+ if (rc < 0) {
+ pr_err("read register %x failed rc = %d\n",
+ ibb_settings[i].address, rc);
+ return rc;
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(lab_settings); i++) {
+ rc = qpnp_labibb_read(labibb, labibb->lab_base +
+ lab_settings[i].address, &lab_settings[i].value, 1);
+ if (rc < 0) {
+ pr_err("read register %x failed rc = %d\n",
+ lab_settings[i].address, rc);
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+static int qpnp_labibb_ttw_enter_ibb_common(struct qpnp_labibb *labibb)
+{
+ int rc = 0;
+ u8 val;
+
+ val = 0;
+ rc = qpnp_labibb_write(labibb, labibb->ibb_base + REG_IBB_PD_CTL,
+ &val, 1);
+ if (rc < 0) {
+ pr_err("read register %x failed rc = %d\n",
+ REG_IBB_PD_CTL, rc);
+ return rc;
+ }
+
+ val = 0;
+ rc = qpnp_labibb_sec_write(labibb, labibb->ibb_base,
+ REG_IBB_PWRUP_PWRDN_CTL_1, val);
+ if (rc < 0) {
+ pr_err("write to register %x failed rc = %d\n",
+ REG_IBB_PWRUP_PWRDN_CTL_1, rc);
+ return rc;
+ }
+
+ val = IBB_WAIT_MBG_OK;
+ rc = qpnp_labibb_sec_masked_write(labibb, labibb->ibb_base,
+ REG_IBB_PWRUP_PWRDN_CTL_2,
+ IBB_DIS_DLY_MASK | IBB_WAIT_MBG_OK, val);
+ if (rc < 0) {
+ pr_err("write to register %x failed rc = %d\n",
+ REG_IBB_PWRUP_PWRDN_CTL_2, rc);
+ return rc;
+ }
+
+ val = IBB_NFET_SLEW_EN | IBB_PFET_SLEW_EN | IBB_OVERRIDE_NFET_SW_SIZE |
+ IBB_OVERRIDE_PFET_SW_SIZE;
+ rc = qpnp_labibb_masked_write(labibb, labibb->ibb_base +
+ REG_IBB_RDSON_MNGMNT, 0xFF, val);
+ if (rc < 0) {
+ pr_err("write to register %x failed rc = %d\n",
+ REG_IBB_RDSON_MNGMNT, rc);
+ return rc;
+ }
+
+ val = IBB_CURRENT_LIMIT_EN | IBB_CURRENT_MAX_500MA |
+ (IBB_ILIMIT_COUNT_CYC8 << IBB_CURRENT_LIMIT_DEBOUNCE_SHIFT);
+ rc = qpnp_labibb_sec_write(labibb, labibb->ibb_base,
+ REG_IBB_CURRENT_LIMIT, val);
+ if (rc < 0)
+ pr_err("write to register %x failed rc = %d\n",
+ REG_IBB_CURRENT_LIMIT, rc);
+
+ return rc;
+}
+
+static int qpnp_labibb_ttw_enter_ibb_pmi8996(struct qpnp_labibb *labibb)
+{
+ int rc;
+ u8 val;
+
+ val = IBB_BYPASS_PWRDN_DLY2_BIT | IBB_FAST_STARTUP;
+ rc = qpnp_labibb_write(labibb, labibb->ibb_base + REG_IBB_SPARE_CTL,
+ &val, 1);
+ if (rc < 0)
+ pr_err("write to register %x failed rc = %d\n",
+ REG_IBB_SPARE_CTL, rc);
+
+ return rc;
+}
+
+static int qpnp_labibb_ttw_enter_ibb_pmi8950(struct qpnp_labibb *labibb)
+{
+ int rc;
+ u8 val;
+
+ rc = qpnp_ibb_ps_config(labibb, true);
+ if (rc < 0) {
+ pr_err("Failed to enable ibb_ps_config rc=%d\n", rc);
+ return rc;
+ }
+
+ val = IBB_SOFT_START_CHARGING_RESISTOR_16K;
+ rc = qpnp_labibb_write(labibb, labibb->ibb_base +
+ REG_IBB_SOFT_START_CTL, &val, 1);
+ if (rc < 0) {
+ pr_err("write to register %x failed rc = %d\n",
+ REG_IBB_SOFT_START_CTL, rc);
+ return rc;
+ }
+
+ val = IBB_MODULE_RDY_EN;
+ rc = qpnp_labibb_write(labibb, labibb->lab_base +
+ REG_IBB_MODULE_RDY, &val, 1);
+ if (rc < 0)
+ pr_err("write to register %x failed rc = %d\n",
+ REG_IBB_MODULE_RDY, rc);
+
+ return rc;
+}
+
+static int qpnp_labibb_regulator_ttw_mode_enter(struct qpnp_labibb *labibb)
+{
+ int rc = 0;
+ u8 val;
+
+ /* Save the IBB settings before they get modified for TTW mode */
+ if (!labibb->ibb_settings_saved) {
+ rc = qpnp_labibb_save_settings(labibb);
+ if (rc) {
+ pr_err("Error in storing IBB setttings, rc=%d\n", rc);
+ return rc;
+ }
+ labibb->ibb_settings_saved = true;
+ }
+
+ if (labibb->ttw_force_lab_on) {
+ val = LAB_MODULE_RDY_EN;
+ rc = qpnp_labibb_write(labibb, labibb->lab_base +
+ REG_LAB_MODULE_RDY, &val, 1);
+ if (rc < 0) {
+ pr_err("write to register %x failed rc = %d\n",
+ REG_LAB_MODULE_RDY, rc);
+ return rc;
+ }
+
+ /* Prevents LAB being turned off by IBB */
+ val = LAB_ENABLE_CTL_EN;
+ rc = qpnp_labibb_write(labibb, labibb->lab_base +
+ REG_LAB_ENABLE_CTL, &val, 1);
+ if (rc < 0) {
+ pr_err("write to register %x failed rc = %d\n",
+ REG_LAB_ENABLE_CTL, rc);
+ return rc;
+ }
+
+ val = LAB_RDSON_MNGMNT_NFET_SLEW_EN |
+ LAB_RDSON_MNGMNT_PFET_SLEW_EN |
+ LAB_RDSON_NFET_SW_SIZE_QUARTER |
+ LAB_RDSON_PFET_SW_SIZE_QUARTER;
+ rc = qpnp_labibb_write(labibb, labibb->lab_base +
+ REG_LAB_RDSON_MNGMNT, &val, 1);
+ if (rc < 0) {
+ pr_err("write to register %x failed rc = %d\n",
+ REG_LAB_RDSON_MNGMNT, rc);
+ return rc;
+ }
+
+ rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
+ REG_LAB_PS_CTL, LAB_PS_CTL_EN, LAB_PS_CTL_EN);
+ if (rc < 0) {
+ pr_err("qpnp_labibb_write register %x failed rc = %d\n",
+ REG_LAB_PS_CTL, rc);
+ return rc;
+ }
+ } else {
+ val = LAB_PD_CTL_DISABLE_PD;
+ rc = qpnp_labibb_write(labibb, labibb->lab_base +
+ REG_LAB_PD_CTL, &val, 1);
+ if (rc < 0) {
+ pr_err("qpnp_labibb_write register %x failed rc = %d\n",
+ REG_LAB_PD_CTL, rc);
+ return rc;
+ }
+
+ val = LAB_SPARE_DISABLE_SCP_BIT;
+ if (labibb->pmic_rev_id->pmic_subtype != PMI8950_SUBTYPE)
+ val |= LAB_SPARE_TOUCH_WAKE_BIT;
+ rc = qpnp_labibb_write(labibb, labibb->lab_base +
+ REG_LAB_SPARE_CTL, &val, 1);
+ if (rc < 0) {
+ pr_err("qpnp_labibb_write register %x failed rc = %d\n",
+ REG_LAB_SPARE_CTL, rc);
+ return rc;
+ }
+
+ val = 0;
+ rc = qpnp_labibb_write(labibb, labibb->lab_base +
+ REG_LAB_SOFT_START_CTL, &val, 1);
+ if (rc < 0) {
+ pr_err("qpnp_labibb_write register %x failed rc = %d\n",
+ REG_LAB_SOFT_START_CTL, rc);
+ return rc;
+ }
+ }
+
+ rc = qpnp_labibb_ttw_enter_ibb_common(labibb);
+ if (rc) {
+ pr_err("Failed to apply TTW ibb common settings rc=%d\n", rc);
+ return rc;
+ }
+
+ switch (labibb->pmic_rev_id->pmic_subtype) {
+ case PMI8996_SUBTYPE:
+ rc = qpnp_labibb_ttw_enter_ibb_pmi8996(labibb);
+ break;
+ case PMI8950_SUBTYPE:
+ rc = qpnp_labibb_ttw_enter_ibb_pmi8950(labibb);
+ break;
+ }
+ if (rc < 0) {
+ pr_err("Failed to configure TTW-enter for IBB rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = qpnp_ibb_set_mode(labibb, IBB_HW_CONTROL);
+ if (rc < 0) {
+ pr_err("Unable to set SWIRE_RDY rc = %d\n", rc);
+ return rc;
+ }
+ labibb->in_ttw_mode = true;
+ return 0;
+}
+
+static int qpnp_labibb_ttw_exit_ibb_common(struct qpnp_labibb *labibb)
+{
+ int rc;
+ u8 val;
+
+ val = IBB_FASTER_PFET_OFF;
+ rc = qpnp_labibb_write(labibb, labibb->ibb_base + REG_IBB_SPARE_CTL,
+ &val, 1);
+ if (rc < 0)
+ pr_err("qpnp_labibb_write register %x failed rc = %d\n",
+ REG_IBB_SPARE_CTL, rc);
+
+ return rc;
+}
+
+static int qpnp_labibb_regulator_ttw_mode_exit(struct qpnp_labibb *labibb)
+{
+ int rc = 0;
+ u8 val;
+
+ if (!labibb->ibb_settings_saved) {
+ pr_err("IBB settings are not saved!\n");
+ return -EINVAL;
+ }
+
+ /* Restore the IBB settings back to switch back to normal mode */
+ rc = qpnp_labibb_restore_settings(labibb);
+ if (rc < 0) {
+ pr_err("Error in restoring IBB setttings, rc=%d\n", rc);
+ return rc;
+ }
+
+ if (labibb->ttw_force_lab_on) {
+ val = 0;
+ rc = qpnp_labibb_write(labibb, labibb->lab_base +
+ REG_LAB_ENABLE_CTL, &val, 1);
+ if (rc < 0) {
+ pr_err("qpnp_labibb_write register %x failed rc = %d\n",
+ REG_LAB_ENABLE_CTL, rc);
+ return rc;
+ }
+ } else {
+ val = LAB_PD_CTL_STRONG_PULL;
+ rc = qpnp_labibb_write(labibb, labibb->lab_base +
+ REG_LAB_PD_CTL, &val, 1);
+ if (rc < 0) {
+ pr_err("qpnp_labibb_write register %x failed rc = %d\n",
+ REG_LAB_PD_CTL, rc);
+ return rc;
+ }
+
+ val = 0;
+ rc = qpnp_labibb_write(labibb, labibb->lab_base +
+ REG_LAB_SPARE_CTL, &val, 1);
+ if (rc < 0) {
+ pr_err("qpnp_labibb_write register %x failed rc = %d\n",
+ REG_LAB_SPARE_CTL, rc);
+ return rc;
+ }
+ }
+
+ switch (labibb->pmic_rev_id->pmic_subtype) {
+ case PMI8996_SUBTYPE:
+ case PMI8994_SUBTYPE:
+ case PMI8950_SUBTYPE:
+ rc = qpnp_labibb_ttw_exit_ibb_common(labibb);
+ break;
+ }
+ if (rc < 0) {
+ pr_err("Failed to configure TTW-exit for IBB rc=%d\n", rc);
+ return rc;
+ }
+
+ labibb->in_ttw_mode = false;
+ return rc;
+}
+
+static int qpnp_labibb_regulator_enable(struct qpnp_labibb *labibb)
+{
+ int rc;
+ u8 val;
+ int dly;
+ int retries;
+ bool enabled = false;
+
+ if (labibb->ttw_en && !labibb->ibb_vreg.vreg_enabled &&
+ labibb->in_ttw_mode) {
+ rc = qpnp_labibb_regulator_ttw_mode_exit(labibb);
+ if (rc) {
+ pr_err("Error in exiting TTW mode rc = %d\n", rc);
+ return rc;
+ }
+ }
+
+ rc = qpnp_ibb_set_mode(labibb, IBB_SW_CONTROL_EN);
+ if (rc) {
+ pr_err("Unable to set IBB_MODULE_EN rc = %d\n", rc);
+ return rc;
+ }
+
+ /* total delay time */
+ dly = labibb->lab_vreg.soft_start + labibb->ibb_vreg.soft_start
+ + labibb->ibb_vreg.pwrup_dly;
+ usleep_range(dly, dly + 100);
+
+ /* after this delay, lab should be enabled */
+ rc = qpnp_labibb_read(labibb, labibb->lab_base + REG_LAB_STATUS1,
+ &val, 1);
+ if (rc < 0) {
+ pr_err("read register %x failed rc = %d\n",
+ REG_LAB_STATUS1, rc);
+ goto err_out;
+ }
+
+ pr_debug("soft=%d %d up=%d dly=%d\n",
+ labibb->lab_vreg.soft_start, labibb->ibb_vreg.soft_start,
+ labibb->ibb_vreg.pwrup_dly, dly);
+
+ if (!(val & LAB_STATUS1_VREG_OK)) {
+ pr_err("failed for LAB %x\n", val);
+ goto err_out;
+ }
+
+ /* poll IBB_STATUS to make sure ibb had been enabled */
+ dly = labibb->ibb_vreg.soft_start + labibb->ibb_vreg.pwrup_dly;
+ retries = 10;
+ while (retries--) {
+ rc = qpnp_labibb_read(labibb, labibb->ibb_base +
+ REG_IBB_STATUS1, &val, 1);
+ if (rc < 0) {
+ pr_err("read register %x failed rc = %d\n",
+ REG_IBB_STATUS1, rc);
+ goto err_out;
+ }
+
+ if (val & IBB_STATUS1_VREG_OK) {
+ enabled = true;
+ break;
+ }
+ usleep_range(dly, dly + 100);
+ }
+
+ if (!enabled) {
+ pr_err("failed for IBB %x\n", val);
+ goto err_out;
+ }
+
+ labibb->lab_vreg.vreg_enabled = 1;
+ labibb->ibb_vreg.vreg_enabled = 1;
+
+ return 0;
+err_out:
+ rc = qpnp_ibb_set_mode(labibb, IBB_SW_CONTROL_DIS);
+ if (rc < 0) {
+ pr_err("Unable to set IBB_MODULE_EN rc = %d\n", rc);
+ return rc;
+ }
+ return -EINVAL;
+}
+
+static int qpnp_labibb_regulator_disable(struct qpnp_labibb *labibb)
+{
+ int rc;
+ u8 val;
+ int dly;
+ int retries;
+ bool disabled = false;
+
+ /*
+ * When TTW mode is enabled and LABIBB regulators are disabled, it is
+ * recommended not to disable IBB through IBB_ENABLE_CTL when switching
+ * to SWIRE control on entering TTW mode. Hence, just enter TTW mode
+ * and mark the regulators disabled. When we exit TTW mode, normal
+ * mode settings will be restored anyways and regulators will be
+ * enabled as before.
+ */
+ if (labibb->ttw_en && !labibb->in_ttw_mode) {
+ rc = qpnp_labibb_regulator_ttw_mode_enter(labibb);
+ if (rc < 0) {
+ pr_err("Error in entering TTW mode rc = %d\n", rc);
+ return rc;
+ }
+ labibb->lab_vreg.vreg_enabled = 0;
+ labibb->ibb_vreg.vreg_enabled = 0;
+ return 0;
+ }
+
+ rc = qpnp_ibb_set_mode(labibb, IBB_SW_CONTROL_DIS);
+ if (rc < 0) {
+ pr_err("Unable to set IBB_MODULE_EN rc = %d\n", rc);
+ return rc;
+ }
+
+ /* poll IBB_STATUS to make sure ibb had been disabled */
+ dly = labibb->ibb_vreg.pwrdn_dly;
+ retries = 2;
+ while (retries--) {
+ usleep_range(dly, dly + 100);
+ rc = qpnp_labibb_read(labibb, labibb->ibb_base +
+ REG_IBB_STATUS1, &val, 1);
+ if (rc < 0) {
+ pr_err("read register %x failed rc = %d\n",
+ REG_IBB_STATUS1, rc);
+ return rc;
+ }
+
+ if (!(val & IBB_STATUS1_VREG_OK)) {
+ disabled = true;
+ break;
+ }
+ }
+
+ if (!disabled) {
+ pr_err("failed for IBB %x\n", val);
+ return -EINVAL;
+ }
+
+ if (labibb->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE &&
+ labibb->mode == QPNP_LABIBB_LCD_MODE) {
+ rc = qpnp_lab_pfm_disable(labibb);
+ if (rc < 0) {
+ pr_err("Error in disabling PFM, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ labibb->lab_vreg.vreg_enabled = 0;
+ labibb->ibb_vreg.vreg_enabled = 0;
+
+ return 0;
+}
+
+static int qpnp_lab_regulator_enable(struct regulator_dev *rdev)
+{
+ int rc;
+ u8 val;
+
+ struct qpnp_labibb *labibb = rdev_get_drvdata(rdev);
+
+ if (labibb->skip_2nd_swire_cmd) {
+ rc = qpnp_ibb_ps_config(labibb, false);
+ if (rc < 0) {
+ pr_err("Failed to disable IBB PS rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ if (!labibb->lab_vreg.vreg_enabled && !labibb->swire_control) {
+
+ if (!labibb->standalone)
+ return qpnp_labibb_regulator_enable(labibb);
+
+ val = LAB_ENABLE_CTL_EN;
+ rc = qpnp_labibb_write(labibb,
+ labibb->lab_base + REG_LAB_ENABLE_CTL, &val, 1);
+ if (rc < 0) {
+ pr_err("qpnp_lab_regulator_enable write register %x failed rc = %d\n",
+ REG_LAB_ENABLE_CTL, rc);
+ return rc;
+ }
+
+ udelay(labibb->lab_vreg.soft_start);
+
+ rc = qpnp_labibb_read(labibb, labibb->lab_base +
+ REG_LAB_STATUS1, &val, 1);
+ if (rc < 0) {
+ pr_err("qpnp_lab_regulator_enable read register %x failed rc = %d\n",
+ REG_LAB_STATUS1, rc);
+ return rc;
+ }
+
+ if ((val & LAB_STATUS1_VREG_OK_MASK) != LAB_STATUS1_VREG_OK) {
+ pr_err("qpnp_lab_regulator_enable failed\n");
+ return -EINVAL;
+ }
+
+ labibb->lab_vreg.vreg_enabled = 1;
+ }
+
+ return 0;
+}
+
+static int qpnp_lab_regulator_disable(struct regulator_dev *rdev)
+{
+ int rc;
+ u8 val;
+ struct qpnp_labibb *labibb = rdev_get_drvdata(rdev);
+
+ if (labibb->lab_vreg.vreg_enabled && !labibb->swire_control) {
+
+ if (!labibb->standalone)
+ return qpnp_labibb_regulator_disable(labibb);
+
+ val = 0;
+ rc = qpnp_labibb_write(labibb,
+ labibb->lab_base + REG_LAB_ENABLE_CTL, &val, 1);
+ if (rc < 0) {
+ pr_err("qpnp_lab_regulator_enable write register %x failed rc = %d\n",
+ REG_LAB_ENABLE_CTL, rc);
+ return rc;
+ }
+
+ labibb->lab_vreg.vreg_enabled = 0;
+ }
+ return 0;
+}
+
+static int qpnp_lab_regulator_is_enabled(struct regulator_dev *rdev)
+{
+ struct qpnp_labibb *labibb = rdev_get_drvdata(rdev);
+
+ if (labibb->swire_control)
+ return 0;
+
+ return labibb->lab_vreg.vreg_enabled;
+}
+
+static int qpnp_lab_regulator_set_voltage(struct regulator_dev *rdev,
+ int min_uV, int max_uV, unsigned int *selector)
+{
+ int rc, new_uV;
+ u8 val;
+ struct qpnp_labibb *labibb = rdev_get_drvdata(rdev);
+
+ if (labibb->swire_control)
+ return 0;
+
+ if (min_uV < labibb->lab_vreg.min_volt) {
+ pr_err("min_uV %d is less than min_volt %d", min_uV,
+ labibb->lab_vreg.min_volt);
+ return -EINVAL;
+ }
+
+ val = DIV_ROUND_UP(min_uV - labibb->lab_vreg.min_volt,
+ labibb->lab_vreg.step_size);
+ new_uV = val * labibb->lab_vreg.step_size + labibb->lab_vreg.min_volt;
+
+ if (new_uV > max_uV) {
+ pr_err("unable to set voltage %d (min:%d max:%d)\n", new_uV,
+ min_uV, max_uV);
+ return -EINVAL;
+ }
+
+ rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
+ REG_LAB_VOLTAGE,
+ LAB_VOLTAGE_SET_MASK |
+ LAB_VOLTAGE_OVERRIDE_EN,
+ val | LAB_VOLTAGE_OVERRIDE_EN);
+
+ if (rc < 0) {
+ pr_err("write to register %x failed rc = %d\n", REG_LAB_VOLTAGE,
+ rc);
+ return rc;
+ }
+
+ if (new_uV > labibb->lab_vreg.curr_volt) {
+ val = DIV_ROUND_UP(new_uV - labibb->lab_vreg.curr_volt,
+ labibb->lab_vreg.step_size);
+ udelay(val * labibb->lab_vreg.slew_rate);
+ }
+ labibb->lab_vreg.curr_volt = new_uV;
+
+ return 0;
+}
+
+static int qpnp_skip_swire_command(struct qpnp_labibb *labibb)
+{
+ int rc = 0, retry = 50, dly;
+ u8 reg;
+
+ do {
+ /* poll for ibb vreg_ok */
+ rc = qpnp_labibb_read(labibb, labibb->ibb_base +
+ REG_IBB_STATUS1, ®, 1);
+ if (rc < 0) {
+ pr_err("Failed to read ibb_status1 reg rc=%d\n", rc);
+ return rc;
+ }
+ if ((reg & IBB_STATUS1_VREG_OK_MASK) == IBB_STATUS1_VREG_OK)
+ break;
+
+ /* poll delay */
+ usleep_range(500, 600);
+
+ } while (--retry);
+
+ if (!retry) {
+ pr_err("ibb vreg_ok failed to turn-on\n");
+ return -EBUSY;
+ }
+
+ /* move to SW control */
+ rc = qpnp_ibb_set_mode(labibb, IBB_SW_CONTROL_EN);
+ if (rc < 0) {
+ pr_err("Failed switch to IBB_SW_CONTROL rc=%d\n", rc);
+ return rc;
+ }
+
+ /* delay to skip the second swire command */
+ dly = labibb->swire_2nd_cmd_delay * 1000;
+ while (dly / 20000) {
+ usleep_range(20000, 20010);
+ dly -= 20000;
+ }
+ if (dly)
+ usleep_range(dly, dly + 10);
+
+ rc = qpnp_ibb_set_mode(labibb, IBB_HW_SW_CONTROL);
+ if (rc < 0) {
+ pr_err("Failed switch to IBB_HW_SW_CONTROL rc=%d\n", rc);
+ return rc;
+ }
+
+ /* delay for SPMI to SWIRE transition */
+ usleep_range(1000, 1100);
+
+ /* Move back to SWIRE control */
+ rc = qpnp_ibb_set_mode(labibb, IBB_HW_CONTROL);
+ if (rc < 0)
+ pr_err("Failed switch to IBB_HW_CONTROL rc=%d\n", rc);
+
+ /* delay before enabling the PS mode */
+ msleep(labibb->swire_ibb_ps_enable_delay);
+ rc = qpnp_ibb_ps_config(labibb, true);
+ if (rc < 0)
+ pr_err("Unable to enable IBB PS rc=%d\n", rc);
+
+ return rc;
+}
+
+static irqreturn_t lab_vreg_ok_handler(int irq, void *_labibb)
+{
+ struct qpnp_labibb *labibb = _labibb;
+ int rc;
+
+ if (labibb->skip_2nd_swire_cmd && labibb->lab_dig_major < 2) {
+ rc = qpnp_skip_swire_command(labibb);
+ if (rc < 0)
+ pr_err("Failed in 'qpnp_skip_swire_command' rc=%d\n",
+ rc);
+ } else if (labibb->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE &&
+ labibb->mode == QPNP_LABIBB_LCD_MODE) {
+ rc = qpnp_lab_pfm_enable(labibb);
+ if (rc < 0)
+ pr_err("Failed to config PFM, rc=%d\n", rc);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int qpnp_lab_regulator_get_voltage(struct regulator_dev *rdev)
+{
+ struct qpnp_labibb *labibb = rdev_get_drvdata(rdev);
+
+ if (labibb->swire_control)
+ return 0;
+
+ return labibb->lab_vreg.curr_volt;
+}
+
+static bool is_lab_vreg_ok_irq_available(struct qpnp_labibb *labibb)
+{
+ /*
+ * LAB VREG_OK interrupt is used only to skip 2nd SWIRE command in
+ * dig_major < 2 targets. For pmi8998, it is used to enable PFM in
+ * LCD mode.
+ */
+ if (labibb->skip_2nd_swire_cmd && labibb->lab_dig_major < 2)
+ return true;
+
+ if (labibb->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE &&
+ labibb->mode == QPNP_LABIBB_LCD_MODE)
+ return true;
+
+ return false;
+}
+
+static struct regulator_ops qpnp_lab_ops = {
+ .enable = qpnp_lab_regulator_enable,
+ .disable = qpnp_lab_regulator_disable,
+ .is_enabled = qpnp_lab_regulator_is_enabled,
+ .set_voltage = qpnp_lab_regulator_set_voltage,
+ .get_voltage = qpnp_lab_regulator_get_voltage,
+};
+
+static int register_qpnp_lab_regulator(struct qpnp_labibb *labibb,
+ struct device_node *of_node)
+{
+ int rc = 0;
+ struct regulator_init_data *init_data;
+ struct regulator_desc *rdesc = &labibb->lab_vreg.rdesc;
+ struct regulator_config cfg = {};
+ u8 val, mask;
+ const char *current_sense_str;
+ bool config_current_sense = false;
+ u32 tmp;
+
+ if (!of_node) {
+ dev_err(labibb->dev, "qpnp lab regulator device tree node is missing\n");
+ return -EINVAL;
+ }
+
+ init_data = of_get_regulator_init_data(labibb->dev, of_node, rdesc);
+ if (!init_data) {
+ pr_err("unable to get regulator init data for qpnp lab regulator\n");
+ return -ENOMEM;
+ }
+
+ rc = of_property_read_u32(of_node, "qcom,qpnp-lab-min-voltage",
+ &(labibb->lab_vreg.min_volt));
+ if (rc < 0) {
+ pr_err("qcom,qpnp-lab-min-voltage is missing, rc = %d\n",
+ rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(of_node, "qcom,qpnp-lab-step-size",
+ &(labibb->lab_vreg.step_size));
+ if (rc < 0) {
+ pr_err("qcom,qpnp-lab-step-size is missing, rc = %d\n", rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(of_node, "qcom,qpnp-lab-slew-rate",
+ &(labibb->lab_vreg.slew_rate));
+ if (rc < 0) {
+ pr_err("qcom,qpnp-lab-slew-rate is missing, rc = %d\n",
+ rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(of_node, "qcom,qpnp-lab-soft-start",
+ &(labibb->lab_vreg.soft_start));
+ if (!rc) {
+ for (val = 0; val < ARRAY_SIZE(lab_soft_start_table); val++)
+ if (lab_soft_start_table[val] ==
+ labibb->lab_vreg.soft_start)
+ break;
+
+ if (val == ARRAY_SIZE(lab_soft_start_table))
+ val = ARRAY_SIZE(lab_soft_start_table) - 1;
+
+ rc = qpnp_labibb_write(labibb, labibb->lab_base +
+ REG_LAB_SOFT_START_CTL, &val, 1);
+ if (rc < 0) {
+ pr_err("qpnp_labibb_write register %x failed rc = %d\n",
+ REG_LAB_SOFT_START_CTL, rc);
+ return rc;
+ }
+
+ labibb->lab_vreg.soft_start = lab_soft_start_table
+ [val & LAB_SOFT_START_CTL_MASK];
+ }
+
+ val = 0;
+ mask = 0;
+ rc = of_property_read_u32(of_node,
+ "qcom,qpnp-lab-max-precharge-time", &tmp);
+ if (!rc) {
+ for (val = 0; val < ARRAY_SIZE(lab_max_precharge_table); val++)
+ if (lab_max_precharge_table[val] == tmp)
+ break;
+
+ if (val == ARRAY_SIZE(lab_max_precharge_table)) {
+ pr_err("Invalid value in qcom,qpnp-lab-max-precharge-time\n");
+ return -EINVAL;
+ }
+
+ mask = LAB_MAX_PRECHARGE_TIME_MASK;
+ }
+
+ if (of_property_read_bool(of_node,
+ "qcom,qpnp-lab-max-precharge-enable")) {
+ val |= LAB_FAST_PRECHARGE_CTL_EN;
+ mask |= LAB_FAST_PRECHARGE_CTL_EN;
+ }
+
+ rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
+ REG_LAB_PRECHARGE_CTL, mask, val);
+ if (rc < 0) {
+ pr_err("qpnp_lab_dt_init write register %x failed rc = %d\n",
+ REG_LAB_PRECHARGE_CTL, rc);
+ return rc;
+ }
+
+ if (labibb->mode == QPNP_LABIBB_AMOLED_MODE &&
+ labibb->pmic_rev_id->pmic_subtype != PM660L_SUBTYPE) {
+ /*
+ * default to 1.5 times current gain if
+ * user doesn't specify the current-sense
+ * dt parameter
+ */
+ current_sense_str = "1.5x";
+ val = qpnp_labibb_get_matching_idx(current_sense_str);
+ config_current_sense = true;
+ }
+
+ if (of_find_property(of_node,
+ "qcom,qpnp-lab-current-sense", NULL)) {
+ config_current_sense = true;
+ rc = of_property_read_string(of_node,
+ "qcom,qpnp-lab-current-sense",
+ ¤t_sense_str);
+ if (!rc) {
+ val = qpnp_labibb_get_matching_idx(
+ current_sense_str);
+ } else {
+ pr_err("qcom,qpnp-lab-current-sense configured incorrectly rc = %d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ if (config_current_sense) {
+ rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
+ REG_LAB_CURRENT_SENSE,
+ LAB_CURRENT_SENSE_GAIN_MASK,
+ val);
+ if (rc < 0) {
+ pr_err("qpnp_labibb_write register %x failed rc = %d\n",
+ REG_LAB_CURRENT_SENSE, rc);
+ return rc;
+ }
+ }
+
+ val = (labibb->standalone) ? 0 : LAB_IBB_EN_RDY_EN;
+ rc = qpnp_labibb_sec_write(labibb, labibb->lab_base,
+ REG_LAB_IBB_EN_RDY, val);
+
+ if (rc < 0) {
+ pr_err("qpnp_lab_sec_write register %x failed rc = %d\n",
+ REG_LAB_IBB_EN_RDY, rc);
+ return rc;
+ }
+
+ rc = qpnp_labibb_read(labibb, labibb->ibb_base + REG_IBB_ENABLE_CTL,
+ &val, 1);
+ if (rc < 0) {
+ pr_err("qpnp_labibb_read register %x failed rc = %d\n",
+ REG_IBB_ENABLE_CTL, rc);
+ return rc;
+ }
+
+ if (!(val & (IBB_ENABLE_CTL_SWIRE_RDY | IBB_ENABLE_CTL_MODULE_EN))) {
+ /* SWIRE_RDY and IBB_MODULE_EN not enabled */
+ rc = qpnp_lab_dt_init(labibb, of_node);
+ if (rc < 0) {
+ pr_err("qpnp-lab: wrong DT parameter specified: rc = %d\n",
+ rc);
+ return rc;
+ }
+ } else {
+ rc = labibb->ibb_ver_ops->get_mode(labibb);
+
+ rc = qpnp_labibb_read(labibb, labibb->lab_base +
+ REG_LAB_VOLTAGE, &val, 1);
+ if (rc < 0) {
+ pr_err("qpnp_lab_read read register %x failed rc = %d\n",
+ REG_LAB_VOLTAGE, rc);
+ return rc;
+ }
+
+ labibb->lab_vreg.curr_volt =
+ (val &
+ LAB_VOLTAGE_SET_MASK) *
+ labibb->lab_vreg.step_size +
+ labibb->lab_vreg.min_volt;
+ if (labibb->mode == QPNP_LABIBB_LCD_MODE) {
+ rc = of_property_read_u32(of_node,
+ "qcom,qpnp-lab-init-lcd-voltage",
+ &(labibb->lab_vreg.curr_volt));
+ if (rc < 0) {
+ pr_err("get qcom,qpnp-lab-init-lcd-voltage failed, rc = %d\n",
+ rc);
+ return rc;
+ }
+ } else if (!(val & LAB_VOLTAGE_OVERRIDE_EN)) {
+ rc = of_property_read_u32(of_node,
+ "qcom,qpnp-lab-init-amoled-voltage",
+ &(labibb->lab_vreg.curr_volt));
+ if (rc < 0) {
+ pr_err("get qcom,qpnp-lab-init-amoled-voltage failed, rc = %d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ labibb->lab_vreg.vreg_enabled = 1;
+ }
+
+ if (is_lab_vreg_ok_irq_available(labibb)) {
+ rc = devm_request_threaded_irq(labibb->dev,
+ labibb->lab_vreg.lab_vreg_ok_irq, NULL,
+ lab_vreg_ok_handler,
+ IRQF_ONESHOT | IRQF_TRIGGER_RISING,
+ "lab-vreg-ok", labibb);
+ if (rc) {
+ pr_err("Failed to register 'lab-vreg-ok' irq rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ rc = qpnp_labibb_read(labibb, labibb->lab_base + REG_LAB_MODULE_RDY,
+ &val, 1);
+ if (rc < 0) {
+ pr_err("qpnp_lab_read read register %x failed rc = %d\n",
+ REG_LAB_MODULE_RDY, rc);
+ return rc;
+ }
+
+ if (!(val & LAB_MODULE_RDY_EN)) {
+ val = LAB_MODULE_RDY_EN;
+
+ rc = qpnp_labibb_write(labibb, labibb->lab_base +
+ REG_LAB_MODULE_RDY, &val, 1);
+
+ if (rc < 0) {
+ pr_err("qpnp_lab_dt_init write register %x failed rc = %d\n",
+ REG_LAB_MODULE_RDY, rc);
+ return rc;
+ }
+ }
+
+ if (init_data->constraints.name) {
+ rdesc->owner = THIS_MODULE;
+ rdesc->type = REGULATOR_VOLTAGE;
+ rdesc->ops = &qpnp_lab_ops;
+ rdesc->name = init_data->constraints.name;
+
+ cfg.dev = labibb->dev;
+ cfg.init_data = init_data;
+ cfg.driver_data = labibb;
+ cfg.of_node = of_node;
+
+ if (of_get_property(labibb->dev->of_node, "parent-supply",
+ NULL))
+ init_data->supply_regulator = "parent";
+
+ init_data->constraints.valid_ops_mask
+ |= REGULATOR_CHANGE_VOLTAGE |
+ REGULATOR_CHANGE_STATUS;
+
+ labibb->lab_vreg.rdev = regulator_register(rdesc, &cfg);
+ if (IS_ERR(labibb->lab_vreg.rdev)) {
+ rc = PTR_ERR(labibb->lab_vreg.rdev);
+ labibb->lab_vreg.rdev = NULL;
+ pr_err("unable to get regulator init data for qpnp lab regulator, rc = %d\n",
+ rc);
+
+ return rc;
+ }
+ } else {
+ dev_err(labibb->dev, "qpnp lab regulator name missing\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int qpnp_ibb_pfm_mode_enable(struct qpnp_labibb *labibb,
+ struct device_node *of_node)
+{
+ int rc = 0;
+ u32 i, tmp = 0;
+ u8 val = IBB_PFM_ENABLE;
+
+ rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-pfm-peak-curr",
+ &tmp);
+ if (rc < 0) {
+ pr_err("qcom,qpnp-ibb-pfm-peak-curr is missing, rc = %d\n",
+ rc);
+ return rc;
+ }
+ for (i = 0; i < ARRAY_SIZE(ibb_pfm_peak_curr_table); i++)
+ if (ibb_pfm_peak_curr_table[i] == tmp)
+ break;
+
+ if (i == ARRAY_SIZE(ibb_pfm_peak_curr_table)) {
+ pr_err("Invalid value in qcom,qpnp-ibb-pfm-peak-curr\n");
+ return -EINVAL;
+ }
+
+ val |= (i << IBB_PFM_PEAK_CURRENT_BIT_SHIFT);
+
+ rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-pfm-hysteresis",
+ &tmp);
+ if (rc < 0) {
+ pr_err("qcom,qpnp-ibb-pfm-hysteresis is missing, rc = %d\n",
+ rc);
+ return rc;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(ibb_pfm_hysteresis_table); i++)
+ if (ibb_pfm_hysteresis_table[i] == tmp)
+ break;
+
+ if (i == ARRAY_SIZE(ibb_pfm_hysteresis_table)) {
+ pr_err("Invalid value in qcom,qpnp-ibb-pfm-hysteresis\n");
+ return -EINVAL;
+ }
+
+ val |= (i << IBB_PFM_HYSTERESIS_BIT_SHIFT);
+
+ rc = qpnp_labibb_write(labibb, labibb->ibb_base +
+ REG_IBB_PFM_CTL, &val, 1);
+ if (rc < 0)
+ pr_err("qpnp_ibb_pfm_ctl write register %x failed rc = %d\n",
+ REG_IBB_PFM_CTL, rc);
+
+ return rc;
+}
+
+static int qpnp_labibb_pbs_mode_enable(struct qpnp_labibb *labibb,
+ struct device_node *of_node)
+{
+ int rc = 0;
+
+ rc = qpnp_labibb_masked_write(labibb, labibb->ibb_base +
+ REG_IBB_SWIRE_CTL,
+ IBB_SWIRE_VOUT_UPD_EN, 0);
+ if (rc < 0) {
+ pr_err("qpnp_ibb_swire_ctl write register %x failed rc = %d\n",
+ REG_IBB_SWIRE_CTL, rc);
+ return rc;
+ }
+
+ rc = qpnp_labibb_masked_write(labibb, labibb->ibb_base +
+ REG_IBB_PD_CTL, IBB_SWIRE_PD_UPD, 0);
+ if (rc < 0) {
+ pr_err("qpnp_ibb_pd_ctl write register %x failed rc = %d\n",
+ REG_IBB_PD_CTL, rc);
+ return rc;
+ }
+
+ rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
+ REG_LAB_SWIRE_PGM_CTL, LAB_EN_SWIRE_PGM_VOUT |
+ LAB_EN_SWIRE_PGM_PD, 0);
+ if (rc < 0)
+ pr_err("qpnp_lab_swire_pgm_ctl write register %x failed rc = %d\n",
+ REG_LAB_SWIRE_PGM_CTL, rc);
+
+ return rc;
+}
+
+static int qpnp_ibb_slew_rate_config(struct qpnp_labibb *labibb,
+ struct device_node *of_node)
+{
+ int rc = 0;
+ u32 i, tmp = 0;
+ u8 val = 0, mask = 0;
+
+ rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-fast-slew-rate",
+ &tmp);
+ if (!rc) {
+ for (i = 0; i < ARRAY_SIZE(ibb_output_slew_ctl_table); i++)
+ if (ibb_output_slew_ctl_table[i] == tmp)
+ break;
+
+ if (i == ARRAY_SIZE(ibb_output_slew_ctl_table)) {
+ pr_err("Invalid value in qcom,qpnp-ibb-fast-slew-rate\n");
+ return -EINVAL;
+ }
+
+ labibb->ibb_vreg.slew_rate = tmp;
+ val |= (i << IBB_SLEW_RATE_TRANS_TIME_FAST_SHIFT) |
+ IBB_SLEW_RATE_SPEED_FAST_EN | IBB_SLEW_CTL_EN;
+
+ mask = IBB_SLEW_RATE_SPEED_FAST_EN |
+ IBB_SLEW_RATE_TRANS_TIME_FAST_MASK | IBB_SLEW_CTL_EN;
+ }
+
+ rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-slow-slew-rate",
+ &tmp);
+ if (!rc) {
+ for (i = 0; i < ARRAY_SIZE(ibb_output_slew_ctl_table); i++)
+ if (ibb_output_slew_ctl_table[i] == tmp)
+ break;
+
+ if (i == ARRAY_SIZE(ibb_output_slew_ctl_table)) {
+ pr_err("Invalid value in qcom,qpnp-ibb-slow-slew-rate\n");
+ return -EINVAL;
+ }
+
+ labibb->ibb_vreg.slew_rate = tmp;
+ val |= (i | IBB_SLEW_CTL_EN);
+
+ mask |= IBB_SLEW_RATE_SPEED_FAST_EN |
+ IBB_SLEW_RATE_TRANS_TIME_SLOW_MASK | IBB_SLEW_CTL_EN;
+ }
+
+ rc = qpnp_labibb_masked_write(labibb, labibb->ibb_base +
+ REG_IBB_OUTPUT_SLEW_CTL,
+ mask, val);
+ if (rc < 0)
+ pr_err("qpnp_labibb_write register %x failed rc = %d\n",
+ REG_IBB_OUTPUT_SLEW_CTL, rc);
+
+ return rc;
+}
+
+static bool qpnp_ibb_poff_ctl_required(struct qpnp_labibb *labibb)
+{
+ if (labibb->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE)
+ return false;
+
+ return true;
+}
+
+static int qpnp_ibb_dt_init(struct qpnp_labibb *labibb,
+ struct device_node *of_node)
+{
+ int rc = 0;
+ u32 i, tmp = 0;
+ u8 val, mask;
+
+ /*
+ * Do not configure LCD_AMOLED_SEL for pmi8998 as it will be done by
+ * GPIO selector. Override the labibb->mode with what was configured
+ * by the bootloader.
+ */
+ if (labibb->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE) {
+ rc = qpnp_labibb_read(labibb, labibb->ibb_base +
+ REG_IBB_LCD_AMOLED_SEL, &val, 1);
+ if (rc) {
+ pr_err("qpnp_labibb_read register %x failed rc = %d\n",
+ REG_IBB_LCD_AMOLED_SEL, rc);
+ return rc;
+ }
+ if (val == REG_LAB_IBB_AMOLED_MODE)
+ labibb->mode = QPNP_LABIBB_AMOLED_MODE;
+ else
+ labibb->mode = QPNP_LABIBB_LCD_MODE;
+ } else {
+ rc = labibb->ibb_ver_ops->sel_mode(labibb, 1);
+ if (rc < 0) {
+ pr_err("qpnp_labibb_sec_write register %x failed rc = %d\n",
+ REG_IBB_LCD_AMOLED_SEL, rc);
+ return rc;
+ }
+ }
+
+ val = 0;
+ mask = 0;
+ rc = of_property_read_u32(of_node,
+ "qcom,qpnp-ibb-lab-pwrdn-delay", &tmp);
+ if (!rc) {
+ for (val = 0; val < ARRAY_SIZE(ibb_pwrdn_dly_table); val++)
+ if (ibb_pwrdn_dly_table[val] == tmp)
+ break;
+
+ if (val == ARRAY_SIZE(ibb_pwrdn_dly_table)) {
+ pr_err("Invalid value in qcom,qpnp-ibb-lab-pwrdn-delay\n");
+ return -EINVAL;
+ }
+
+ labibb->ibb_vreg.pwrdn_dly = tmp;
+ val |= IBB_PWRUP_PWRDN_CTL_1_EN_DLY2;
+ mask |= IBB_PWRUP_PWRDN_CTL_1_EN_DLY2;
+ }
+
+ rc = of_property_read_u32(of_node,
+ "qcom,qpnp-ibb-lab-pwrup-delay", &tmp);
+ if (!rc) {
+ for (i = 0; i < ARRAY_SIZE(ibb_pwrup_dly_table); i++)
+ if (ibb_pwrup_dly_table[i] == tmp)
+ break;
+
+ if (i == ARRAY_SIZE(ibb_pwrup_dly_table)) {
+ pr_err("Invalid value in qcom,qpnp-ibb-lab-pwrup-delay\n");
+ return -EINVAL;
+ }
+
+ labibb->ibb_vreg.pwrup_dly = tmp;
+
+ val |= (i << IBB_PWRUP_PWRDN_CTL_1_DLY1_SHIFT);
+ val |= (IBB_PWRUP_PWRDN_CTL_1_EN_DLY1 |
+ IBB_PWRUP_PWRDN_CTL_1_LAB_VREG_OK);
+ mask |= (IBB_PWRUP_PWRDN_CTL_1_EN_DLY1 |
+ IBB_PWRUP_PWRDN_CTL_1_DLY1_MASK |
+ IBB_PWRUP_PWRDN_CTL_1_LAB_VREG_OK);
+ }
+
+ if (of_property_read_bool(of_node,
+ "qcom,qpnp-ibb-en-discharge")) {
+ val |= PWRUP_PWRDN_CTL_1_DISCHARGE_EN;
+ mask |= PWRUP_PWRDN_CTL_1_DISCHARGE_EN;
+ }
+
+ rc = qpnp_labibb_sec_masked_write(labibb, labibb->ibb_base,
+ REG_IBB_PWRUP_PWRDN_CTL_1, mask, val);
+ if (rc < 0) {
+ pr_err("qpnp_labibb_sec_write register %x failed rc = %d\n",
+ REG_IBB_PWRUP_PWRDN_CTL_1, rc);
+ return rc;
+ }
+
+ if (of_property_read_bool(of_node, "qcom,qpnp-ibb-slew-rate-config")) {
+
+ rc = qpnp_ibb_slew_rate_config(labibb, of_node);
+ if (rc < 0)
+ return rc;
+ }
+
+ val = 0;
+ if (!of_property_read_bool(of_node, "qcom,qpnp-ibb-full-pull-down"))
+ val = IBB_PD_CTL_HALF_STRENGTH;
+
+ if (of_property_read_bool(of_node, "qcom,qpnp-ibb-pull-down-enable"))
+ val |= IBB_PD_CTL_EN;
+
+ mask = IBB_PD_CTL_STRENGTH_MASK | IBB_PD_CTL_EN;
+ rc = qpnp_labibb_masked_write(labibb,
+ labibb->ibb_base + REG_IBB_PD_CTL, mask, val);
+
+ if (rc < 0) {
+ pr_err("qpnp_lab_dt_init write register %x failed rc = %d\n",
+ REG_IBB_PD_CTL, rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(of_node,
+ "qcom,qpnp-ibb-switching-clock-frequency", &tmp);
+ if (!rc) {
+ for (val = 0; val < ARRAY_SIZE(ibb_clk_div_table); val++)
+ if (ibb_clk_div_table[val] == tmp)
+ break;
+
+ if (val == ARRAY_SIZE(ibb_clk_div_table)) {
+ pr_err("Invalid value in qpnp-ibb-switching-clock-frequency\n");
+ return -EINVAL;
+ }
+ rc = labibb->ibb_ver_ops->set_clk_div(labibb, val);
+ if (rc < 0) {
+ pr_err("qpnp_ibb_dt_init write register %x failed rc = %d\n",
+ REG_IBB_CLK_DIV, rc);
+ return rc;
+ }
+ }
+
+ val = 0;
+ mask = 0;
+ rc = of_property_read_u32(of_node,
+ "qcom,qpnp-ibb-limit-maximum-current", &tmp);
+ if (!rc) {
+ for (val = 0; val < ARRAY_SIZE(ibb_current_limit_table); val++)
+ if (ibb_current_limit_table[val] == tmp)
+ break;
+
+ if (val == ARRAY_SIZE(ibb_current_limit_table)) {
+ pr_err("Invalid value in qcom,qpnp-ibb-limit-maximum-current\n");
+ return -EINVAL;
+ }
+
+ mask = IBB_CURRENT_LIMIT_MASK;
+ }
+
+ rc = of_property_read_u32(of_node,
+ "qcom,qpnp-ibb-debounce-cycle", &tmp);
+ if (!rc) {
+ for (i = 0; i < ARRAY_SIZE(ibb_debounce_table); i++)
+ if (ibb_debounce_table[i] == tmp)
+ break;
+
+ if (i == ARRAY_SIZE(ibb_debounce_table)) {
+ pr_err("Invalid value in qcom,qpnp-ibb-debounce-cycle\n");
+ return -EINVAL;
+ }
+
+ val |= (i << IBB_CURRENT_LIMIT_DEBOUNCE_SHIFT);
+ mask |= IBB_CURRENT_LIMIT_DEBOUNCE_MASK;
+ }
+
+ if (of_property_read_bool(of_node,
+ "qcom,qpnp-ibb-limit-max-current-enable")) {
+ val |= IBB_CURRENT_LIMIT_EN;
+ mask |= IBB_CURRENT_LIMIT_EN;
+ }
+
+ rc = qpnp_labibb_sec_masked_write(labibb, labibb->ibb_base,
+ REG_IBB_CURRENT_LIMIT, mask, val);
+ if (rc < 0) {
+ pr_err("qpnp_labibb_sec_write register %x failed rc = %d\n",
+ REG_IBB_CURRENT_LIMIT, rc);
+ return rc;
+ }
+
+ if (of_property_read_bool(of_node,
+ "qcom,qpnp-ibb-ring-suppression-enable")) {
+ val = IBB_RING_SUPPRESSION_CTL_EN;
+ rc = qpnp_labibb_write(labibb, labibb->ibb_base +
+ REG_IBB_RING_SUPPRESSION_CTL,
+ &val,
+ 1);
+ if (rc < 0) {
+ pr_err("qpnp_ibb_dt_init write register %x failed rc = %d\n",
+ REG_IBB_RING_SUPPRESSION_CTL, rc);
+ return rc;
+ }
+ }
+
+ if (of_property_read_bool(of_node, "qcom,qpnp-ibb-ps-enable")) {
+ rc = qpnp_ibb_ps_config(labibb, true);
+ if (rc < 0) {
+ pr_err("qpnp_ibb_dt_init PS enable failed rc=%d\n", rc);
+ return rc;
+ }
+ } else {
+ rc = qpnp_ibb_ps_config(labibb, false);
+ if (rc < 0) {
+ pr_err("qpnp_ibb_dt_init PS disable failed rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ if (of_property_read_bool(of_node,
+ "qcom,qpnp-ibb-smart-ps-enable")){
+ of_property_read_u32(of_node, "qcom,qpnp-ibb-num-swire-trans",
+ &labibb->ibb_vreg.num_swire_trans);
+
+ of_property_read_u32(of_node,
+ "qcom,qpnp-ibb-neg-curr-limit", &tmp);
+
+ rc = labibb->ibb_ver_ops->smart_ps_config(labibb, true,
+ labibb->ibb_vreg.num_swire_trans, tmp);
+ if (rc < 0) {
+ pr_err("qpnp_ibb_dt_init smart PS enable failed rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ }
+
+ rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-init-voltage",
+ &(labibb->ibb_vreg.curr_volt));
+ if (rc < 0) {
+ pr_err("get qcom,qpnp-ibb-init-voltage failed, rc = %d\n", rc);
+ return rc;
+ }
+
+ if (of_property_read_bool(of_node,
+ "qcom,qpnp-ibb-use-default-voltage"))
+ rc = labibb->ibb_ver_ops->set_default_voltage(labibb, true);
+ else
+ rc = labibb->ibb_ver_ops->set_default_voltage(labibb, false);
+
+ if (rc < 0)
+ return rc;
+
+ if (of_property_read_bool(of_node, "qcom,qpnp-ibb-overload-blank")) {
+ rc = qpnp_ibb_vreg_ok_ctl(labibb, of_node);
+ if (rc < 0)
+ return rc;
+ }
+
+ return 0;
+}
+
+static int qpnp_ibb_regulator_enable(struct regulator_dev *rdev)
+{
+ int rc, delay, retries = 10;
+ u8 val;
+ struct qpnp_labibb *labibb = rdev_get_drvdata(rdev);
+
+ if (!labibb->ibb_vreg.vreg_enabled && !labibb->swire_control) {
+
+ if (!labibb->standalone)
+ return qpnp_labibb_regulator_enable(labibb);
+
+ rc = qpnp_ibb_set_mode(labibb, IBB_SW_CONTROL_EN);
+ if (rc < 0) {
+ pr_err("Unable to set IBB_MODULE_EN rc = %d\n", rc);
+ return rc;
+ }
+
+ delay = labibb->ibb_vreg.soft_start;
+ while (retries--) {
+ /* Wait for a small period before reading IBB_STATUS1 */
+ usleep_range(delay, delay + 100);
+
+ rc = qpnp_labibb_read(labibb, labibb->ibb_base +
+ REG_IBB_STATUS1, &val, 1);
+ if (rc < 0) {
+ pr_err("qpnp_ibb_regulator_enable read register %x failed rc = %d\n",
+ REG_IBB_STATUS1, rc);
+ return rc;
+ }
+
+ if (val & IBB_STATUS1_VREG_OK)
+ break;
+ }
+
+ if (!(val & IBB_STATUS1_VREG_OK)) {
+ pr_err("qpnp_ibb_regulator_enable failed\n");
+ return -EINVAL;
+ }
+
+ labibb->ibb_vreg.vreg_enabled = 1;
+ }
+ return 0;
+}
+
+static int qpnp_ibb_regulator_disable(struct regulator_dev *rdev)
+{
+ int rc;
+ struct qpnp_labibb *labibb = rdev_get_drvdata(rdev);
+
+ if (labibb->ibb_vreg.vreg_enabled && !labibb->swire_control) {
+
+ if (!labibb->standalone)
+ return qpnp_labibb_regulator_disable(labibb);
+
+ rc = qpnp_ibb_set_mode(labibb, IBB_SW_CONTROL_DIS);
+ if (rc < 0) {
+ pr_err("Unable to set IBB_MODULE_EN rc = %d\n", rc);
+ return rc;
+ }
+
+ labibb->ibb_vreg.vreg_enabled = 0;
+ }
+ return 0;
+}
+
+static int qpnp_ibb_regulator_is_enabled(struct regulator_dev *rdev)
+{
+ struct qpnp_labibb *labibb = rdev_get_drvdata(rdev);
+
+ if (labibb->swire_control)
+ return 0;
+
+ return labibb->ibb_vreg.vreg_enabled;
+}
+
+static int qpnp_ibb_regulator_set_voltage(struct regulator_dev *rdev,
+ int min_uV, int max_uV, unsigned int *selector)
+{
+ int rc = 0;
+
+ struct qpnp_labibb *labibb = rdev_get_drvdata(rdev);
+
+ if (labibb->swire_control)
+ return 0;
+
+ rc = labibb->ibb_ver_ops->set_voltage(labibb, min_uV, max_uV);
+ return rc;
+}
+
+
+static int qpnp_ibb_regulator_get_voltage(struct regulator_dev *rdev)
+{
+ struct qpnp_labibb *labibb = rdev_get_drvdata(rdev);
+
+ if (labibb->swire_control)
+ return 0;
+
+ return labibb->ibb_vreg.curr_volt;
+}
+
+static struct regulator_ops qpnp_ibb_ops = {
+ .enable = qpnp_ibb_regulator_enable,
+ .disable = qpnp_ibb_regulator_disable,
+ .is_enabled = qpnp_ibb_regulator_is_enabled,
+ .set_voltage = qpnp_ibb_regulator_set_voltage,
+ .get_voltage = qpnp_ibb_regulator_get_voltage,
+};
+
+static int register_qpnp_ibb_regulator(struct qpnp_labibb *labibb,
+ struct device_node *of_node)
+{
+ int rc = 0;
+ struct regulator_init_data *init_data;
+ struct regulator_desc *rdesc = &labibb->ibb_vreg.rdesc;
+ struct regulator_config cfg = {};
+ u8 val, ibb_enable_ctl, index;
+ u32 tmp;
+
+ if (!of_node) {
+ dev_err(labibb->dev, "qpnp ibb regulator device tree node is missing\n");
+ return -EINVAL;
+ }
+
+ init_data = of_get_regulator_init_data(labibb->dev, of_node, rdesc);
+ if (!init_data) {
+ pr_err("unable to get regulator init data for qpnp ibb regulator\n");
+ return -ENOMEM;
+ }
+
+ rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-min-voltage",
+ &(labibb->ibb_vreg.min_volt));
+ if (rc < 0) {
+ pr_err("qcom,qpnp-ibb-min-voltage is missing, rc = %d\n",
+ rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-step-size",
+ &(labibb->ibb_vreg.step_size));
+ if (rc < 0) {
+ pr_err("qcom,qpnp-ibb-step-size is missing, rc = %d\n", rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-slew-rate",
+ &(labibb->ibb_vreg.slew_rate));
+ if (rc < 0)
+ labibb->ibb_vreg.slew_rate = IBB_HW_DEFAULT_SLEW_RATE;
+
+ rc = labibb->ibb_ver_ops->soft_start_ctl(labibb, of_node);
+ if (rc < 0) {
+ pr_err("qpnp_labibb_write register %x failed rc = %d\n",
+ REG_IBB_SOFT_START_CTL, rc);
+ return rc;
+ }
+
+ if (of_find_property(of_node, "qcom,output-voltage-one-pulse", NULL)) {
+ if (!labibb->swire_control) {
+ pr_err("output-voltage-one-pulse valid for SWIRE only\n");
+ return -EINVAL;
+ }
+ rc = of_property_read_u32(of_node,
+ "qcom,output-voltage-one-pulse", &tmp);
+ if (rc < 0) {
+ pr_err("failed to read qcom,output-voltage-one-pulse rc=%d\n",
+ rc);
+ return rc;
+ }
+ if (tmp > MAX_OUTPUT_PULSE_VOLTAGE_MV ||
+ tmp < MIN_OUTPUT_PULSE_VOLTAGE_MV) {
+ pr_err("Invalid one-pulse voltage range %d\n", tmp);
+ return -EINVAL;
+ }
+ rc = labibb->ibb_ver_ops->voltage_at_one_pulse(labibb, tmp);
+ if (rc < 0)
+ return rc;
+ }
+
+ rc = qpnp_labibb_read(labibb, labibb->ibb_base + REG_IBB_ENABLE_CTL,
+ &ibb_enable_ctl, 1);
+ if (rc < 0) {
+ pr_err("qpnp_ibb_read register %x failed rc = %d\n",
+ REG_IBB_ENABLE_CTL, rc);
+ return rc;
+ }
+
+ /*
+ * For pmi8998, override swire_control with what was configured
+ * before by the bootloader.
+ */
+ if (labibb->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE)
+ labibb->swire_control = ibb_enable_ctl &
+ IBB_ENABLE_CTL_SWIRE_RDY;
+
+ if (ibb_enable_ctl &
+ (IBB_ENABLE_CTL_SWIRE_RDY | IBB_ENABLE_CTL_MODULE_EN)) {
+
+ rc = labibb->ibb_ver_ops->get_mode(labibb);
+ if (rc < 0) {
+ pr_err("qpnp_labibb_read register %x failed rc = %d\n",
+ REG_IBB_LCD_AMOLED_SEL, rc);
+ return rc;
+ }
+ rc = qpnp_labibb_read(labibb, labibb->ibb_base +
+ REG_IBB_VOLTAGE, &val, 1);
+ if (rc < 0) {
+ pr_err("qpnp_labibb_read read register %x failed rc = %d\n",
+ REG_IBB_VOLTAGE, rc);
+ return rc;
+ }
+
+ labibb->ibb_vreg.curr_volt =
+ (val & IBB_VOLTAGE_SET_MASK) *
+ labibb->ibb_vreg.step_size +
+ labibb->ibb_vreg.min_volt;
+
+ if (labibb->mode == QPNP_LABIBB_LCD_MODE) {
+ rc = of_property_read_u32(of_node,
+ "qcom,qpnp-ibb-init-lcd-voltage",
+ &(labibb->ibb_vreg.curr_volt));
+ if (rc < 0) {
+ pr_err("get qcom,qpnp-ibb-init-lcd-voltage failed, rc = %d\n",
+ rc);
+ return rc;
+ }
+ } else if (!(val & IBB_VOLTAGE_OVERRIDE_EN)) {
+ rc = of_property_read_u32(of_node,
+ "qcom,qpnp-ibb-init-amoled-voltage",
+ &(labibb->ibb_vreg.curr_volt));
+ if (rc < 0) {
+ pr_err("get qcom,qpnp-ibb-init-amoled-voltage failed, rc = %d\n",
+ rc);
+ return rc;
+ }
+
+ }
+
+ rc = qpnp_labibb_read(labibb, labibb->ibb_base +
+ REG_IBB_PWRUP_PWRDN_CTL_1, &val, 1);
+ if (rc < 0) {
+ pr_err("qpnp_labibb_config_init read register %x failed rc = %d\n",
+ REG_IBB_PWRUP_PWRDN_CTL_1, rc);
+ return rc;
+ }
+
+ index = (val & IBB_PWRUP_PWRDN_CTL_1_DLY1_MASK) >>
+ IBB_PWRUP_PWRDN_CTL_1_DLY1_SHIFT;
+ labibb->ibb_vreg.pwrup_dly = ibb_pwrup_dly_table[index];
+ index = val & IBB_PWRUP_PWRDN_CTL_1_DLY2_MASK;
+ labibb->ibb_vreg.pwrdn_dly = ibb_pwrdn_dly_table[index];
+
+ labibb->ibb_vreg.vreg_enabled = 1;
+ } else {
+ /* SWIRE_RDY and IBB_MODULE_EN not enabled */
+ rc = qpnp_ibb_dt_init(labibb, of_node);
+ if (rc < 0) {
+ pr_err("qpnp-ibb: wrong DT parameter specified: rc = %d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ if (labibb->mode == QPNP_LABIBB_AMOLED_MODE &&
+ qpnp_ibb_poff_ctl_required(labibb)) {
+
+ val = IBB_OVERRIDE_NONOVERLAP | IBB_NFET_GATE_DELAY_2;
+ rc = qpnp_labibb_sec_masked_write(labibb, labibb->ibb_base,
+ REG_IBB_NONOVERLAP_TIME_1,
+ IBB_OVERRIDE_NONOVERLAP | IBB_NONOVERLAP_NFET_MASK,
+ val);
+
+ if (rc < 0) {
+ pr_err("qpnp_labibb_sec_masked_write register %x failed rc = %d\n",
+ REG_IBB_NONOVERLAP_TIME_1, rc);
+ return rc;
+ }
+
+ val = IBB_N2P_MUX_SEL;
+ rc = qpnp_labibb_sec_write(labibb, labibb->ibb_base,
+ REG_IBB_NONOVERLAP_TIME_2, val);
+
+ if (rc < 0) {
+ pr_err("qpnp_labibb_sec_write register %x failed rc = %d\n",
+ REG_IBB_NONOVERLAP_TIME_2, rc);
+ return rc;
+ }
+
+ val = IBB_FASTER_PFET_OFF;
+ rc = qpnp_labibb_masked_write(labibb,
+ labibb->ibb_base + REG_IBB_SPARE_CTL,
+ IBB_POFF_CTL_MASK, val);
+ if (rc < 0) {
+ pr_err("write to register %x failed rc = %d\n",
+ REG_IBB_SPARE_CTL, rc);
+ return rc;
+ }
+ }
+
+ if (labibb->standalone) {
+ val = 0;
+ rc = qpnp_labibb_sec_write(labibb, labibb->ibb_base,
+ REG_IBB_PWRUP_PWRDN_CTL_1, val);
+ if (rc < 0) {
+ pr_err("qpnp_labibb_sec_write register %x failed rc = %d\n",
+ REG_IBB_PWRUP_PWRDN_CTL_1, rc);
+ return rc;
+ }
+ labibb->ibb_vreg.pwrup_dly = 0;
+ labibb->ibb_vreg.pwrdn_dly = 0;
+ }
+
+ rc = qpnp_labibb_read(labibb, labibb->ibb_base + REG_IBB_MODULE_RDY,
+ &val, 1);
+ if (rc < 0) {
+ pr_err("qpnp_ibb_read read register %x failed rc = %d\n",
+ REG_IBB_MODULE_RDY, rc);
+ return rc;
+ }
+
+ if (!(val & IBB_MODULE_RDY_EN)) {
+ val = IBB_MODULE_RDY_EN;
+
+ rc = qpnp_labibb_write(labibb, labibb->ibb_base +
+ REG_IBB_MODULE_RDY, &val, 1);
+
+ if (rc < 0) {
+ pr_err("qpnp_ibb_dt_init write register %x failed rc = %d\n",
+ REG_IBB_MODULE_RDY, rc);
+ return rc;
+ }
+ }
+
+ if (of_property_read_bool(of_node,
+ "qcom,qpnp-ibb-enable-pfm-mode")) {
+ rc = qpnp_ibb_pfm_mode_enable(labibb, of_node);
+ if (rc < 0)
+ return rc;
+ }
+
+ if (labibb->pbs_control) {
+ rc = qpnp_labibb_pbs_mode_enable(labibb, of_node);
+ if (rc < 0)
+ return rc;
+ }
+
+ if (init_data->constraints.name) {
+ rdesc->owner = THIS_MODULE;
+ rdesc->type = REGULATOR_VOLTAGE;
+ rdesc->ops = &qpnp_ibb_ops;
+ rdesc->name = init_data->constraints.name;
+
+ cfg.dev = labibb->dev;
+ cfg.init_data = init_data;
+ cfg.driver_data = labibb;
+ cfg.of_node = of_node;
+
+ if (of_get_property(labibb->dev->of_node, "parent-supply",
+ NULL))
+ init_data->supply_regulator = "parent";
+
+ init_data->constraints.valid_ops_mask
+ |= REGULATOR_CHANGE_VOLTAGE |
+ REGULATOR_CHANGE_STATUS;
+
+ labibb->ibb_vreg.rdev = regulator_register(rdesc, &cfg);
+ if (IS_ERR(labibb->ibb_vreg.rdev)) {
+ rc = PTR_ERR(labibb->ibb_vreg.rdev);
+ labibb->ibb_vreg.rdev = NULL;
+ pr_err("unable to get regulator init data for qpnp ibb regulator, rc = %d\n",
+ rc);
+
+ return rc;
+ }
+ } else {
+ dev_err(labibb->dev, "qpnp ibb regulator name missing\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int qpnp_lab_register_irq(struct device_node *child,
+ struct qpnp_labibb *labibb)
+{
+ if (is_lab_vreg_ok_irq_available(labibb)) {
+ labibb->lab_vreg.lab_vreg_ok_irq =
+ of_irq_get_byname(child, "lab-vreg-ok");
+ if (labibb->lab_vreg.lab_vreg_ok_irq < 0) {
+ pr_err("Invalid lab-vreg-ok irq\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int qpnp_labibb_check_ttw_supported(struct qpnp_labibb *labibb)
+{
+ int rc = 0;
+ u8 val;
+
+ switch (labibb->pmic_rev_id->pmic_subtype) {
+ case PMI8996_SUBTYPE:
+ rc = qpnp_labibb_read(labibb, labibb->ibb_base +
+ REG_IBB_REVISION4, &val, 1);
+ if (rc < 0) {
+ pr_err("qpnp_labibb_read register %x failed rc = %d\n",
+ REG_IBB_REVISION4, rc);
+ return rc;
+ }
+
+ /* PMI8996 has revision 1 */
+ if (val < 1) {
+ pr_err("TTW feature cannot be enabled for revision %d\n",
+ val);
+ labibb->ttw_en = false;
+ }
+ /* FORCE_LAB_ON in TTW is not required for PMI8996 */
+ labibb->ttw_force_lab_on = false;
+ break;
+ case PMI8950_SUBTYPE:
+ /* TTW supported for all revisions */
+ break;
+ default:
+ pr_info("TTW mode not supported for PMIC-subtype = %d\n",
+ labibb->pmic_rev_id->pmic_subtype);
+ labibb->ttw_en = false;
+ break;
+
+ }
+ return rc;
+}
+
+static int qpnp_labibb_regulator_probe(struct platform_device *pdev)
+{
+ struct qpnp_labibb *labibb;
+ unsigned int base;
+ struct device_node *child, *revid_dev_node;
+ const char *mode_name;
+ u8 type, revision;
+ int rc = 0;
+
+ labibb = devm_kzalloc(&pdev->dev, sizeof(*labibb), GFP_KERNEL);
+ if (labibb == NULL)
+ return -ENOMEM;
+
+ labibb->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!labibb->regmap) {
+ dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+ return -EINVAL;
+ }
+
+ labibb->dev = &(pdev->dev);
+ labibb->pdev = pdev;
+
+ mutex_init(&(labibb->lab_vreg.lab_mutex));
+ mutex_init(&(labibb->ibb_vreg.ibb_mutex));
+ mutex_init(&(labibb->bus_mutex));
+
+ revid_dev_node = of_parse_phandle(labibb->dev->of_node,
+ "qcom,pmic-revid", 0);
+ if (!revid_dev_node) {
+ pr_err("Missing qcom,pmic-revid property - driver failed\n");
+ return -EINVAL;
+ }
+
+ labibb->pmic_rev_id = get_revid_data(revid_dev_node);
+ if (IS_ERR(labibb->pmic_rev_id)) {
+ pr_debug("Unable to get revid data\n");
+ return -EPROBE_DEFER;
+ }
+
+ if (labibb->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) {
+ labibb->ibb_ver_ops = &ibb_ops_v2;
+ labibb->lab_ver_ops = &lab_ops_v2;
+ } else {
+ labibb->ibb_ver_ops = &ibb_ops_v1;
+ labibb->lab_ver_ops = &lab_ops_v1;
+ }
+
+ if (labibb->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) {
+ labibb->mode = QPNP_LABIBB_AMOLED_MODE;
+ } else {
+ rc = of_property_read_string(labibb->dev->of_node,
+ "qcom,qpnp-labibb-mode", &mode_name);
+ if (!rc) {
+ if (strcmp("lcd", mode_name) == 0) {
+ labibb->mode = QPNP_LABIBB_LCD_MODE;
+ } else if (strcmp("amoled", mode_name) == 0) {
+ labibb->mode = QPNP_LABIBB_AMOLED_MODE;
+ } else {
+ pr_err("Invalid device property in qcom,qpnp-labibb-mode: %s\n",
+ mode_name);
+ return -EINVAL;
+ }
+ } else {
+ pr_err("qpnp_labibb: qcom,qpnp-labibb-mode is missing.\n");
+ return rc;
+ }
+ }
+
+ labibb->standalone = of_property_read_bool(labibb->dev->of_node,
+ "qcom,labibb-standalone");
+
+ labibb->ttw_en = of_property_read_bool(labibb->dev->of_node,
+ "qcom,labibb-touch-to-wake-en");
+ if (labibb->ttw_en && labibb->mode != QPNP_LABIBB_LCD_MODE) {
+ pr_err("Invalid mode for TTW\n");
+ return -EINVAL;
+ }
+
+ labibb->ttw_force_lab_on = of_property_read_bool(
+ labibb->dev->of_node, "qcom,labibb-ttw-force-lab-on");
+
+ labibb->swire_control = of_property_read_bool(labibb->dev->of_node,
+ "qcom,swire-control");
+
+ labibb->pbs_control = of_property_read_bool(labibb->dev->of_node,
+ "qcom,pbs-control");
+ if (labibb->swire_control && labibb->mode != QPNP_LABIBB_AMOLED_MODE) {
+ pr_err("Invalid mode for SWIRE control\n");
+ return -EINVAL;
+ }
+
+ if (labibb->swire_control) {
+ labibb->skip_2nd_swire_cmd =
+ of_property_read_bool(labibb->dev->of_node,
+ "qcom,skip-2nd-swire-cmd");
+
+ rc = of_property_read_u32(labibb->dev->of_node,
+ "qcom,swire-2nd-cmd-delay",
+ &labibb->swire_2nd_cmd_delay);
+ if (rc < 0)
+ labibb->swire_2nd_cmd_delay =
+ SWIRE_DEFAULT_2ND_CMD_DLY_MS;
+
+ rc = of_property_read_u32(labibb->dev->of_node,
+ "qcom,swire-ibb-ps-enable-delay",
+ &labibb->swire_ibb_ps_enable_delay);
+ if (rc < 0)
+ labibb->swire_ibb_ps_enable_delay =
+ SWIRE_DEFAULT_IBB_PS_ENABLE_DLY_MS;
+ }
+
+ if (of_get_available_child_count(pdev->dev.of_node) == 0) {
+ pr_err("no child nodes\n");
+ return -ENXIO;
+ }
+
+ for_each_available_child_of_node(pdev->dev.of_node, child) {
+ rc = of_property_read_u32(child, "reg", &base);
+ if (rc < 0) {
+ dev_err(&pdev->dev,
+ "Couldn't find reg in node = %s rc = %d\n",
+ child->full_name, rc);
+ return rc;
+ }
+
+ rc = qpnp_labibb_read(labibb, base + REG_REVISION_2,
+ &revision, 1);
+ if (rc < 0) {
+ pr_err("Reading REVISION_2 failed rc=%d\n", rc);
+ goto fail_registration;
+ }
+
+ rc = qpnp_labibb_read(labibb, base + REG_PERPH_TYPE,
+ &type, 1);
+ if (rc < 0) {
+ pr_err("Peripheral type read failed rc=%d\n", rc);
+ goto fail_registration;
+ }
+
+ switch (type) {
+ case QPNP_LAB_TYPE:
+ labibb->lab_base = base;
+ labibb->lab_dig_major = revision;
+ rc = qpnp_lab_register_irq(child, labibb);
+ if (rc) {
+ pr_err("Failed to register LAB IRQ rc=%d\n",
+ rc);
+ goto fail_registration;
+ }
+ rc = register_qpnp_lab_regulator(labibb, child);
+ if (rc < 0)
+ goto fail_registration;
+ break;
+
+ case QPNP_IBB_TYPE:
+ labibb->ibb_base = base;
+ labibb->ibb_dig_major = revision;
+ rc = register_qpnp_ibb_regulator(labibb, child);
+ if (rc < 0)
+ goto fail_registration;
+ break;
+
+ default:
+ pr_err("qpnp_labibb: unknown peripheral type %x\n",
+ type);
+ rc = -EINVAL;
+ goto fail_registration;
+ }
+ }
+
+ if (labibb->ttw_en) {
+ rc = qpnp_labibb_check_ttw_supported(labibb);
+ if (rc < 0) {
+ pr_err("pmic revision check failed for TTW rc=%d\n",
+ rc);
+ goto fail_registration;
+ }
+ }
+ dev_set_drvdata(&pdev->dev, labibb);
+ pr_info("LAB/IBB registered successfully, lab_vreg enable=%d ibb_vreg enable=%d swire_control=%d\n",
+ labibb->lab_vreg.vreg_enabled,
+ labibb->ibb_vreg.vreg_enabled,
+ labibb->swire_control);
+
+ return 0;
+
+fail_registration:
+ if (labibb->lab_vreg.rdev)
+ regulator_unregister(labibb->lab_vreg.rdev);
+ if (labibb->ibb_vreg.rdev)
+ regulator_unregister(labibb->ibb_vreg.rdev);
+
+ return rc;
+}
+
+static int qpnp_labibb_regulator_remove(struct platform_device *pdev)
+{
+ struct qpnp_labibb *labibb = dev_get_drvdata(&pdev->dev);
+
+ if (labibb) {
+ if (labibb->lab_vreg.rdev)
+ regulator_unregister(labibb->lab_vreg.rdev);
+ if (labibb->ibb_vreg.rdev)
+ regulator_unregister(labibb->ibb_vreg.rdev);
+ }
+ return 0;
+}
+
+static const struct of_device_id spmi_match_table[] = {
+ { .compatible = QPNP_LABIBB_REGULATOR_DRIVER_NAME, },
+ { },
+};
+
+static struct platform_driver qpnp_labibb_regulator_driver = {
+ .driver = {
+ .name = QPNP_LABIBB_REGULATOR_DRIVER_NAME,
+ .of_match_table = spmi_match_table,
+ },
+ .probe = qpnp_labibb_regulator_probe,
+ .remove = qpnp_labibb_regulator_remove,
+};
+
+static int __init qpnp_labibb_regulator_init(void)
+{
+ return platform_driver_register(&qpnp_labibb_regulator_driver);
+}
+arch_initcall(qpnp_labibb_regulator_init);
+
+static void __exit qpnp_labibb_regulator_exit(void)
+{
+ platform_driver_unregister(&qpnp_labibb_regulator_driver);
+}
+module_exit(qpnp_labibb_regulator_exit);
+
+MODULE_DESCRIPTION("QPNP labibb driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/regulator/qpnp-lcdb-regulator.c b/drivers/regulator/qpnp-lcdb-regulator.c
new file mode 100644
index 0000000..a08ade6
--- /dev/null
+++ b/drivers/regulator/qpnp-lcdb-regulator.c
@@ -0,0 +1,1692 @@
+/*
+ * Copyright (c) 2016-2017, The Linux Foundation. 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.
+ */
+
+#define pr_fmt(fmt) "LCDB: %s: " fmt, __func__
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/regulator/machine.h>
+
+#define QPNP_LCDB_REGULATOR_DRIVER_NAME "qcom,qpnp-lcdb-regulator"
+
+/* LCDB */
+#define LCDB_STS1_REG 0x08
+
+#define INT_RT_STATUS_REG 0x10
+#define VREG_OK_RT_STS_BIT BIT(0)
+
+#define LCDB_AUTO_TOUCH_WAKE_CTL_REG 0x40
+#define EN_AUTO_TOUCH_WAKE_BIT BIT(7)
+#define ATTW_TOFF_TIME_MASK GENMASK(3, 2)
+#define ATTW_TON_TIME_MASK GENMASK(1, 0)
+#define ATTW_TOFF_TIME_SHIFT 2
+#define ATTW_MIN_MS 4
+#define ATTW_MAX_MS 32
+
+#define LCDB_BST_OUTPUT_VOLTAGE_REG 0x41
+
+#define LCDB_MODULE_RDY_REG 0x45
+#define MODULE_RDY_BIT BIT(7)
+
+#define LCDB_ENABLE_CTL1_REG 0x46
+#define MODULE_EN_BIT BIT(7)
+#define HWEN_RDY_BIT BIT(6)
+
+/* BST */
+#define LCDB_BST_PD_CTL_REG 0x47
+#define BOOST_DIS_PULLDOWN_BIT BIT(1)
+#define BOOST_PD_STRENGTH_BIT BIT(0)
+
+#define LCDB_BST_ILIM_CTL_REG 0x4B
+#define EN_BST_ILIM_BIT BIT(7)
+#define SET_BST_ILIM_MASK GENMASK(2, 0)
+#define MIN_BST_ILIM_MA 200
+#define MAX_BST_ILIM_MA 1600
+
+#define LCDB_PS_CTL_REG 0x50
+#define EN_PS_BIT BIT(7)
+#define PS_THRESHOLD_MASK GENMASK(1, 0)
+#define MIN_BST_PS_MA 50
+#define MAX_BST_PS_MA 80
+
+#define LCDB_RDSON_MGMNT_REG 0x53
+#define NFET_SW_SIZE_MASK GENMASK(3, 2)
+#define NFET_SW_SIZE_SHIFT 2
+#define PFET_SW_SIZE_MASK GENMASK(1, 0)
+
+#define LCDB_BST_VREG_OK_CTL_REG 0x55
+#define BST_VREG_OK_DEB_MASK GENMASK(1, 0)
+
+#define LCDB_SOFT_START_CTL_REG 0x5F
+
+#define LCDB_MISC_CTL_REG 0x60
+#define AUTO_GM_EN_BIT BIT(4)
+#define EN_TOUCH_WAKE_BIT BIT(3)
+#define DIS_SCP_BIT BIT(0)
+
+#define LCDB_PFM_CTL_REG 0x62
+#define EN_PFM_BIT BIT(7)
+#define BYP_BST_SOFT_START_COMP_BIT BIT(0)
+#define PFM_HYSTERESIS_SHIFT 4
+#define PFM_CURRENT_SHIFT 2
+
+#define LCDB_PWRUP_PWRDN_CTL_REG 0x66
+
+/* LDO */
+#define LCDB_LDO_OUTPUT_VOLTAGE_REG 0x71
+#define SET_OUTPUT_VOLTAGE_MASK GENMASK(4, 0)
+
+#define LCDB_LDO_VREG_OK_CTL_REG 0x75
+#define VREG_OK_DEB_MASK GENMASK(1, 0)
+
+#define LCDB_LDO_PD_CTL_REG 0x77
+#define LDO_DIS_PULLDOWN_BIT BIT(1)
+#define LDO_PD_STRENGTH_BIT BIT(0)
+
+#define LCDB_LDO_ILIM_CTL1_REG 0x7B
+#define EN_LDO_ILIM_BIT BIT(7)
+#define SET_LDO_ILIM_MASK GENMASK(2, 0)
+#define MIN_LDO_ILIM_MA 110
+#define MAX_LDO_ILIM_MA 460
+#define LDO_ILIM_STEP_MA 50
+
+#define LCDB_LDO_ILIM_CTL2_REG 0x7C
+
+#define LCDB_LDO_SOFT_START_CTL_REG 0x7F
+#define SOFT_START_MASK GENMASK(1, 0)
+
+/* NCP */
+#define LCDB_NCP_OUTPUT_VOLTAGE_REG 0x81
+
+#define LCDB_NCP_VREG_OK_CTL_REG 0x85
+
+#define LCDB_NCP_PD_CTL_REG 0x87
+#define NCP_DIS_PULLDOWN_BIT BIT(1)
+#define NCP_PD_STRENGTH_BIT BIT(0)
+
+#define LCDB_NCP_ILIM_CTL1_REG 0x8B
+#define EN_NCP_ILIM_BIT BIT(7)
+#define SET_NCP_ILIM_MASK GENMASK(1, 0)
+#define MIN_NCP_ILIM_MA 260
+#define MAX_NCP_ILIM_MA 810
+
+#define LCDB_NCP_ILIM_CTL2_REG 0x8C
+
+#define LCDB_NCP_SOFT_START_CTL_REG 0x8F
+
+/* common for BST/NCP/LDO */
+#define MIN_DBC_US 2
+#define MAX_DBC_US 32
+
+#define MIN_SOFT_START_US 0
+#define MAX_SOFT_START_US 2000
+
+struct ldo_regulator {
+ struct regulator_desc rdesc;
+ struct regulator_dev *rdev;
+ struct device_node *node;
+
+ /* LDO DT params */
+ int pd;
+ int pd_strength;
+ int ilim_ma;
+ int soft_start_us;
+ int vreg_ok_dbc_us;
+ int voltage_mv;
+};
+
+struct ncp_regulator {
+ struct regulator_desc rdesc;
+ struct regulator_dev *rdev;
+ struct device_node *node;
+
+ /* NCP DT params */
+ int pd;
+ int pd_strength;
+ int ilim_ma;
+ int soft_start_us;
+ int vreg_ok_dbc_us;
+ int voltage_mv;
+};
+
+struct bst_params {
+ struct device_node *node;
+
+ /* BST DT params */
+ int pd;
+ int pd_strength;
+ int ilim_ma;
+ int ps;
+ int ps_threshold;
+ int soft_start_us;
+ int vreg_ok_dbc_us;
+ int voltage_mv;
+};
+
+struct qpnp_lcdb {
+ struct device *dev;
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ u32 base;
+
+ /* TTW params */
+ bool ttw_enable;
+ bool ttw_mode_sw;
+
+ /* status parameters */
+ bool lcdb_enabled;
+ bool settings_saved;
+
+ struct mutex lcdb_mutex;
+ struct mutex read_write_mutex;
+ struct bst_params bst;
+ struct ldo_regulator ldo;
+ struct ncp_regulator ncp;
+};
+
+struct settings {
+ u16 address;
+ u8 value;
+ bool sec_access;
+};
+
+enum lcdb_module {
+ LDO,
+ NCP,
+ BST,
+};
+
+enum pfm_hysteresis {
+ PFM_HYST_15MV,
+ PFM_HYST_25MV,
+ PFM_HYST_35MV,
+ PFM_HYST_45MV,
+};
+
+enum pfm_peak_current {
+ PFM_PEAK_CURRENT_300MA,
+ PFM_PEAK_CURRENT_400MA,
+ PFM_PEAK_CURRENT_500MA,
+ PFM_PEAK_CURRENT_600MA,
+};
+
+enum rdson_fet_size {
+ RDSON_QUARTER,
+ RDSON_HALF,
+ RDSON_THREE_FOURTH,
+ RDSON_FULLSIZE,
+};
+
+enum lcdb_settings_index {
+ LCDB_BST_PD_CTL = 0,
+ LCDB_RDSON_MGMNT,
+ LCDB_MISC_CTL,
+ LCDB_SOFT_START_CTL,
+ LCDB_PFM_CTL,
+ LCDB_PWRUP_PWRDN_CTL,
+ LCDB_LDO_PD_CTL,
+ LCDB_LDO_SOFT_START_CTL,
+ LCDB_NCP_PD_CTL,
+ LCDB_NCP_SOFT_START_CTL,
+ LCDB_SETTING_MAX,
+};
+
+static u32 soft_start_us[] = {
+ 0,
+ 500,
+ 1000,
+ 2000,
+};
+
+static u32 dbc_us[] = {
+ 2,
+ 4,
+ 16,
+ 32,
+};
+
+static u32 ncp_ilim_ma[] = {
+ 260,
+ 460,
+ 640,
+ 810,
+};
+
+#define SETTING(_id, _sec_access) \
+ [_id] = { \
+ .address = _id##_REG, \
+ .sec_access = _sec_access, \
+ } \
+
+static bool is_between(int value, int min, int max)
+{
+ if (value < min || value > max)
+ return false;
+ return true;
+}
+
+static int qpnp_lcdb_read(struct qpnp_lcdb *lcdb,
+ u16 addr, u8 *value, u8 count)
+{
+ int rc = 0;
+
+ mutex_lock(&lcdb->read_write_mutex);
+ rc = regmap_bulk_read(lcdb->regmap, addr, value, count);
+ if (rc < 0)
+ pr_err("Failed to read from addr=0x%02x rc=%d\n", addr, rc);
+ mutex_unlock(&lcdb->read_write_mutex);
+
+ return rc;
+}
+
+static int qpnp_lcdb_write(struct qpnp_lcdb *lcdb,
+ u16 addr, u8 *value, u8 count)
+{
+ int rc;
+
+ mutex_lock(&lcdb->read_write_mutex);
+ rc = regmap_bulk_write(lcdb->regmap, addr, value, count);
+ if (rc < 0)
+ pr_err("Failed to write to addr=0x%02x rc=%d\n", addr, rc);
+ mutex_unlock(&lcdb->read_write_mutex);
+
+ return rc;
+}
+
+#define SEC_ADDRESS_REG 0xD0
+#define SECURE_UNLOCK_VALUE 0xA5
+static int qpnp_lcdb_secure_write(struct qpnp_lcdb *lcdb,
+ u16 addr, u8 value)
+{
+ int rc;
+ u8 val = SECURE_UNLOCK_VALUE;
+
+ mutex_lock(&lcdb->read_write_mutex);
+ rc = regmap_write(lcdb->regmap, lcdb->base + SEC_ADDRESS_REG, val);
+ if (rc < 0) {
+ pr_err("Failed to unlock register rc=%d\n", rc);
+ goto fail_write;
+ }
+ rc = regmap_write(lcdb->regmap, addr, value);
+ if (rc < 0)
+ pr_err("Failed to write to addr=0x%02x rc=%d\n", addr, rc);
+
+fail_write:
+ mutex_unlock(&lcdb->read_write_mutex);
+ return rc;
+}
+
+static int qpnp_lcdb_masked_write(struct qpnp_lcdb *lcdb,
+ u16 addr, u8 mask, u8 value)
+{
+ int rc = 0;
+
+ mutex_lock(&lcdb->read_write_mutex);
+ rc = regmap_update_bits(lcdb->regmap, addr, mask, value);
+ if (rc < 0)
+ pr_err("Failed to write addr=0x%02x value=0x%02x rc=%d\n",
+ addr, value, rc);
+ mutex_unlock(&lcdb->read_write_mutex);
+
+ return rc;
+}
+
+static bool is_lcdb_enabled(struct qpnp_lcdb *lcdb)
+{
+ int rc;
+ u8 val = 0;
+
+ rc = qpnp_lcdb_read(lcdb, lcdb->base + LCDB_ENABLE_CTL1_REG, &val, 1);
+ if (rc < 0)
+ pr_err("Failed to read ENABLE_CTL1 rc=%d\n", rc);
+
+ return rc ? false : !!(val & MODULE_EN_BIT);
+}
+
+static int dump_status_registers(struct qpnp_lcdb *lcdb)
+{
+ int rc = 0;
+ u8 sts[6] = {0};
+
+ rc = qpnp_lcdb_write(lcdb, lcdb->base + LCDB_STS1_REG, &sts[0], 6);
+ if (rc < 0) {
+ pr_err("Failed to write to STS registers rc=%d\n", rc);
+ } else {
+ rc = qpnp_lcdb_read(lcdb, lcdb->base + LCDB_STS1_REG, sts, 6);
+ if (rc < 0)
+ pr_err("Failed to read lcdb status rc=%d\n", rc);
+ else
+ pr_err("STS1=0x%02x STS2=0x%02x STS3=0x%02x STS4=0x%02x STS5=0x%02x, STS6=0x%02x\n",
+ sts[0], sts[1], sts[2], sts[3], sts[4], sts[5]);
+ }
+
+ return rc;
+}
+
+static struct settings lcdb_settings[] = {
+ SETTING(LCDB_BST_PD_CTL, false),
+ SETTING(LCDB_RDSON_MGMNT, false),
+ SETTING(LCDB_MISC_CTL, false),
+ SETTING(LCDB_SOFT_START_CTL, false),
+ SETTING(LCDB_PFM_CTL, false),
+ SETTING(LCDB_PWRUP_PWRDN_CTL, true),
+ SETTING(LCDB_LDO_PD_CTL, false),
+ SETTING(LCDB_LDO_SOFT_START_CTL, false),
+ SETTING(LCDB_NCP_PD_CTL, false),
+ SETTING(LCDB_NCP_SOFT_START_CTL, false),
+};
+
+static int qpnp_lcdb_save_settings(struct qpnp_lcdb *lcdb)
+{
+ int i, rc = 0;
+
+ for (i = 0; i < ARRAY_SIZE(lcdb_settings); i++) {
+ rc = qpnp_lcdb_read(lcdb, lcdb->base +
+ lcdb_settings[i].address,
+ &lcdb_settings[i].value, 1);
+ if (rc < 0) {
+ pr_err("Failed to read lcdb register address=%x\n",
+ lcdb_settings[i].address);
+ return rc;
+ }
+ }
+
+ return rc;
+}
+
+static int qpnp_lcdb_restore_settings(struct qpnp_lcdb *lcdb)
+{
+ int i, rc = 0;
+
+ for (i = 0; i < ARRAY_SIZE(lcdb_settings); i++) {
+ if (lcdb_settings[i].sec_access)
+ rc = qpnp_lcdb_secure_write(lcdb, lcdb->base +
+ lcdb_settings[i].address,
+ lcdb_settings[i].value);
+ else
+ rc = qpnp_lcdb_write(lcdb, lcdb->base +
+ lcdb_settings[i].address,
+ &lcdb_settings[i].value, 1);
+ if (rc < 0) {
+ pr_err("Failed to write register address=%x\n",
+ lcdb_settings[i].address);
+ return rc;
+ }
+ }
+
+ return rc;
+}
+
+static int qpnp_lcdb_ttw_enter(struct qpnp_lcdb *lcdb)
+{
+ int rc;
+ u8 val;
+
+ if (!lcdb->settings_saved) {
+ rc = qpnp_lcdb_save_settings(lcdb);
+ if (rc < 0) {
+ pr_err("Failed to save LCDB settings rc=%d\n", rc);
+ return rc;
+ }
+ lcdb->settings_saved = true;
+ }
+
+ val = BOOST_DIS_PULLDOWN_BIT;
+ rc = qpnp_lcdb_write(lcdb, lcdb->base + LCDB_BST_PD_CTL_REG,
+ &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to set BST PD rc=%d\n", rc);
+ return rc;
+ }
+
+ val = (RDSON_HALF << NFET_SW_SIZE_SHIFT) | RDSON_HALF;
+ rc = qpnp_lcdb_write(lcdb, lcdb->base + LCDB_RDSON_MGMNT_REG,
+ &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to set RDSON MGMT rc=%d\n", rc);
+ return rc;
+ }
+
+ val = AUTO_GM_EN_BIT | EN_TOUCH_WAKE_BIT | DIS_SCP_BIT;
+ rc = qpnp_lcdb_write(lcdb, lcdb->base + LCDB_MISC_CTL_REG,
+ &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to set MISC CTL rc=%d\n", rc);
+ return rc;
+ }
+
+ val = 0;
+ rc = qpnp_lcdb_write(lcdb, lcdb->base + LCDB_SOFT_START_CTL_REG,
+ &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to set LCDB_SOFT_START rc=%d\n", rc);
+ return rc;
+ }
+
+ val = EN_PFM_BIT | (PFM_HYST_25MV << PFM_HYSTERESIS_SHIFT) |
+ (PFM_PEAK_CURRENT_400MA << PFM_CURRENT_SHIFT) |
+ BYP_BST_SOFT_START_COMP_BIT;
+ rc = qpnp_lcdb_write(lcdb, lcdb->base + LCDB_PFM_CTL_REG,
+ &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to set PFM_CTL rc=%d\n", rc);
+ return rc;
+ }
+
+ val = 0;
+ rc = qpnp_lcdb_secure_write(lcdb, lcdb->base + LCDB_PWRUP_PWRDN_CTL_REG,
+ val);
+ if (rc < 0) {
+ pr_err("Failed to set PWRUP_PWRDN_CTL rc=%d\n", rc);
+ return rc;
+ }
+
+ val = LDO_DIS_PULLDOWN_BIT;
+ rc = qpnp_lcdb_write(lcdb, lcdb->base + LCDB_LDO_PD_CTL_REG,
+ &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to set LDO_PD_CTL rc=%d\n", rc);
+ return rc;
+ }
+
+ val = 0;
+ rc = qpnp_lcdb_write(lcdb, lcdb->base + LCDB_LDO_SOFT_START_CTL_REG,
+ &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to set LDO_SOFT_START rc=%d\n", rc);
+ return rc;
+ }
+
+ val = NCP_DIS_PULLDOWN_BIT;
+ rc = qpnp_lcdb_write(lcdb, lcdb->base + LCDB_NCP_PD_CTL_REG,
+ &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to set NCP_PD_CTL rc=%d\n", rc);
+ return rc;
+ }
+
+ val = 0;
+ rc = qpnp_lcdb_write(lcdb, lcdb->base + LCDB_NCP_SOFT_START_CTL_REG,
+ &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to set NCP_SOFT_START rc=%d\n", rc);
+ return rc;
+ }
+
+ if (lcdb->ttw_mode_sw) {
+ rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+ LCDB_AUTO_TOUCH_WAKE_CTL_REG,
+ EN_AUTO_TOUCH_WAKE_BIT,
+ EN_AUTO_TOUCH_WAKE_BIT);
+ if (rc < 0)
+ pr_err("Failed to enable auto(sw) TTW\n rc = %d\n", rc);
+ } else {
+ val = HWEN_RDY_BIT;
+ rc = qpnp_lcdb_write(lcdb, lcdb->base + LCDB_ENABLE_CTL1_REG,
+ &val, 1);
+ if (rc < 0)
+ pr_err("Failed to hw_enable lcdb rc= %d\n", rc);
+ }
+
+ return rc;
+}
+
+static int qpnp_lcdb_ttw_exit(struct qpnp_lcdb *lcdb)
+{
+ int rc;
+
+ if (lcdb->settings_saved) {
+ rc = qpnp_lcdb_restore_settings(lcdb);
+ if (rc < 0) {
+ pr_err("Failed to restore lcdb settings rc=%d\n", rc);
+ return rc;
+ }
+ lcdb->settings_saved = false;
+ }
+
+ return 0;
+}
+
+static int qpnp_lcdb_enable(struct qpnp_lcdb *lcdb)
+{
+ int rc = 0, timeout, delay;
+ u8 val = 0;
+
+ if (lcdb->lcdb_enabled)
+ return 0;
+
+ if (lcdb->ttw_enable) {
+ rc = qpnp_lcdb_ttw_exit(lcdb);
+ if (rc < 0) {
+ pr_err("Failed to exit TTW mode rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ val = MODULE_EN_BIT;
+ rc = qpnp_lcdb_write(lcdb, lcdb->base + LCDB_ENABLE_CTL1_REG,
+ &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to enable lcdb rc= %d\n", rc);
+ goto fail_enable;
+ }
+
+ /* poll for vreg_ok */
+ timeout = 10;
+ delay = lcdb->bst.soft_start_us + lcdb->ldo.soft_start_us +
+ lcdb->ncp.soft_start_us;
+ delay += lcdb->bst.vreg_ok_dbc_us + lcdb->ldo.vreg_ok_dbc_us +
+ lcdb->ncp.vreg_ok_dbc_us;
+ while (timeout--) {
+ rc = qpnp_lcdb_read(lcdb, lcdb->base + INT_RT_STATUS_REG,
+ &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to poll for vreg-ok status rc=%d\n", rc);
+ break;
+ }
+ if (val & VREG_OK_RT_STS_BIT)
+ break;
+
+ usleep_range(delay, delay + 100);
+ }
+
+ if (rc || !timeout) {
+ if (!timeout) {
+ pr_err("lcdb-vreg-ok status failed to change\n");
+ rc = -ETIMEDOUT;
+ }
+ goto fail_enable;
+ }
+
+ lcdb->lcdb_enabled = true;
+ pr_debug("lcdb enabled successfully!\n");
+
+ return 0;
+
+fail_enable:
+ dump_status_registers(lcdb);
+ pr_err("Failed to enable lcdb rc=%d\n", rc);
+ return rc;
+}
+
+static int qpnp_lcdb_disable(struct qpnp_lcdb *lcdb)
+{
+ int rc = 0;
+ u8 val;
+
+ if (!lcdb->lcdb_enabled)
+ return 0;
+
+ if (lcdb->ttw_enable) {
+ rc = qpnp_lcdb_ttw_enter(lcdb);
+ if (rc < 0) {
+ pr_err("Failed to enable TTW mode rc=%d\n", rc);
+ return rc;
+ }
+ lcdb->lcdb_enabled = false;
+
+ return 0;
+ }
+
+ val = 0;
+ rc = qpnp_lcdb_write(lcdb, lcdb->base + LCDB_ENABLE_CTL1_REG,
+ &val, 1);
+ if (rc < 0)
+ pr_err("Failed to disable lcdb rc= %d\n", rc);
+ else
+ lcdb->lcdb_enabled = false;
+
+ return rc;
+}
+
+#define MIN_BST_VOLTAGE_MV 4700
+#define MAX_BST_VOLTAGE_MV 6250
+#define MIN_VOLTAGE_MV 4000
+#define MAX_VOLTAGE_MV 6000
+#define VOLTAGE_MIN_STEP_100_MV 4000
+#define VOLTAGE_MIN_STEP_50_MV 4950
+#define VOLTAGE_STEP_100_MV 100
+#define VOLTAGE_STEP_50_MV 50
+#define VOLTAGE_STEP_50MV_OFFSET 0xA
+static int qpnp_lcdb_set_bst_voltage(struct qpnp_lcdb *lcdb,
+ int voltage_mv)
+{
+ int rc = 0;
+ u8 val = 0;
+
+ if (voltage_mv < MIN_BST_VOLTAGE_MV)
+ voltage_mv = MIN_BST_VOLTAGE_MV;
+ else if (voltage_mv > MAX_BST_VOLTAGE_MV)
+ voltage_mv = MAX_BST_VOLTAGE_MV;
+
+ val = DIV_ROUND_UP(voltage_mv - MIN_BST_VOLTAGE_MV,
+ VOLTAGE_STEP_50_MV);
+
+ rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+ LCDB_BST_OUTPUT_VOLTAGE_REG,
+ SET_OUTPUT_VOLTAGE_MASK, val);
+ if (rc < 0)
+ pr_err("Failed to set boost voltage %d mv rc=%d\n",
+ voltage_mv, rc);
+ else
+ pr_debug("Boost voltage set = %d mv (0x%02x = 0x%02x)\n",
+ voltage_mv, LCDB_BST_OUTPUT_VOLTAGE_REG, val);
+
+ return rc;
+}
+
+static int qpnp_lcdb_get_bst_voltage(struct qpnp_lcdb *lcdb,
+ int *voltage_mv)
+{
+ int rc;
+ u8 val = 0;
+
+ rc = qpnp_lcdb_read(lcdb, lcdb->base + LCDB_BST_OUTPUT_VOLTAGE_REG,
+ &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to reat BST voltage rc=%d\n", rc);
+ return rc;
+ }
+
+ val &= SET_OUTPUT_VOLTAGE_MASK;
+ *voltage_mv = (val * VOLTAGE_STEP_50_MV) + MIN_BST_VOLTAGE_MV;
+
+ return 0;
+}
+
+static int qpnp_lcdb_set_voltage(struct qpnp_lcdb *lcdb,
+ int voltage_mv, u8 type)
+{
+ int rc = 0;
+ u16 offset = LCDB_LDO_OUTPUT_VOLTAGE_REG;
+ u8 val = 0;
+
+ if (type == BST)
+ return qpnp_lcdb_set_bst_voltage(lcdb, voltage_mv);
+
+ if (type == NCP)
+ offset = LCDB_NCP_OUTPUT_VOLTAGE_REG;
+
+ if (!is_between(voltage_mv, MIN_VOLTAGE_MV, MAX_VOLTAGE_MV)) {
+ pr_err("Invalid voltage %dmv (min=%d max=%d)\n",
+ voltage_mv, MIN_VOLTAGE_MV, MAX_VOLTAGE_MV);
+ return -EINVAL;
+ }
+
+ /* Change the BST voltage to LDO + 100mV */
+ if (type == LDO) {
+ rc = qpnp_lcdb_set_bst_voltage(lcdb, voltage_mv + 100);
+ if (rc < 0) {
+ pr_err("Failed to set boost voltage rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ /* Below logic is only valid for LDO and NCP type */
+ if (voltage_mv < VOLTAGE_MIN_STEP_50_MV) {
+ val = DIV_ROUND_UP(voltage_mv - VOLTAGE_MIN_STEP_100_MV,
+ VOLTAGE_STEP_100_MV);
+ } else {
+ val = DIV_ROUND_UP(voltage_mv - VOLTAGE_MIN_STEP_50_MV,
+ VOLTAGE_STEP_50_MV);
+ val += VOLTAGE_STEP_50MV_OFFSET;
+ }
+
+ rc = qpnp_lcdb_masked_write(lcdb, lcdb->base + offset,
+ SET_OUTPUT_VOLTAGE_MASK, val);
+ if (rc < 0)
+ pr_err("Failed to set output voltage %d mv for %s rc=%d\n",
+ voltage_mv, (type == LDO) ? "LDO" : "NCP", rc);
+ else
+ pr_debug("%s voltage set = %d mv (0x%02x = 0x%02x)\n",
+ (type == LDO) ? "LDO" : "NCP", voltage_mv, offset, val);
+
+ return rc;
+}
+
+static int qpnp_lcdb_get_voltage(struct qpnp_lcdb *lcdb,
+ u32 *voltage_mv, u8 type)
+{
+ int rc = 0;
+ u16 offset = LCDB_LDO_OUTPUT_VOLTAGE_REG;
+ u8 val = 0;
+
+ if (type == BST)
+ return qpnp_lcdb_get_bst_voltage(lcdb, voltage_mv);
+
+ if (type == NCP)
+ offset = LCDB_NCP_OUTPUT_VOLTAGE_REG;
+
+ rc = qpnp_lcdb_read(lcdb, lcdb->base + offset, &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to read %s volatge rc=%d\n",
+ (type == LDO) ? "LDO" : "NCP", rc);
+ return rc;
+ }
+
+ if (val < VOLTAGE_STEP_50MV_OFFSET) {
+ *voltage_mv = VOLTAGE_MIN_STEP_100_MV +
+ (val * VOLTAGE_STEP_100_MV);
+ } else {
+ *voltage_mv = VOLTAGE_MIN_STEP_50_MV +
+ ((val - VOLTAGE_STEP_50MV_OFFSET) * VOLTAGE_STEP_50_MV);
+ }
+
+ if (!rc)
+ pr_debug("%s voltage read-back = %d mv (0x%02x = 0x%02x)\n",
+ (type == LDO) ? "LDO" : "NCP",
+ *voltage_mv, offset, val);
+
+ return rc;
+}
+
+static int qpnp_lcdb_set_soft_start(struct qpnp_lcdb *lcdb,
+ u32 ss_us, u8 type)
+{
+ int rc = 0, i = 0;
+ u16 offset = LCDB_LDO_SOFT_START_CTL_REG;
+ u8 val = 0;
+
+ if (type == NCP)
+ offset = LCDB_NCP_SOFT_START_CTL_REG;
+
+ if (!is_between(ss_us, MIN_SOFT_START_US, MAX_SOFT_START_US)) {
+ pr_err("Invalid soft_start_us %d (min=%d max=%d)\n",
+ ss_us, MIN_SOFT_START_US, MAX_SOFT_START_US);
+ return -EINVAL;
+ }
+
+ i = 0;
+ while (ss_us > soft_start_us[i])
+ i++;
+ val = ((i == 0) ? 0 : i - 1) & SOFT_START_MASK;
+
+ rc = qpnp_lcdb_masked_write(lcdb,
+ lcdb->base + offset, SOFT_START_MASK, val);
+ if (rc < 0)
+ pr_err("Failed to write %s soft-start time %d rc=%d",
+ (type == LDO) ? "LDO" : "NCP", soft_start_us[i], rc);
+
+ return rc;
+}
+
+static int qpnp_lcdb_ldo_regulator_enable(struct regulator_dev *rdev)
+{
+ int rc = 0;
+ struct qpnp_lcdb *lcdb = rdev_get_drvdata(rdev);
+
+ mutex_lock(&lcdb->lcdb_mutex);
+ rc = qpnp_lcdb_enable(lcdb);
+ if (rc < 0)
+ pr_err("Failed to enable lcdb rc=%d\n", rc);
+ mutex_unlock(&lcdb->lcdb_mutex);
+
+ return rc;
+}
+
+static int qpnp_lcdb_ldo_regulator_disable(struct regulator_dev *rdev)
+{
+ int rc = 0;
+ struct qpnp_lcdb *lcdb = rdev_get_drvdata(rdev);
+
+ mutex_lock(&lcdb->lcdb_mutex);
+ rc = qpnp_lcdb_disable(lcdb);
+ if (rc < 0)
+ pr_err("Failed to disable lcdb rc=%d\n", rc);
+ mutex_unlock(&lcdb->lcdb_mutex);
+
+ return rc;
+}
+
+static int qpnp_lcdb_ldo_regulator_is_enabled(struct regulator_dev *rdev)
+{
+ struct qpnp_lcdb *lcdb = rdev_get_drvdata(rdev);
+
+ return lcdb->lcdb_enabled;
+}
+
+static int qpnp_lcdb_ldo_regulator_set_voltage(struct regulator_dev *rdev,
+ int min_uV, int max_uV, unsigned int *selector)
+{
+ int rc = 0;
+ struct qpnp_lcdb *lcdb = rdev_get_drvdata(rdev);
+
+ rc = qpnp_lcdb_set_voltage(lcdb, min_uV / 1000, LDO);
+ if (rc < 0)
+ pr_err("Failed to set LDO voltage rc=%c\n", rc);
+
+ return rc;
+}
+
+static int qpnp_lcdb_ldo_regulator_get_voltage(struct regulator_dev *rdev)
+{
+ int rc = 0;
+ u32 voltage_mv = 0;
+ struct qpnp_lcdb *lcdb = rdev_get_drvdata(rdev);
+
+ rc = qpnp_lcdb_get_voltage(lcdb, &voltage_mv, LDO);
+ if (rc < 0) {
+ pr_err("Failed to get ldo voltage rc=%d\n", rc);
+ return rc;
+ }
+
+ return voltage_mv * 1000;
+}
+
+static struct regulator_ops qpnp_lcdb_ldo_ops = {
+ .enable = qpnp_lcdb_ldo_regulator_enable,
+ .disable = qpnp_lcdb_ldo_regulator_disable,
+ .is_enabled = qpnp_lcdb_ldo_regulator_is_enabled,
+ .set_voltage = qpnp_lcdb_ldo_regulator_set_voltage,
+ .get_voltage = qpnp_lcdb_ldo_regulator_get_voltage,
+};
+
+static int qpnp_lcdb_ncp_regulator_enable(struct regulator_dev *rdev)
+{
+ int rc = 0;
+ struct qpnp_lcdb *lcdb = rdev_get_drvdata(rdev);
+
+ mutex_lock(&lcdb->lcdb_mutex);
+ rc = qpnp_lcdb_enable(lcdb);
+ if (rc < 0)
+ pr_err("Failed to enable lcdb rc=%d\n", rc);
+ mutex_unlock(&lcdb->lcdb_mutex);
+
+ return rc;
+}
+
+static int qpnp_lcdb_ncp_regulator_disable(struct regulator_dev *rdev)
+{
+ int rc = 0;
+ struct qpnp_lcdb *lcdb = rdev_get_drvdata(rdev);
+
+ mutex_lock(&lcdb->lcdb_mutex);
+ rc = qpnp_lcdb_disable(lcdb);
+ if (rc < 0)
+ pr_err("Failed to disable lcdb rc=%d\n", rc);
+ mutex_unlock(&lcdb->lcdb_mutex);
+
+ return rc;
+}
+
+static int qpnp_lcdb_ncp_regulator_is_enabled(struct regulator_dev *rdev)
+{
+ struct qpnp_lcdb *lcdb = rdev_get_drvdata(rdev);
+
+ return lcdb->lcdb_enabled;
+}
+
+static int qpnp_lcdb_ncp_regulator_set_voltage(struct regulator_dev *rdev,
+ int min_uV, int max_uV, unsigned int *selector)
+{
+ int rc = 0;
+ struct qpnp_lcdb *lcdb = rdev_get_drvdata(rdev);
+
+ rc = qpnp_lcdb_set_voltage(lcdb, min_uV / 1000, NCP);
+ if (rc < 0)
+ pr_err("Failed to set LDO voltage rc=%c\n", rc);
+
+ return rc;
+}
+
+static int qpnp_lcdb_ncp_regulator_get_voltage(struct regulator_dev *rdev)
+{
+ int rc;
+ u32 voltage_mv = 0;
+ struct qpnp_lcdb *lcdb = rdev_get_drvdata(rdev);
+
+ rc = qpnp_lcdb_get_voltage(lcdb, &voltage_mv, NCP);
+ if (rc < 0) {
+ pr_err("Failed to get ncp voltage rc=%d\n", rc);
+ return rc;
+ }
+
+ return voltage_mv * 1000;
+}
+
+static struct regulator_ops qpnp_lcdb_ncp_ops = {
+ .enable = qpnp_lcdb_ncp_regulator_enable,
+ .disable = qpnp_lcdb_ncp_regulator_disable,
+ .is_enabled = qpnp_lcdb_ncp_regulator_is_enabled,
+ .set_voltage = qpnp_lcdb_ncp_regulator_set_voltage,
+ .get_voltage = qpnp_lcdb_ncp_regulator_get_voltage,
+};
+
+static int qpnp_lcdb_regulator_register(struct qpnp_lcdb *lcdb, u8 type)
+{
+ int rc = 0;
+ struct regulator_init_data *init_data;
+ struct regulator_config cfg = {};
+ struct regulator_desc *rdesc;
+ struct regulator_dev *rdev;
+ struct device_node *node;
+
+ if (type == LDO) {
+ node = lcdb->ldo.node;
+ rdesc = &lcdb->ldo.rdesc;
+ rdesc->ops = &qpnp_lcdb_ldo_ops;
+ rdev = lcdb->ldo.rdev;
+ } else if (type == NCP) {
+ node = lcdb->ncp.node;
+ rdesc = &lcdb->ncp.rdesc;
+ rdesc->ops = &qpnp_lcdb_ncp_ops;
+ rdev = lcdb->ncp.rdev;
+ } else {
+ pr_err("Invalid regulator type %d\n", type);
+ return -EINVAL;
+ }
+
+ init_data = of_get_regulator_init_data(lcdb->dev, node, rdesc);
+ if (!init_data) {
+ pr_err("Failed to get regulator_init_data for %s\n",
+ (type == LDO) ? "LDO" : "NCP");
+ return -ENOMEM;
+ }
+
+ if (init_data->constraints.name) {
+ rdesc->owner = THIS_MODULE;
+ rdesc->type = REGULATOR_VOLTAGE;
+ rdesc->name = init_data->constraints.name;
+
+ cfg.dev = lcdb->dev;
+ cfg.init_data = init_data;
+ cfg.driver_data = lcdb;
+ cfg.of_node = node;
+
+ if (of_get_property(lcdb->dev->of_node, "parent-supply", NULL))
+ init_data->supply_regulator = "parent";
+
+ init_data->constraints.valid_ops_mask
+ |= REGULATOR_CHANGE_VOLTAGE
+ | REGULATOR_CHANGE_STATUS;
+
+ rdev = devm_regulator_register(lcdb->dev, rdesc, &cfg);
+ if (IS_ERR(rdev)) {
+ rc = PTR_ERR(rdev);
+ rdev = NULL;
+ pr_err("Failed to register lcdb_%s regulator rc = %d\n",
+ (type == LDO) ? "LDO" : "NCP", rc);
+ return rc;
+ }
+ } else {
+ pr_err("%s_regulator name missing\n",
+ (type == LDO) ? "LDO" : "NCP");
+ return -EINVAL;
+ }
+
+ return rc;
+}
+
+static int qpnp_lcdb_parse_ttw(struct qpnp_lcdb *lcdb)
+{
+ int rc = 0;
+ u32 temp;
+ u8 val = 0;
+ struct device_node *node = lcdb->dev->of_node;
+
+ if (of_property_read_bool(node, "qcom,ttw-mode-sw")) {
+ lcdb->ttw_mode_sw = true;
+ rc = of_property_read_u32(node, "qcom,attw-toff-ms", &temp);
+ if (!rc) {
+ if (!is_between(temp, ATTW_MIN_MS, ATTW_MAX_MS)) {
+ pr_err("Invalid TOFF val %d (min=%d max=%d)\n",
+ temp, ATTW_MIN_MS, ATTW_MAX_MS);
+ return -EINVAL;
+ }
+ val = ilog2(temp / 4) << ATTW_TOFF_TIME_SHIFT;
+ } else {
+ pr_err("qcom,attw-toff-ms not specified for TTW SW mode\n");
+ return rc;
+ }
+
+ rc = of_property_read_u32(node, "qcom,attw-ton-ms", &temp);
+ if (!rc) {
+ if (!is_between(temp, ATTW_MIN_MS, ATTW_MAX_MS)) {
+ pr_err("Invalid TON value %d (min=%d max=%d)\n",
+ temp, ATTW_MIN_MS, ATTW_MAX_MS);
+ return -EINVAL;
+ }
+ val |= ilog2(temp / 4);
+ } else {
+ pr_err("qcom,attw-ton-ms not specified for TTW SW mode\n");
+ return rc;
+ }
+ rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+ LCDB_AUTO_TOUCH_WAKE_CTL_REG,
+ ATTW_TON_TIME_MASK | ATTW_TOFF_TIME_MASK, val);
+ if (rc < 0) {
+ pr_err("Failed to write ATTW ON/OFF rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+static int qpnp_lcdb_ldo_dt_init(struct qpnp_lcdb *lcdb)
+{
+ int rc = 0;
+ struct device_node *node = lcdb->ldo.node;
+
+ /* LDO output voltage */
+ lcdb->ldo.voltage_mv = -EINVAL;
+ rc = of_property_read_u32(node, "qcom,ldo-voltage-mv",
+ &lcdb->ldo.voltage_mv);
+ if (!rc && !is_between(lcdb->ldo.voltage_mv, MIN_VOLTAGE_MV,
+ MAX_VOLTAGE_MV)) {
+ pr_err("Invalid LDO voltage %dmv (min=%d max=%d)\n",
+ lcdb->ldo.voltage_mv, MIN_VOLTAGE_MV, MAX_VOLTAGE_MV);
+ return -EINVAL;
+ }
+
+ /* LDO PD configuration */
+ lcdb->ldo.pd = -EINVAL;
+ of_property_read_u32(node, "qcom,ldo-pd", &lcdb->ldo.pd);
+
+ lcdb->ldo.pd_strength = -EINVAL;
+ of_property_read_u32(node, "qcom,ldo-pd-strength",
+ &lcdb->ldo.pd_strength);
+
+ /* LDO ILIM configuration */
+ lcdb->ldo.ilim_ma = -EINVAL;
+ rc = of_property_read_u32(node, "qcom,ldo-ilim-ma", &lcdb->ldo.ilim_ma);
+ if (!rc && !is_between(lcdb->ldo.ilim_ma, MIN_LDO_ILIM_MA,
+ MAX_LDO_ILIM_MA)) {
+ pr_err("Invalid ilim_ma %d (min=%d, max=%d)\n",
+ lcdb->ldo.ilim_ma, MIN_LDO_ILIM_MA,
+ MAX_LDO_ILIM_MA);
+ return -EINVAL;
+ }
+
+ /* LDO soft-start (SS) configuration */
+ lcdb->ldo.soft_start_us = -EINVAL;
+ of_property_read_u32(node, "qcom,ldo-soft-start-us",
+ &lcdb->ldo.soft_start_us);
+
+ return 0;
+}
+
+static int qpnp_lcdb_ncp_dt_init(struct qpnp_lcdb *lcdb)
+{
+ int rc = 0;
+ struct device_node *node = lcdb->ncp.node;
+
+ /* NCP output voltage */
+ lcdb->ncp.voltage_mv = -EINVAL;
+ rc = of_property_read_u32(node, "qcom,ncp-voltage-mv",
+ &lcdb->ncp.voltage_mv);
+ if (!rc && !is_between(lcdb->ncp.voltage_mv, MIN_VOLTAGE_MV,
+ MAX_VOLTAGE_MV)) {
+ pr_err("Invalid NCP voltage %dmv (min=%d max=%d)\n",
+ lcdb->ldo.voltage_mv, MIN_VOLTAGE_MV, MAX_VOLTAGE_MV);
+ return -EINVAL;
+ }
+
+ /* NCP PD configuration */
+ lcdb->ncp.pd = -EINVAL;
+ of_property_read_u32(node, "qcom,ncp-pd", &lcdb->ncp.pd);
+
+ lcdb->ncp.pd_strength = -EINVAL;
+ of_property_read_u32(node, "qcom,ncp-pd-strength",
+ &lcdb->ncp.pd_strength);
+
+ /* NCP ILIM configuration */
+ lcdb->ncp.ilim_ma = -EINVAL;
+ rc = of_property_read_u32(node, "qcom,ncp-ilim-ma", &lcdb->ncp.ilim_ma);
+ if (!rc && !is_between(lcdb->ncp.ilim_ma, MIN_NCP_ILIM_MA,
+ MAX_NCP_ILIM_MA)) {
+ pr_err("Invalid ilim_ma %d (min=%d, max=%d)\n",
+ lcdb->ncp.ilim_ma, MIN_NCP_ILIM_MA, MAX_NCP_ILIM_MA);
+ return -EINVAL;
+ }
+
+ /* NCP soft-start (SS) configuration */
+ lcdb->ncp.soft_start_us = -EINVAL;
+ of_property_read_u32(node, "qcom,ncp-soft-start-us",
+ &lcdb->ncp.soft_start_us);
+
+ return 0;
+}
+
+static int qpnp_lcdb_bst_dt_init(struct qpnp_lcdb *lcdb)
+{
+ int rc = 0;
+ struct device_node *node = lcdb->bst.node;
+
+ /* Boost PD configuration */
+ lcdb->bst.pd = -EINVAL;
+ of_property_read_u32(node, "qcom,bst-pd", &lcdb->bst.pd);
+
+ lcdb->bst.pd_strength = -EINVAL;
+ of_property_read_u32(node, "qcom,bst-pd-strength",
+ &lcdb->bst.pd_strength);
+
+ /* Boost ILIM */
+ lcdb->bst.ilim_ma = -EINVAL;
+ rc = of_property_read_u32(node, "qcom,bst-ilim-ma", &lcdb->bst.ilim_ma);
+ if (!rc && !is_between(lcdb->bst.ilim_ma, MIN_BST_ILIM_MA,
+ MAX_BST_ILIM_MA)) {
+ pr_err("Invalid ilim_ma %d (min=%d, max=%d)\n",
+ lcdb->bst.ilim_ma, MIN_BST_ILIM_MA, MAX_BST_ILIM_MA);
+ return -EINVAL;
+ }
+
+ /* Boost PS configuration */
+ lcdb->bst.ps = -EINVAL;
+ of_property_read_u32(node, "qcom,bst-ps", &lcdb->bst.ps);
+
+ lcdb->bst.ps_threshold = -EINVAL;
+ rc = of_property_read_u32(node, "qcom,bst-ps-threshold-ma",
+ &lcdb->bst.ps_threshold);
+ if (!rc && !is_between(lcdb->bst.ps_threshold,
+ MIN_BST_PS_MA, MAX_BST_PS_MA)) {
+ pr_err("Invalid bst ps_threshold %d (min=%d, max=%d)\n",
+ lcdb->bst.ps_threshold, MIN_BST_PS_MA, MAX_BST_PS_MA);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int qpnp_lcdb_init_ldo(struct qpnp_lcdb *lcdb)
+{
+ int rc = 0, ilim_ma;
+ u8 val = 0;
+
+ /* configure parameters only if LCDB is disabled */
+ if (!is_lcdb_enabled(lcdb)) {
+ if (lcdb->ldo.voltage_mv != -EINVAL) {
+ rc = qpnp_lcdb_set_voltage(lcdb,
+ lcdb->ldo.voltage_mv, LDO);
+ if (rc < 0) {
+ pr_err("Failed to set voltage rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ if (lcdb->ldo.pd != -EINVAL) {
+ rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+ LCDB_LDO_PD_CTL_REG, LDO_DIS_PULLDOWN_BIT,
+ lcdb->ldo.pd ? 0 : LDO_DIS_PULLDOWN_BIT);
+ if (rc < 0) {
+ pr_err("Failed to configure LDO PD rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ if (lcdb->ldo.pd_strength != -EINVAL) {
+ rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+ LCDB_LDO_PD_CTL_REG, LDO_PD_STRENGTH_BIT,
+ lcdb->ldo.pd_strength ?
+ LDO_PD_STRENGTH_BIT : 0);
+ if (rc < 0) {
+ pr_err("Failed to configure LDO PD strength %s rc=%d",
+ lcdb->ldo.pd_strength ?
+ "(strong)" : "(weak)", rc);
+ return rc;
+ }
+ }
+
+ if (lcdb->ldo.ilim_ma != -EINVAL) {
+ ilim_ma = lcdb->ldo.ilim_ma - MIN_LDO_ILIM_MA;
+ ilim_ma /= LDO_ILIM_STEP_MA;
+ val = (ilim_ma & SET_LDO_ILIM_MASK) | EN_LDO_ILIM_BIT;
+ rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+ LCDB_LDO_ILIM_CTL1_REG,
+ SET_LDO_ILIM_MASK | EN_LDO_ILIM_BIT,
+ val);
+ if (rc < 0) {
+ pr_err("Failed to configure LDO ilim_ma (CTL1=%d) rc=%d",
+ val, rc);
+ return rc;
+ }
+
+ val = ilim_ma & SET_LDO_ILIM_MASK;
+ rc = qpnp_lcdb_masked_write(lcdb,
+ lcdb->base + LCDB_LDO_ILIM_CTL2_REG,
+ SET_LDO_ILIM_MASK, val);
+ if (rc < 0) {
+ pr_err("Failed to configure LDO ilim_ma (CTL2=%d) rc=%d",
+ val, rc);
+ return rc;
+ }
+ }
+
+ if (lcdb->ldo.soft_start_us != -EINVAL) {
+ rc = qpnp_lcdb_set_soft_start(lcdb,
+ lcdb->ldo.soft_start_us, LDO);
+ if (rc < 0) {
+ pr_err("Failed to set LDO soft_start rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+ }
+
+ rc = qpnp_lcdb_get_voltage(lcdb, &lcdb->ldo.voltage_mv, LDO);
+ if (rc < 0) {
+ pr_err("Failed to get LDO volatge rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = qpnp_lcdb_read(lcdb, lcdb->base +
+ LCDB_LDO_VREG_OK_CTL_REG, &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to read ldo_vreg_ok rc=%d\n", rc);
+ return rc;
+ }
+ lcdb->ldo.vreg_ok_dbc_us = dbc_us[val & VREG_OK_DEB_MASK];
+
+ rc = qpnp_lcdb_read(lcdb, lcdb->base +
+ LCDB_LDO_SOFT_START_CTL_REG, &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to read ldo_soft_start_ctl rc=%d\n", rc);
+ return rc;
+ }
+ lcdb->ldo.soft_start_us = soft_start_us[val & SOFT_START_MASK];
+
+ rc = qpnp_lcdb_regulator_register(lcdb, LDO);
+ if (rc < 0)
+ pr_err("Failed to register ldo rc=%d\n", rc);
+
+ return rc;
+}
+
+static int qpnp_lcdb_init_ncp(struct qpnp_lcdb *lcdb)
+{
+ int rc = 0, i = 0;
+ u8 val = 0;
+
+ /* configure parameters only if LCDB is disabled */
+ if (!is_lcdb_enabled(lcdb)) {
+ if (lcdb->ncp.voltage_mv != -EINVAL) {
+ rc = qpnp_lcdb_set_voltage(lcdb,
+ lcdb->ncp.voltage_mv, NCP);
+ if (rc < 0) {
+ pr_err("Failed to set voltage rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ if (lcdb->ncp.pd != -EINVAL) {
+ rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+ LCDB_NCP_PD_CTL_REG, NCP_DIS_PULLDOWN_BIT,
+ lcdb->ncp.pd ? 0 : NCP_DIS_PULLDOWN_BIT);
+ if (rc < 0) {
+ pr_err("Failed to configure NCP PD rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ if (lcdb->ncp.pd_strength != -EINVAL) {
+ rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+ LCDB_NCP_PD_CTL_REG, NCP_PD_STRENGTH_BIT,
+ lcdb->ncp.pd_strength ?
+ NCP_PD_STRENGTH_BIT : 0);
+ if (rc < 0) {
+ pr_err("Failed to configure NCP PD strength %s rc=%d",
+ lcdb->ncp.pd_strength ?
+ "(strong)" : "(weak)", rc);
+ return rc;
+ }
+ }
+
+ if (lcdb->ncp.ilim_ma != -EINVAL) {
+ while (lcdb->ncp.ilim_ma > ncp_ilim_ma[i])
+ i++;
+ val = (i == 0) ? 0 : i - 1;
+ val = (lcdb->ncp.ilim_ma & SET_NCP_ILIM_MASK) |
+ EN_NCP_ILIM_BIT;
+ rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+ LCDB_NCP_ILIM_CTL1_REG,
+ SET_NCP_ILIM_MASK | EN_NCP_ILIM_BIT, val);
+ if (rc < 0) {
+ pr_err("Failed to configure NCP ilim_ma (CTL1=%d) rc=%d",
+ val, rc);
+ return rc;
+ }
+ val = lcdb->ncp.ilim_ma & SET_NCP_ILIM_MASK;
+ rc = qpnp_lcdb_masked_write(lcdb,
+ lcdb->base + LCDB_NCP_ILIM_CTL2_REG,
+ SET_NCP_ILIM_MASK, val);
+ if (rc < 0) {
+ pr_err("Failed to configure NCP ilim_ma (CTL2=%d) rc=%d",
+ val, rc);
+ return rc;
+ }
+ }
+
+ if (lcdb->ncp.soft_start_us != -EINVAL) {
+ rc = qpnp_lcdb_set_soft_start(lcdb,
+ lcdb->ncp.soft_start_us, NCP);
+ if (rc < 0) {
+ pr_err("Failed to set NCP soft_start rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+ }
+
+ rc = qpnp_lcdb_get_voltage(lcdb, &lcdb->ncp.voltage_mv, NCP);
+ if (rc < 0) {
+ pr_err("Failed to get NCP volatge rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = qpnp_lcdb_read(lcdb, lcdb->base +
+ LCDB_NCP_VREG_OK_CTL_REG, &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to read ncp_vreg_ok rc=%d\n", rc);
+ return rc;
+ }
+ lcdb->ncp.vreg_ok_dbc_us = dbc_us[val & VREG_OK_DEB_MASK];
+
+ rc = qpnp_lcdb_read(lcdb, lcdb->base +
+ LCDB_NCP_SOFT_START_CTL_REG, &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to read ncp_soft_start_ctl rc=%d\n", rc);
+ return rc;
+ }
+ lcdb->ncp.soft_start_us = soft_start_us[val & SOFT_START_MASK];
+
+ rc = qpnp_lcdb_regulator_register(lcdb, NCP);
+ if (rc < 0)
+ pr_err("Failed to register NCP rc=%d\n", rc);
+
+ return rc;
+}
+
+static int qpnp_lcdb_init_bst(struct qpnp_lcdb *lcdb)
+{
+ int rc = 0;
+ u8 val = 0;
+
+ /* configure parameters only if LCDB is disabled */
+ if (!is_lcdb_enabled(lcdb)) {
+ if (lcdb->bst.pd != -EINVAL) {
+ rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+ LCDB_BST_PD_CTL_REG, BOOST_DIS_PULLDOWN_BIT,
+ lcdb->bst.pd ? 0 : BOOST_DIS_PULLDOWN_BIT);
+ if (rc < 0) {
+ pr_err("Failed to configure BST PD rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ if (lcdb->bst.pd_strength != -EINVAL) {
+ rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+ LCDB_NCP_PD_CTL_REG, BOOST_PD_STRENGTH_BIT,
+ lcdb->bst.pd_strength ?
+ BOOST_PD_STRENGTH_BIT : 0);
+ if (rc < 0) {
+ pr_err("Failed to configure NCP PD strength %s rc=%d",
+ lcdb->bst.pd_strength ?
+ "(strong)" : "(weak)", rc);
+ return rc;
+ }
+ }
+
+ if (lcdb->bst.ilim_ma != -EINVAL) {
+ val = (lcdb->bst.ilim_ma / MIN_BST_ILIM_MA) - 1;
+ val = (lcdb->bst.ilim_ma & SET_BST_ILIM_MASK) |
+ EN_BST_ILIM_BIT;
+ rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+ LCDB_BST_ILIM_CTL_REG,
+ SET_BST_ILIM_MASK | EN_BST_ILIM_BIT, val);
+ if (rc < 0) {
+ pr_err("Failed to configure BST ilim_ma rc=%d",
+ rc);
+ return rc;
+ }
+ }
+
+ if (lcdb->bst.ps != -EINVAL) {
+ rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+ LCDB_PS_CTL_REG, EN_PS_BIT,
+ &lcdb->bst.ps ? EN_PS_BIT : 0);
+ if (rc < 0) {
+ pr_err("Failed to disable BST PS rc=%d", rc);
+ return rc;
+ }
+ }
+
+ if (lcdb->bst.ps_threshold != -EINVAL) {
+ val = (lcdb->bst.ps_threshold - MIN_BST_PS_MA) / 10;
+ val = (lcdb->bst.ps_threshold & PS_THRESHOLD_MASK) |
+ EN_PS_BIT;
+ rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+ LCDB_PS_CTL_REG,
+ PS_THRESHOLD_MASK | EN_PS_BIT,
+ val);
+ if (rc < 0) {
+ pr_err("Failed to configure BST PS threshold rc=%d",
+ rc);
+ return rc;
+ }
+ }
+ }
+
+ rc = qpnp_lcdb_get_voltage(lcdb, &lcdb->bst.voltage_mv, BST);
+ if (rc < 0) {
+ pr_err("Failed to get BST volatge rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = qpnp_lcdb_read(lcdb, lcdb->base +
+ LCDB_BST_VREG_OK_CTL_REG, &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to read bst_vreg_ok rc=%d\n", rc);
+ return rc;
+ }
+ lcdb->bst.vreg_ok_dbc_us = dbc_us[val & VREG_OK_DEB_MASK];
+
+ rc = qpnp_lcdb_read(lcdb, lcdb->base +
+ LCDB_SOFT_START_CTL_REG, &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to read ncp_soft_start_ctl rc=%d\n", rc);
+ return rc;
+ }
+ lcdb->bst.soft_start_us = (val & SOFT_START_MASK) * 200 + 200;
+
+ return 0;
+}
+
+static int qpnp_lcdb_hw_init(struct qpnp_lcdb *lcdb)
+{
+ int rc = 0;
+ u8 val = 0;
+
+ rc = qpnp_lcdb_init_bst(lcdb);
+ if (rc < 0) {
+ pr_err("Failed to initialize BOOST rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = qpnp_lcdb_init_ldo(lcdb);
+ if (rc < 0) {
+ pr_err("Failed to initialize LDO rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = qpnp_lcdb_init_ncp(lcdb);
+ if (rc < 0) {
+ pr_err("Failed to initialize NCP rc=%d\n", rc);
+ return rc;
+ }
+
+ if (!is_lcdb_enabled(lcdb)) {
+ rc = qpnp_lcdb_read(lcdb, lcdb->base +
+ LCDB_MODULE_RDY_REG, &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to read MODULE_RDY rc=%d\n", rc);
+ return rc;
+ }
+ if (!(val & MODULE_RDY_BIT)) {
+ rc = qpnp_lcdb_masked_write(lcdb, lcdb->base +
+ LCDB_MODULE_RDY_REG, MODULE_RDY_BIT,
+ MODULE_RDY_BIT);
+ if (rc < 0) {
+ pr_err("Failed to set MODULE RDY rc=%d\n", rc);
+ return rc;
+ }
+ }
+ } else {
+ /* module already enabled */
+ lcdb->lcdb_enabled = true;
+ }
+
+ return 0;
+}
+
+static int qpnp_lcdb_parse_dt(struct qpnp_lcdb *lcdb)
+{
+ int rc = 0;
+ const char *label;
+ struct device_node *temp, *node = lcdb->dev->of_node;
+
+ for_each_available_child_of_node(node, temp) {
+ rc = of_property_read_string(temp, "label", &label);
+ if (rc < 0) {
+ pr_err("Failed to read label rc=%d\n", rc);
+ return rc;
+ }
+
+ if (!strcmp(label, "ldo")) {
+ lcdb->ldo.node = temp;
+ rc = qpnp_lcdb_ldo_dt_init(lcdb);
+ } else if (!strcmp(label, "ncp")) {
+ lcdb->ncp.node = temp;
+ rc = qpnp_lcdb_ncp_dt_init(lcdb);
+ } else if (!strcmp(label, "bst")) {
+ lcdb->bst.node = temp;
+ rc = qpnp_lcdb_bst_dt_init(lcdb);
+ } else {
+ pr_err("Failed to identify label %s\n", label);
+ return -EINVAL;
+ }
+ if (rc < 0) {
+ pr_err("Failed to register %s module\n", label);
+ return rc;
+ }
+ }
+
+ if (of_property_read_bool(node, "qcom,ttw-enable")) {
+ rc = qpnp_lcdb_parse_ttw(lcdb);
+ if (rc < 0) {
+ pr_err("Failed to parse ttw-params rc=%d\n", rc);
+ return rc;
+ }
+ lcdb->ttw_enable = true;
+ }
+
+ return rc;
+}
+
+static int qpnp_lcdb_regulator_probe(struct platform_device *pdev)
+{
+ int rc;
+ struct device_node *node;
+ struct qpnp_lcdb *lcdb;
+
+ node = pdev->dev.of_node;
+ if (!node) {
+ pr_err("No nodes defined\n");
+ return -ENODEV;
+ }
+
+ lcdb = devm_kzalloc(&pdev->dev, sizeof(*lcdb), GFP_KERNEL);
+ if (!lcdb)
+ return -ENOMEM;
+
+ rc = of_property_read_u32(node, "reg", &lcdb->base);
+ if (rc < 0) {
+ pr_err("Failed to find reg node rc=%d\n", rc);
+ return rc;
+ }
+
+ lcdb->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!lcdb->regmap) {
+ pr_err("Failed to get the regmap handle rc=%d\n", rc);
+ return -EINVAL;
+ }
+
+ lcdb->dev = &pdev->dev;
+ lcdb->pdev = pdev;
+ mutex_init(&lcdb->lcdb_mutex);
+ mutex_init(&lcdb->read_write_mutex);
+
+ rc = qpnp_lcdb_parse_dt(lcdb);
+ if (rc < 0) {
+ pr_err("Failed to parse dt rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = qpnp_lcdb_hw_init(lcdb);
+ if (rc < 0)
+ pr_err("Failed to initialize LCDB module rc=%d\n", rc);
+ else
+ pr_info("LCDB module successfully registered! lcdb_en=%d ldo_voltage=%dmV ncp_voltage=%dmV bst_voltage=%dmV\n",
+ lcdb->lcdb_enabled, lcdb->ldo.voltage_mv,
+ lcdb->ncp.voltage_mv, lcdb->bst.voltage_mv);
+
+ return rc;
+}
+
+static int qpnp_lcdb_regulator_remove(struct platform_device *pdev)
+{
+ struct qpnp_lcdb *lcdb = dev_get_drvdata(&pdev->dev);
+
+ mutex_destroy(&lcdb->lcdb_mutex);
+ mutex_destroy(&lcdb->read_write_mutex);
+
+ return 0;
+}
+
+static const struct of_device_id lcdb_match_table[] = {
+ { .compatible = QPNP_LCDB_REGULATOR_DRIVER_NAME, },
+ { },
+};
+
+static struct platform_driver qpnp_lcdb_regulator_driver = {
+ .driver = {
+ .name = QPNP_LCDB_REGULATOR_DRIVER_NAME,
+ .of_match_table = lcdb_match_table,
+ },
+ .probe = qpnp_lcdb_regulator_probe,
+ .remove = qpnp_lcdb_regulator_remove,
+};
+
+static int __init qpnp_lcdb_regulator_init(void)
+{
+ return platform_driver_register(&qpnp_lcdb_regulator_driver);
+}
+arch_initcall(qpnp_lcdb_regulator_init);
+
+static void __exit qpnp_lcdb_regulator_exit(void)
+{
+ platform_driver_unregister(&qpnp_lcdb_regulator_driver);
+}
+module_exit(qpnp_lcdb_regulator_exit);
+
+MODULE_DESCRIPTION("QPNP LCDB regulator driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/regulator/qpnp-oledb-regulator.c b/drivers/regulator/qpnp-oledb-regulator.c
new file mode 100644
index 0000000..8d017fb
--- /dev/null
+++ b/drivers/regulator/qpnp-oledb-regulator.c
@@ -0,0 +1,1201 @@
+/* Copyright (c) 2016-2017, The Linux Foundation. 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.
+ */
+
+#define pr_fmt(fmt) "OLEDB: %s: " fmt, __func__
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+
+#define QPNP_OLEDB_REGULATOR_DRIVER_NAME "qcom,qpnp-oledb-regulator"
+#define OLEDB_VOUT_STEP_MV 100
+#define OLEDB_VOUT_MIN_MV 5000
+#define OLEDB_VOUT_MAX_MV 8100
+#define OLEDB_VOUT_HW_DEFAULT 6400
+
+#define OLEDB_MODULE_RDY 0x45
+#define OLEDB_MODULE_RDY_BIT BIT(7)
+
+#define OLEDB_MODULE_ENABLE 0x46
+#define OLEDB_MODULE_ENABLE_BIT BIT(7)
+
+#define OLEDB_EXT_PIN_CTL 0x47
+#define OLEDB_EXT_PIN_CTL_BIT BIT(7)
+
+#define OLEDB_SWIRE_CONTROL 0x48
+#define OLEDB_EN_SWIRE_VOUT_UPD_BIT BIT(6)
+#define OLEDB_EN_SWIRE_PD_UPD_BIT BIT(7)
+
+#define OLEDB_VOUT_PGM 0x49
+#define OLEDB_VOUT_PGM_MASK GENMASK(4, 0)
+
+#define OLEDB_VOUT_DEFAULT 0x4A
+#define OLEDB_VOUT_DEFAULT_MASK GENMASK(4, 0)
+
+#define OLEDB_PD_CTL 0x4B
+
+#define OLEDB_ILIM_NFET 0x4E
+#define OLEDB_ILIMIT_NFET_MASK GENMASK(2, 0)
+
+#define OLEDB_BIAS_GEN_WARMUP_DELAY 0x52
+#define OLEDB_BIAS_GEN_WARMUP_DELAY_MASK GENMASK(1, 0)
+
+#define OLEDB_SHORT_PROTECT 0x59
+#define OLEDB_ENABLE_SC_DETECTION_BIT BIT(7)
+#define OLEDB_DBNC_SHORT_DETECTION_MASK GENMASK(1, 0)
+
+#define OLEDB_FAST_PRECHARGE 0x5A
+#define OLEDB_FAST_PRECHG_PPULSE_EN_BIT BIT(7)
+#define OLEDB_DBNC_PRECHARGE_MASK GENMASK(5, 4)
+#define OLEDB_DBNC_PRECHARGE_SHIFT 4
+#define OLEDB_PRECHARGE_PULSE_PERIOD_MASK GENMASK(3, 2)
+#define OLEDB_PRECHARGE_PULSE_PERIOD_SHIFT 2
+#define OLEDB_PRECHARGE_PULSE_TON_MASK GENMASK(1, 0)
+
+#define OLEDB_EN_PSM 0x5B
+#define OLEDB_PSM_ENABLE_BIT BIT(7)
+
+#define OLEDB_PSM_CTL 0x5C
+#define OLEDB_PSM_HYSTERYSIS_CTL_BIT BIT(3)
+#define OLEDB_PSM_HYSTERYSIS_CTL_BIT_SHIFT 3
+#define OLEDB_VREF_PSM_MASK GENMASK(2, 0)
+
+#define OLEDB_PFM_CTL 0x5D
+#define OLEDB_PFM_ENABLE_BIT BIT(7)
+#define OLEDB_PFM_HYSTERYSIS_CTRL_BIT_MASK BIT(4)
+#define OLEDB_PFM_HYSTERYSIS_CTL_BIT_SHIFT 4
+#define OLEDB_PFM_CURR_LIMIT_MASK GENMASK(3, 2)
+#define OLEDB_PFM_CURR_LIMIT_SHIFT 2
+#define OLEDB_PFM_OFF_TIME_NS_MASK GENMASK(1, 0)
+
+#define OLEDB_NLIMIT 0x64
+#define OLEDB_ENABLE_NLIMIT_BIT BIT(7)
+#define OLEDB_ENABLE_NLIMIT_BIT_SHIFT 7
+#define OLEDB_NLIMIT_PGM_MASK GENMASK(1, 0)
+
+#define OLEDB_PSM_HYS_CTRL_MIN 13
+#define OLEDB_PSM_HYS_CTRL_MAX 26
+
+#define OLEDB_PFM_HYS_CTRL_MIN 13
+#define OLEDB_PFM_HYS_CTRL_MAX 26
+
+#define OLEDB_PFM_OFF_TIME_MIN 110
+#define OLEDB_PFM_OFF_TIME_MAX 480
+
+#define OLEDB_PRECHG_TIME_MIN 1
+#define OLEDB_PRECHG_TIME_MAX 8
+
+#define OLEDB_PRECHG_PULSE_PERIOD_MIN 3
+#define OLEDB_PRECHG_PULSE_PERIOD_MAX 12
+
+#define OLEDB_MIN_SC_DBNC_TIME_FSW 2
+#define OLEDB_MAX_SC_DBNC_TIME_FSW 16
+
+#define OLEDB_PRECHG_PULSE_ON_TIME_MIN 1200
+#define OLEDB_PRECHG_PULSE_ON_TIME_MAX 3000
+
+#define PSM_HYSTERYSIS_MV_TO_VAL(val_mv) ((val_mv/13) - 1)
+#define PFM_HYSTERYSIS_MV_TO_VAL(val_mv) ((val_mv/13) - 1)
+#define PFM_OFF_TIME_NS_TO_VAL(val_ns) ((val_ns/110) - 1)
+#define PRECHG_DEBOUNCE_TIME_MS_TO_VAL(val_ms) ((val_ms/2) - \
+ (val_ms/8))
+#define PRECHG_PULSE_PERIOD_US_TO_VAL(val_us) ((val_us/3) - 1)
+#define PRECHG_PULSE_ON_TIME_NS_TO_VAL(val_ns) (val_ns/600 - 2)
+#define SHORT_CIRCUIT_DEBOUNCE_TIME_TO_VAL(val) ((val/4) - (val/16))
+
+struct qpnp_oledb_psm_ctl {
+ int psm_enable;
+ int psm_hys_ctl;
+ int psm_vref;
+};
+
+struct qpnp_oledb_pfm_ctl {
+ int pfm_enable;
+ int pfm_hys_ctl;
+ int pfm_curr_limit;
+ int pfm_off_time;
+};
+
+struct qpnp_oledb_fast_precharge_ctl {
+ int fast_prechg_ppulse_en;
+ int prechg_debounce_time;
+ int prechg_pulse_period;
+ int prechg_pulse_on_time;
+};
+
+struct qpnp_oledb {
+ struct platform_device *pdev;
+ struct device *dev;
+ struct regmap *regmap;
+ struct regulator_desc rdesc;
+ struct regulator_dev *rdev;
+ struct qpnp_oledb_psm_ctl psm_ctl;
+ struct qpnp_oledb_pfm_ctl pfm_ctl;
+ struct qpnp_oledb_fast_precharge_ctl fast_prechg_ctl;
+
+ u32 base;
+ u8 mod_enable;
+ u8 ext_pinctl_state;
+ int current_voltage;
+ int default_voltage;
+ int vout_mv;
+ int warmup_delay;
+ int peak_curr_limit;
+ int pd_ctl;
+ int negative_curr_limit;
+ int nlimit_enable;
+ int sc_en;
+ int sc_dbnc_time;
+ bool swire_control;
+ bool ext_pin_control;
+ bool dynamic_ext_pinctl_config;
+ bool pbs_control;
+};
+
+static const u16 oledb_warmup_dly_ns[] = {6700, 13300, 26700, 53400};
+static const u16 oledb_peak_curr_limit_ma[] = {115, 265, 415, 570,
+ 720, 870, 1020, 1170};
+static const u16 oledb_psm_vref_mv[] = {440, 510, 580, 650, 715,
+ 780, 850, 920};
+static const u16 oledb_pfm_curr_limit_ma[] = {130, 200, 270, 340};
+static const u16 oledb_nlimit_ma[] = {170, 300, 420, 550};
+
+static int qpnp_oledb_read(struct qpnp_oledb *oledb, u32 address,
+ u8 *val, int count)
+{
+ int rc = 0;
+ struct platform_device *pdev = oledb->pdev;
+
+ rc = regmap_bulk_read(oledb->regmap, address, val, count);
+ if (rc)
+ pr_err("Failed to read address=0x%02x sid=0x%02x rc=%d\n",
+ address, to_spmi_device(pdev->dev.parent)->usid, rc);
+
+ return rc;
+}
+
+static int qpnp_oledb_masked_write(struct qpnp_oledb *oledb,
+ u32 address, u8 mask, u8 val)
+{
+ int rc;
+
+ rc = regmap_update_bits(oledb->regmap, address, mask, val);
+ if (rc < 0)
+ pr_err("Failed to write address 0x%04X, rc = %d\n",
+ address, rc);
+ else
+ pr_debug("Wrote 0x%02X to addr 0x%04X\n",
+ val, address);
+
+ return rc;
+}
+
+static int qpnp_oledb_write(struct qpnp_oledb *oledb, u16 address, u8 *val,
+ int count)
+{
+ int rc = 0;
+ struct platform_device *pdev = oledb->pdev;
+
+ rc = regmap_bulk_write(oledb->regmap, address, val, count);
+ if (rc)
+ pr_err("Failed to write address=0x%02x sid=0x%02x rc=%d\n",
+ address, to_spmi_device(pdev->dev.parent)->usid, rc);
+ else
+ pr_debug("Wrote 0x%02X to addr 0x%04X\n",
+ *val, address);
+
+ return 0;
+}
+
+static int qpnp_oledb_regulator_enable(struct regulator_dev *rdev)
+{
+ int rc = 0;
+ u8 val = 0;
+
+ struct qpnp_oledb *oledb = rdev_get_drvdata(rdev);
+
+ if (oledb->ext_pin_control) {
+ rc = qpnp_oledb_read(oledb, oledb->base + OLEDB_EXT_PIN_CTL,
+ &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to read EXT_PIN_CTL rc=%d\n", rc);
+ return rc;
+ }
+
+ /*
+ * Enable ext-pin-ctl after display-supply is turned on.
+ * This is to avoid glitches on the external pin.
+ */
+ if (!(val & OLEDB_EXT_PIN_CTL_BIT) &&
+ oledb->dynamic_ext_pinctl_config) {
+ val = OLEDB_EXT_PIN_CTL_BIT;
+ rc = qpnp_oledb_write(oledb, oledb->base +
+ OLEDB_EXT_PIN_CTL, &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to write EXT_PIN_CTL rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+ pr_debug("ext-pin-ctrl mode enabled\n");
+ } else {
+ val = OLEDB_MODULE_ENABLE_BIT;
+ rc = qpnp_oledb_write(oledb, oledb->base + OLEDB_MODULE_ENABLE,
+ &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to write MODULE_ENABLE rc=%d\n", rc);
+ return rc;
+ }
+
+ ndelay(oledb->warmup_delay);
+ pr_debug("register-control mode, module enabled\n");
+ }
+
+ oledb->mod_enable = true;
+ if (oledb->pbs_control) {
+ rc = qpnp_oledb_masked_write(oledb, oledb->base +
+ OLEDB_SWIRE_CONTROL, OLEDB_EN_SWIRE_PD_UPD_BIT |
+ OLEDB_EN_SWIRE_VOUT_UPD_BIT, 0);
+ if (rc < 0)
+ pr_err("Failed to write SWIRE_CTL for pbs mode rc=%d\n",
+ rc);
+ }
+
+ return rc;
+}
+
+static int qpnp_oledb_regulator_disable(struct regulator_dev *rdev)
+{
+ int rc = 0;
+
+ struct qpnp_oledb *oledb = rdev_get_drvdata(rdev);
+
+ /*
+ * Disable ext-pin-ctl after display-supply is turned off. This is to
+ * avoid glitches on the external pin.
+ */
+ if (oledb->ext_pin_control) {
+ if (oledb->dynamic_ext_pinctl_config) {
+ rc = qpnp_oledb_masked_write(oledb, oledb->base +
+ OLEDB_EXT_PIN_CTL, OLEDB_EXT_PIN_CTL_BIT, 0);
+ if (rc < 0) {
+ pr_err("Failed to write EXT_PIN_CTL rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+ pr_debug("ext-pin-ctrl mode disabled\n");
+ } else {
+ rc = qpnp_oledb_masked_write(oledb, oledb->base +
+ OLEDB_MODULE_ENABLE,
+ OLEDB_MODULE_ENABLE_BIT, 0);
+ if (rc < 0) {
+ pr_err("Failed to write MODULE_ENABLE rc=%d\n", rc);
+ return rc;
+ }
+ pr_debug("Register-control mode, module disabled\n");
+ }
+
+ oledb->mod_enable = false;
+
+ return rc;
+}
+
+static int qpnp_oledb_regulator_is_enabled(struct regulator_dev *rdev)
+{
+ struct qpnp_oledb *oledb = rdev_get_drvdata(rdev);
+
+ return oledb->mod_enable;
+}
+
+static int qpnp_oledb_regulator_set_voltage(struct regulator_dev *rdev,
+ int min_uV, int max_uV, unsigned int *selector)
+{
+ u8 val;
+ int rc = 0;
+
+ struct qpnp_oledb *oledb = rdev_get_drvdata(rdev);
+
+ if (oledb->swire_control)
+ return 0;
+
+ val = DIV_ROUND_UP(min_uV - OLEDB_VOUT_MIN_MV, OLEDB_VOUT_STEP_MV);
+
+ rc = qpnp_oledb_write(oledb, oledb->base + OLEDB_VOUT_PGM,
+ &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to write VOUT_PGM rc=%d\n", rc);
+ return rc;
+ }
+
+ oledb->current_voltage = min_uV;
+ pr_debug("register-control mode, current voltage %d\n",
+ oledb->current_voltage);
+
+ return 0;
+}
+
+static int qpnp_oledb_regulator_get_voltage(struct regulator_dev *rdev)
+{
+ struct qpnp_oledb *oledb = rdev_get_drvdata(rdev);
+
+ if (oledb->swire_control)
+ return 0;
+
+ return oledb->current_voltage;
+}
+
+static struct regulator_ops qpnp_oledb_ops = {
+ .enable = qpnp_oledb_regulator_enable,
+ .disable = qpnp_oledb_regulator_disable,
+ .is_enabled = qpnp_oledb_regulator_is_enabled,
+ .set_voltage = qpnp_oledb_regulator_set_voltage,
+ .get_voltage = qpnp_oledb_regulator_get_voltage,
+};
+
+static int qpnp_oledb_register_regulator(struct qpnp_oledb *oledb)
+{
+ int rc = 0;
+ struct platform_device *pdev = oledb->pdev;
+ struct regulator_init_data *init_data;
+ struct regulator_desc *rdesc = &oledb->rdesc;
+ struct regulator_config cfg = {};
+
+ init_data = of_get_regulator_init_data(&pdev->dev,
+ pdev->dev.of_node, rdesc);
+ if (!init_data) {
+ pr_err("Unable to get OLEDB regulator init data\n");
+ return -ENOMEM;
+ }
+
+ if (init_data->constraints.name) {
+ rdesc->owner = THIS_MODULE;
+ rdesc->type = REGULATOR_VOLTAGE;
+ rdesc->ops = &qpnp_oledb_ops;
+ rdesc->name = init_data->constraints.name;
+
+ cfg.dev = &pdev->dev;
+ cfg.init_data = init_data;
+ cfg.driver_data = oledb;
+ cfg.of_node = pdev->dev.of_node;
+
+ if (of_get_property(pdev->dev.of_node, "parent-supply",
+ NULL))
+ init_data->supply_regulator = "parent";
+
+ init_data->constraints.valid_ops_mask
+ |= REGULATOR_CHANGE_VOLTAGE |
+ REGULATOR_CHANGE_STATUS;
+
+ oledb->rdev = devm_regulator_register(oledb->dev, rdesc, &cfg);
+ if (IS_ERR(oledb->rdev)) {
+ rc = PTR_ERR(oledb->rdev);
+ oledb->rdev = NULL;
+ pr_err("Unable to register OLEDB regulator, rc = %d\n",
+ rc);
+ return rc;
+ }
+ } else {
+ pr_err("OLEDB regulator name missing\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int qpnp_oledb_get_curr_voltage(struct qpnp_oledb *oledb,
+ u16 *current_voltage)
+{
+ int rc = 0;
+ u8 val;
+
+ if (!(oledb->mod_enable || oledb->ext_pinctl_state)) {
+ rc = qpnp_oledb_read(oledb, oledb->base + OLEDB_VOUT_DEFAULT,
+ &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to read VOUT_DEFAULT rc=%d\n", rc);
+ return rc;
+ }
+ } else {
+ rc = qpnp_oledb_read(oledb, oledb->base +
+ OLEDB_VOUT_PGM, &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to read VOUT_PGM rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ *current_voltage = (val * OLEDB_VOUT_STEP_MV) + OLEDB_VOUT_MIN_MV;
+
+ return rc;
+}
+
+static int qpnp_oledb_init_nlimit(struct qpnp_oledb *oledb)
+{
+ int rc = 0, i = 0;
+ u32 val, mask = 0;
+
+ if (oledb->nlimit_enable != -EINVAL) {
+ val = oledb->nlimit_enable <<
+ OLEDB_ENABLE_NLIMIT_BIT_SHIFT;
+ mask = OLEDB_ENABLE_NLIMIT_BIT;
+ if (oledb->negative_curr_limit != -EINVAL) {
+ for (i = 0; i < ARRAY_SIZE(oledb_nlimit_ma); i++) {
+ if (oledb->negative_curr_limit ==
+ oledb_nlimit_ma[i])
+ break;
+ }
+ val |= i;
+ mask |= OLEDB_NLIMIT_PGM_MASK;
+ }
+ rc = qpnp_oledb_masked_write(oledb, oledb->base +
+ OLEDB_NLIMIT, mask, val);
+ if (rc < 0)
+ pr_err("Failed to write NLIMT rc=%d\n", rc);
+ }
+
+ return rc;
+}
+
+static int qpnp_oledb_init_psm(struct qpnp_oledb *oledb)
+{
+ int rc = 0, i = 0;
+ u32 val = 0, mask = 0, temp = 0;
+ struct qpnp_oledb_psm_ctl *psm_ctl = &oledb->psm_ctl;
+
+ if (psm_ctl->psm_enable == -EINVAL)
+ return rc;
+
+ if (psm_ctl->psm_enable) {
+ val = OLEDB_PSM_ENABLE_BIT;
+ rc = qpnp_oledb_masked_write(oledb, oledb->base +
+ OLEDB_EN_PSM, OLEDB_PSM_ENABLE_BIT, val);
+ if (rc < 0) {
+ pr_err("Failed to write PSM_EN rc=%d\n", rc);
+ return rc;
+ }
+
+ val = 0;
+ if (psm_ctl->psm_vref != -EINVAL) {
+ for (i = 0; i < ARRAY_SIZE(oledb_psm_vref_mv); i++) {
+ if (psm_ctl->psm_vref ==
+ oledb_psm_vref_mv[i])
+ break;
+ }
+ val = i;
+ mask = OLEDB_VREF_PSM_MASK;
+ }
+
+ if (psm_ctl->psm_hys_ctl != -EINVAL) {
+ temp = PSM_HYSTERYSIS_MV_TO_VAL(psm_ctl->psm_hys_ctl);
+ val |= (temp << OLEDB_PSM_HYSTERYSIS_CTL_BIT_SHIFT);
+ mask |= OLEDB_PSM_HYSTERYSIS_CTL_BIT;
+ }
+ if (val) {
+ rc = qpnp_oledb_masked_write(oledb, oledb->base +
+ OLEDB_PSM_CTL, mask, val);
+ if (rc < 0)
+ pr_err("Failed to write PSM_CTL rc=%d\n", rc);
+ }
+ } else {
+ rc = qpnp_oledb_masked_write(oledb, oledb->base +
+ OLEDB_EN_PSM, OLEDB_PSM_ENABLE_BIT, 0);
+ if (rc < 0)
+ pr_err("Failed to write PSM_CTL rc=%d\n", rc);
+ }
+
+ return rc;
+}
+
+static int qpnp_oledb_init_pfm(struct qpnp_oledb *oledb)
+{
+ int rc = 0, i = 0;
+ u32 val = 0, temp = 0, mask = 0;
+ struct qpnp_oledb_pfm_ctl *pfm_ctl = &oledb->pfm_ctl;
+
+ if (pfm_ctl->pfm_enable == -EINVAL)
+ return rc;
+
+ if (pfm_ctl->pfm_enable) {
+ mask = val = OLEDB_PFM_ENABLE_BIT;
+ if (pfm_ctl->pfm_hys_ctl != -EINVAL) {
+ temp = PFM_HYSTERYSIS_MV_TO_VAL(pfm_ctl->pfm_hys_ctl);
+ val |= temp <<
+ OLEDB_PFM_HYSTERYSIS_CTL_BIT_SHIFT;
+ mask |= OLEDB_PFM_HYSTERYSIS_CTRL_BIT_MASK;
+ }
+
+ if (pfm_ctl->pfm_curr_limit != -EINVAL) {
+ for (i = 0; i < ARRAY_SIZE(oledb_pfm_curr_limit_ma);
+ i++) {
+ if (pfm_ctl->pfm_curr_limit ==
+ oledb_pfm_curr_limit_ma[i])
+ break;
+ }
+ val |= (i << OLEDB_PFM_CURR_LIMIT_SHIFT);
+ mask |= OLEDB_PFM_CURR_LIMIT_MASK;
+ }
+
+ if (pfm_ctl->pfm_off_time != -EINVAL) {
+ val |= PFM_OFF_TIME_NS_TO_VAL(pfm_ctl->pfm_off_time);
+ mask |= OLEDB_PFM_OFF_TIME_NS_MASK;
+ }
+
+ rc = qpnp_oledb_masked_write(oledb, oledb->base +
+ OLEDB_PFM_CTL, mask, val);
+ if (rc < 0)
+ pr_err("Failed to write PFM_CTL rc=%d\n", rc);
+ } else {
+ rc = qpnp_oledb_masked_write(oledb, oledb->base +
+ OLEDB_PFM_CTL, OLEDB_PFM_ENABLE_BIT, 0);
+ if (rc < 0)
+ pr_err("Failed to write PFM_CTL rc=%d\n", rc);
+ }
+
+ return rc;
+}
+
+static int qpnp_oledb_init_fast_precharge(struct qpnp_oledb *oledb)
+{
+ int rc = 0;
+ u32 val = 0, temp = 0, mask = 0;
+ struct qpnp_oledb_fast_precharge_ctl *prechg_ctl =
+ &oledb->fast_prechg_ctl;
+
+ if (prechg_ctl->fast_prechg_ppulse_en == -EINVAL)
+ return rc;
+
+ if (prechg_ctl->fast_prechg_ppulse_en) {
+ mask = val = OLEDB_FAST_PRECHG_PPULSE_EN_BIT;
+ if (prechg_ctl->prechg_debounce_time != -EINVAL) {
+ temp = PRECHG_DEBOUNCE_TIME_MS_TO_VAL(
+ prechg_ctl->prechg_debounce_time);
+ val |= temp << OLEDB_DBNC_PRECHARGE_SHIFT;
+ mask |= OLEDB_DBNC_PRECHARGE_MASK;
+ }
+
+ if (prechg_ctl->prechg_pulse_period != -EINVAL) {
+ temp = PRECHG_PULSE_PERIOD_US_TO_VAL(
+ prechg_ctl->prechg_pulse_period);
+ val |= temp << OLEDB_PRECHARGE_PULSE_PERIOD_SHIFT;
+ mask |= OLEDB_PRECHARGE_PULSE_PERIOD_MASK;
+ }
+
+ if (prechg_ctl->prechg_pulse_on_time != -EINVAL) {
+ val |= PRECHG_PULSE_ON_TIME_NS_TO_VAL(
+ prechg_ctl->prechg_pulse_on_time);
+ mask |= OLEDB_PRECHARGE_PULSE_TON_MASK;
+ }
+
+ rc = qpnp_oledb_masked_write(oledb, oledb->base +
+ OLEDB_FAST_PRECHARGE, mask, val);
+ if (rc < 0)
+ pr_err("Failed to write FAST_PRECHARGE rc=%d\n", rc);
+ } else {
+ rc = qpnp_oledb_masked_write(oledb, oledb->base +
+ OLEDB_FAST_PRECHARGE,
+ OLEDB_FAST_PRECHG_PPULSE_EN_BIT, 0);
+ if (rc < 0)
+ pr_err("Failed to write FAST_PRECHARGE rc=%d\n", rc);
+ }
+
+ return rc;
+}
+
+static int qpnp_oledb_hw_init(struct qpnp_oledb *oledb)
+{
+ int rc, i = 0;
+ u8 val = 0, mask = 0;
+ u16 current_voltage;
+
+ if (oledb->default_voltage != -EINVAL) {
+ val = (oledb->default_voltage - OLEDB_VOUT_MIN_MV) /
+ OLEDB_VOUT_STEP_MV;
+ rc = qpnp_oledb_write(oledb, oledb->base +
+ OLEDB_VOUT_DEFAULT, &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to write VOUT_DEFAULT rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ rc = qpnp_oledb_read(oledb, oledb->base + OLEDB_MODULE_ENABLE,
+ &oledb->mod_enable, 1);
+ if (rc < 0) {
+ pr_err("Failed to read MODULE_ENABLE rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = qpnp_oledb_read(oledb, oledb->base + OLEDB_EXT_PIN_CTL,
+ &oledb->ext_pinctl_state, 1);
+ if (rc < 0) {
+ pr_err("Failed to read EXT_PIN_CTL rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = qpnp_oledb_get_curr_voltage(oledb, ¤t_voltage);
+ if (rc < 0)
+ return rc;
+
+ /*
+ * Go through if the module is not enabled either through
+ * external pin control or SPMI interface.
+ */
+ if (!((oledb->ext_pinctl_state & OLEDB_EXT_PIN_CTL_BIT)
+ || oledb->mod_enable)) {
+ if (oledb->warmup_delay != -EINVAL) {
+ for (i = 0; i < ARRAY_SIZE(oledb_warmup_dly_ns); i++) {
+ if (oledb->warmup_delay ==
+ oledb_warmup_dly_ns[i])
+ break;
+ }
+ val = i;
+ rc = qpnp_oledb_masked_write(oledb,
+ oledb->base + OLEDB_BIAS_GEN_WARMUP_DELAY,
+ OLEDB_BIAS_GEN_WARMUP_DELAY_MASK, val);
+ if (rc < 0) {
+ pr_err("Failed to write WARMUP_DELAY rc=%d\n",
+ rc);
+ return rc;
+ }
+ } else {
+ rc = qpnp_oledb_read(oledb, oledb->base +
+ OLEDB_BIAS_GEN_WARMUP_DELAY,
+ &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to read WARMUP_DELAY rc=%d\n",
+ rc);
+ return rc;
+ }
+ oledb->warmup_delay = oledb_warmup_dly_ns[val];
+ }
+
+ if (oledb->peak_curr_limit != -EINVAL) {
+ for (i = 0; i < ARRAY_SIZE(oledb_peak_curr_limit_ma);
+ i++) {
+ if (oledb->peak_curr_limit ==
+ oledb_peak_curr_limit_ma[i])
+ break;
+ }
+ val = i;
+ rc = qpnp_oledb_write(oledb,
+ oledb->base + OLEDB_ILIM_NFET,
+ &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to write ILIM_NEFT rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ if (oledb->pd_ctl != -EINVAL) {
+ val = oledb->pd_ctl;
+ rc = qpnp_oledb_write(oledb, oledb->base +
+ OLEDB_PD_CTL, &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to write PD_CTL rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ if (oledb->sc_en != -EINVAL) {
+ val = oledb->sc_en ? OLEDB_ENABLE_SC_DETECTION_BIT : 0;
+ mask = OLEDB_ENABLE_SC_DETECTION_BIT;
+ if (oledb->sc_dbnc_time != -EINVAL) {
+ val |= SHORT_CIRCUIT_DEBOUNCE_TIME_TO_VAL(
+ oledb->sc_dbnc_time);
+ mask |= OLEDB_DBNC_PRECHARGE_MASK;
+ }
+
+ rc = qpnp_oledb_write(oledb, oledb->base +
+ OLEDB_SHORT_PROTECT, &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to write SHORT_PROTECT rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ rc = qpnp_oledb_init_nlimit(oledb);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_oledb_init_psm(oledb);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_oledb_init_pfm(oledb);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_oledb_init_fast_precharge(oledb);
+ if (rc < 0)
+ return rc;
+
+ if (oledb->swire_control) {
+ val = OLEDB_EN_SWIRE_PD_UPD_BIT |
+ OLEDB_EN_SWIRE_VOUT_UPD_BIT;
+ rc = qpnp_oledb_masked_write(oledb, oledb->base +
+ OLEDB_SWIRE_CONTROL, OLEDB_EN_SWIRE_PD_UPD_BIT |
+ OLEDB_EN_SWIRE_VOUT_UPD_BIT, val);
+ if (rc < 0)
+ return rc;
+ }
+
+ rc = qpnp_oledb_read(oledb, oledb->base + OLEDB_MODULE_RDY,
+ &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to read MODULE_RDY rc=%d\n", rc);
+ return rc;
+ }
+
+ if (!(val & OLEDB_MODULE_RDY_BIT)) {
+ val = OLEDB_MODULE_RDY_BIT;
+ rc = qpnp_oledb_write(oledb, oledb->base +
+ OLEDB_MODULE_RDY, &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to write MODULE_RDY rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ if (!oledb->dynamic_ext_pinctl_config) {
+ if (oledb->ext_pin_control) {
+ val = OLEDB_EXT_PIN_CTL_BIT;
+ rc = qpnp_oledb_write(oledb, oledb->base +
+ OLEDB_EXT_PIN_CTL, &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to write EXT_PIN_CTL rc=%d\n",
+ rc);
+ return rc;
+ }
+ } else {
+ val = OLEDB_MODULE_ENABLE_BIT;
+ rc = qpnp_oledb_write(oledb, oledb->base +
+ OLEDB_MODULE_ENABLE, &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to write MODULE_ENABLE rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ ndelay(oledb->warmup_delay);
+ }
+
+ oledb->mod_enable = true;
+ if (oledb->pbs_control) {
+ rc = qpnp_oledb_masked_write(oledb,
+ oledb->base + OLEDB_SWIRE_CONTROL,
+ OLEDB_EN_SWIRE_PD_UPD_BIT |
+ OLEDB_EN_SWIRE_VOUT_UPD_BIT, 0);
+ if (rc < 0) {
+ pr_err("Failed to write SWIRE_CTL rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+ }
+
+ oledb->current_voltage = current_voltage;
+ } else {
+ /* module is enabled */
+ if (oledb->current_voltage == -EINVAL) {
+ oledb->current_voltage = current_voltage;
+ } else if (!oledb->swire_control) {
+ if (oledb->current_voltage < OLEDB_VOUT_MIN_MV) {
+ pr_err("current_voltage %d is less than min_volt %d\n",
+ oledb->current_voltage, OLEDB_VOUT_MIN_MV);
+ return -EINVAL;
+ }
+ val = DIV_ROUND_UP(oledb->current_voltage -
+ OLEDB_VOUT_MIN_MV, OLEDB_VOUT_STEP_MV);
+ rc = qpnp_oledb_write(oledb, oledb->base +
+ OLEDB_VOUT_PGM, &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to write VOUT_PGM rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ oledb->mod_enable = true;
+ }
+
+ return rc;
+}
+
+static int qpnp_oledb_parse_nlimit(struct qpnp_oledb *oledb)
+{
+ int rc = 0;
+ struct device_node *of_node = oledb->dev->of_node;
+
+ oledb->nlimit_enable = -EINVAL;
+ rc = of_property_read_u32(of_node, "qcom,negative-curr-limit-enable",
+ &oledb->nlimit_enable);
+ if (!rc) {
+ oledb->negative_curr_limit = -EINVAL;
+ rc = of_property_read_u32(of_node,
+ "qcom,negative-curr-limit-ma",
+ &oledb->negative_curr_limit);
+ if (!rc) {
+ u16 min_curr_limit = oledb_nlimit_ma[0];
+ u16 max_curr_limit = oledb_nlimit_ma[ARRAY_SIZE(
+ oledb_nlimit_ma) - 1];
+ if (oledb->negative_curr_limit < min_curr_limit ||
+ oledb->negative_curr_limit > max_curr_limit) {
+ pr_err("Invalid value in qcom,negative-curr-limit-ma\n");
+ return -EINVAL;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int qpnp_oledb_parse_psm(struct qpnp_oledb *oledb)
+{
+ int rc = 0;
+ struct qpnp_oledb_psm_ctl *psm_ctl = &oledb->psm_ctl;
+ struct device_node *of_node = oledb->dev->of_node;
+
+ psm_ctl->psm_enable = -EINVAL;
+ rc = of_property_read_u32(of_node, "qcom,psm-enable",
+ &psm_ctl->psm_enable);
+ if (!rc) {
+ psm_ctl->psm_hys_ctl = -EINVAL;
+ rc = of_property_read_u32(of_node, "qcom,psm-hys-mv",
+ &psm_ctl->psm_hys_ctl);
+ if (!rc) {
+ if (psm_ctl->psm_hys_ctl < OLEDB_PSM_HYS_CTRL_MIN ||
+ psm_ctl->psm_hys_ctl > OLEDB_PSM_HYS_CTRL_MAX) {
+ pr_err("Invalid value in qcom,psm-hys-mv\n");
+ return -EINVAL;
+ }
+ }
+
+ psm_ctl->psm_vref = -EINVAL;
+ rc = of_property_read_u32(of_node, "qcom,psm-vref-mv",
+ &psm_ctl->psm_vref);
+ if (!rc) {
+ u16 min_vref = oledb_psm_vref_mv[0];
+ u16 max_vref = oledb_psm_vref_mv[ARRAY_SIZE(
+ oledb_psm_vref_mv) - 1];
+ if (psm_ctl->psm_vref < min_vref ||
+ psm_ctl->psm_vref > max_vref) {
+ pr_err("Invalid value in qcom,psm-vref-mv\n");
+ return -EINVAL;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int qpnp_oledb_parse_pfm(struct qpnp_oledb *oledb)
+{
+ int rc = 0;
+ struct qpnp_oledb_pfm_ctl *pfm_ctl = &oledb->pfm_ctl;
+ struct device_node *of_node = oledb->dev->of_node;
+
+ pfm_ctl->pfm_enable = -EINVAL;
+ rc = of_property_read_u32(of_node, "qcom,pfm-enable",
+ &pfm_ctl->pfm_enable);
+ if (!rc) {
+ pfm_ctl->pfm_hys_ctl = -EINVAL;
+ rc = of_property_read_u32(of_node, "qcom,pfm-hys-mv",
+ &pfm_ctl->pfm_hys_ctl);
+ if (!rc) {
+ if (pfm_ctl->pfm_hys_ctl < OLEDB_PFM_HYS_CTRL_MIN ||
+ pfm_ctl->pfm_hys_ctl > OLEDB_PFM_HYS_CTRL_MAX) {
+ pr_err("Invalid value in qcom,pfm-hys-mv\n");
+ return -EINVAL;
+ }
+ }
+
+ pfm_ctl->pfm_curr_limit = -EINVAL;
+ rc = of_property_read_u32(of_node,
+ "qcom,pfm-curr-limit-ma", &pfm_ctl->pfm_curr_limit);
+ if (!rc) {
+ u16 min_limit = oledb_pfm_curr_limit_ma[0];
+ u16 max_limit = oledb_pfm_curr_limit_ma[ARRAY_SIZE(
+ oledb_pfm_curr_limit_ma) - 1];
+ if (pfm_ctl->pfm_curr_limit < min_limit ||
+ pfm_ctl->pfm_curr_limit > max_limit) {
+ pr_err("Invalid value in qcom,pfm-curr-limit-ma\n");
+ return -EINVAL;
+ }
+ }
+
+ pfm_ctl->pfm_off_time = -EINVAL;
+ rc = of_property_read_u32(of_node, "qcom,pfm-off-time-ns",
+ &pfm_ctl->pfm_off_time);
+ if (!rc) {
+ if (pfm_ctl->pfm_off_time < OLEDB_PFM_OFF_TIME_MIN ||
+ pfm_ctl->pfm_off_time > OLEDB_PFM_OFF_TIME_MAX) {
+ pr_err("Invalid value in qcom,pfm-off-time-ns\n");
+ return -EINVAL;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int qpnp_oledb_parse_fast_precharge(struct qpnp_oledb *oledb)
+{
+ int rc = 0;
+ struct device_node *of_node = oledb->dev->of_node;
+ struct qpnp_oledb_fast_precharge_ctl *fast_prechg =
+ &oledb->fast_prechg_ctl;
+
+ fast_prechg->fast_prechg_ppulse_en = -EINVAL;
+ rc = of_property_read_u32(of_node, "qcom,fast-precharge-ppulse-enable",
+ &fast_prechg->fast_prechg_ppulse_en);
+ if (!rc) {
+ fast_prechg->prechg_debounce_time = -EINVAL;
+ rc = of_property_read_u32(of_node,
+ "qcom,precharge-debounce-time-ms",
+ &fast_prechg->prechg_debounce_time);
+ if (!rc) {
+ int dbnc_time = fast_prechg->prechg_debounce_time;
+
+ if (dbnc_time < OLEDB_PRECHG_TIME_MIN || dbnc_time >
+ OLEDB_PRECHG_TIME_MAX) {
+ pr_err("Invalid value in qcom,precharge-debounce-time-ms\n");
+ return -EINVAL;
+ }
+ }
+
+ fast_prechg->prechg_pulse_period = -EINVAL;
+ rc = of_property_read_u32(of_node,
+ "qcom,precharge-pulse-period-us",
+ &fast_prechg->prechg_pulse_period);
+ if (!rc) {
+ int pulse_period = fast_prechg->prechg_pulse_period;
+
+ if (pulse_period < OLEDB_PRECHG_PULSE_PERIOD_MIN ||
+ pulse_period > OLEDB_PRECHG_PULSE_PERIOD_MAX) {
+ pr_err("Invalid value in qcom,precharge-pulse-period-us\n");
+ return -EINVAL;
+ }
+ }
+
+ fast_prechg->prechg_pulse_on_time = -EINVAL;
+ rc = of_property_read_u32(of_node,
+ "qcom,precharge-pulse-on-time-ns",
+ &fast_prechg->prechg_pulse_on_time);
+ if (!rc) {
+ int pulse_on_time = fast_prechg->prechg_pulse_on_time;
+
+ if (pulse_on_time < OLEDB_PRECHG_PULSE_ON_TIME_MIN ||
+ pulse_on_time > OLEDB_PRECHG_PULSE_ON_TIME_MAX) {
+ pr_err("Invalid value in qcom,precharge-pulse-on-time-ns\n");
+ return -EINVAL;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int qpnp_oledb_parse_dt(struct qpnp_oledb *oledb)
+{
+ int rc = 0;
+ struct device_node *of_node = oledb->dev->of_node;
+
+ oledb->swire_control =
+ of_property_read_bool(of_node, "qcom,swire-control");
+
+ oledb->ext_pin_control =
+ of_property_read_bool(of_node, "qcom,ext-pin-control");
+
+ if (oledb->ext_pin_control)
+ oledb->dynamic_ext_pinctl_config =
+ of_property_read_bool(of_node,
+ "qcom,dynamic-ext-pinctl-config");
+ oledb->pbs_control =
+ of_property_read_bool(of_node, "qcom,pbs-control");
+
+ oledb->current_voltage = -EINVAL;
+ rc = of_property_read_u32(of_node, "qcom,oledb-init-voltage-mv",
+ &oledb->current_voltage);
+ if (!rc && (oledb->current_voltage < OLEDB_VOUT_MIN_MV ||
+ oledb->current_voltage > OLEDB_VOUT_MAX_MV)) {
+ pr_err("Invalid value in qcom,oledb-init-voltage-mv\n");
+ return -EINVAL;
+ }
+
+ oledb->default_voltage = -EINVAL;
+ rc = of_property_read_u32(of_node, "qcom,oledb-default-voltage-mv",
+ &oledb->default_voltage);
+ if (!rc && (oledb->default_voltage < OLEDB_VOUT_MIN_MV ||
+ oledb->default_voltage > OLEDB_VOUT_MAX_MV)) {
+ pr_err("Invalid value in qcom,oledb-default-voltage-mv\n");
+ return -EINVAL;
+ }
+
+ oledb->warmup_delay = -EINVAL;
+ rc = of_property_read_u32(of_node, "qcom,bias-gen-warmup-delay-ns",
+ &oledb->warmup_delay);
+ if (!rc) {
+ u16 min_delay = oledb_warmup_dly_ns[0];
+ u16 max_delay = oledb_warmup_dly_ns[ARRAY_SIZE(
+ oledb_warmup_dly_ns) - 1];
+ if (oledb->warmup_delay < min_delay ||
+ oledb->warmup_delay > max_delay) {
+ pr_err("Invalid value in qcom,bias-gen-warmup-delay-ns\n");
+ return -EINVAL;
+ }
+ }
+
+ oledb->peak_curr_limit = -EINVAL;
+ rc = of_property_read_u32(of_node, "qcom,peak-curr-limit-ma",
+ &oledb->peak_curr_limit);
+ if (!rc) {
+ u16 min_limit = oledb_peak_curr_limit_ma[0];
+ u16 max_limit = oledb_peak_curr_limit_ma[ARRAY_SIZE(
+ oledb_peak_curr_limit_ma) - 1];
+ if (oledb->peak_curr_limit < min_limit ||
+ oledb->peak_curr_limit > max_limit) {
+ pr_err("Invalid value in qcom,peak-curr-limit-ma\n");
+ return -EINVAL;
+ }
+ }
+
+ oledb->pd_ctl = -EINVAL;
+ of_property_read_u32(of_node, "qcom,pull-down-enable", &oledb->pd_ctl);
+
+ oledb->sc_en = -EINVAL;
+ rc = of_property_read_u32(of_node, "qcom,enable-short-circuit",
+ &oledb->sc_en);
+ if (!rc) {
+ oledb->sc_dbnc_time = -EINVAL;
+ rc = of_property_read_u32(of_node,
+ "qcom,short-circuit-dbnc-time", &oledb->sc_dbnc_time);
+ if (!rc) {
+ if (oledb->sc_dbnc_time < OLEDB_MIN_SC_DBNC_TIME_FSW ||
+ oledb->sc_dbnc_time > OLEDB_MAX_SC_DBNC_TIME_FSW) {
+ pr_err("Invalid value in qcom,short-circuit-dbnc-time\n");
+ return -EINVAL;
+ }
+ }
+ }
+
+ rc = qpnp_oledb_parse_nlimit(oledb);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_oledb_parse_psm(oledb);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_oledb_parse_pfm(oledb);
+ if (rc < 0)
+ return rc;
+
+ rc = qpnp_oledb_parse_fast_precharge(oledb);
+
+ return rc;
+}
+
+static int qpnp_oledb_regulator_probe(struct platform_device *pdev)
+{
+ int rc = 0;
+ u32 val;
+ struct qpnp_oledb *oledb;
+ struct device_node *of_node = pdev->dev.of_node;
+
+ oledb = devm_kzalloc(&pdev->dev,
+ sizeof(struct qpnp_oledb), GFP_KERNEL);
+ if (!oledb)
+ return -ENOMEM;
+
+ oledb->pdev = pdev;
+ oledb->dev = &pdev->dev;
+ oledb->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ dev_set_drvdata(&pdev->dev, oledb);
+ if (!oledb->regmap) {
+ pr_err("Couldn't get parent's regmap\n");
+ return -EINVAL;
+ }
+
+ rc = of_property_read_u32(of_node, "reg", &val);
+ if (rc < 0) {
+ pr_err("Couldn't find reg in node, rc = %d\n", rc);
+ return rc;
+ }
+
+ oledb->base = val;
+ rc = qpnp_oledb_parse_dt(oledb);
+ if (rc < 0) {
+ pr_err("Failed to parse common OLEDB device tree\n");
+ return rc;
+ }
+
+ rc = qpnp_oledb_hw_init(oledb);
+ if (rc < 0) {
+ pr_err("Failed to initialize OLEDB, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = qpnp_oledb_register_regulator(oledb);
+ if (!rc)
+ pr_info("OLEDB registered successfully, ext_pin_en=%d mod_en=%d cuurent_voltage=%d mV\n",
+ oledb->ext_pin_control, oledb->mod_enable,
+ oledb->current_voltage);
+
+ return rc;
+}
+
+static int qpnp_oledb_regulator_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+const struct of_device_id qpnp_oledb_regulator_match_table[] = {
+ { .compatible = QPNP_OLEDB_REGULATOR_DRIVER_NAME,},
+ { },
+};
+
+static struct platform_driver qpnp_oledb_regulator_driver = {
+ .driver = {
+ .name = QPNP_OLEDB_REGULATOR_DRIVER_NAME,
+ .of_match_table = qpnp_oledb_regulator_match_table,
+ },
+ .probe = qpnp_oledb_regulator_probe,
+ .remove = qpnp_oledb_regulator_remove,
+};
+
+static int __init qpnp_oledb_regulator_init(void)
+{
+ return platform_driver_register(&qpnp_oledb_regulator_driver);
+}
+arch_initcall(qpnp_oledb_regulator_init);
+
+static void __exit qpnp_oledb_regulator_exit(void)
+{
+ platform_driver_unregister(&qpnp_oledb_regulator_driver);
+}
+module_exit(qpnp_oledb_regulator_exit);
+
+MODULE_DESCRIPTION("QPNP OLEDB driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("qpnp-oledb-regulator");
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index e859d14..6e5c443 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -1589,6 +1589,15 @@
To compile this driver as a module, choose M here: the
module will be called rtc-pm8xxx.
+config RTC_DRV_QPNP
+ tristate "Qualcomm Technologies, Inc. QPNP PMIC RTC"
+ depends on SPMI
+ help
+ This enables support for the RTC found on Qualcomm Technologies, Inc.
+ QPNP PMIC chips. This driver supports using the PMIC RTC peripheral
+ to wake a mobile device up from suspend or to wake it up from power-
+ off.
+
config RTC_DRV_TEGRA
tristate "NVIDIA Tegra Internal RTC driver"
depends on ARCH_TEGRA || COMPILE_TEST
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 1ac694a..f282f73 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -120,6 +120,7 @@
obj-$(CONFIG_RTC_DRV_PS3) += rtc-ps3.o
obj-$(CONFIG_RTC_DRV_PUV3) += rtc-puv3.o
obj-$(CONFIG_RTC_DRV_PXA) += rtc-pxa.o
+obj-$(CONFIG_RTC_DRV_QPNP) += qpnp-rtc.o
obj-$(CONFIG_RTC_DRV_R9701) += rtc-r9701.o
obj-$(CONFIG_RTC_DRV_RC5T583) += rtc-rc5t583.o
obj-$(CONFIG_RTC_DRV_RK808) += rtc-rk808.o
diff --git a/drivers/rtc/qpnp-rtc.c b/drivers/rtc/qpnp-rtc.c
new file mode 100644
index 0000000..a2c004e
--- /dev/null
+++ b/drivers/rtc/qpnp-rtc.c
@@ -0,0 +1,717 @@
+/* Copyright (c) 2012-2015, 2017, The Linux Foundation. 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.
+ */
+
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/init.h>
+#include <linux/rtc.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/idr.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/alarmtimer.h>
+
+/* RTC/ALARM Register offsets */
+#define REG_OFFSET_ALARM_RW 0x40
+#define REG_OFFSET_ALARM_CTRL1 0x46
+#define REG_OFFSET_ALARM_CTRL2 0x48
+#define REG_OFFSET_RTC_WRITE 0x40
+#define REG_OFFSET_RTC_CTRL 0x46
+#define REG_OFFSET_RTC_READ 0x48
+#define REG_OFFSET_PERP_SUBTYPE 0x05
+
+/* RTC_CTRL register bit fields */
+#define BIT_RTC_ENABLE BIT(7)
+#define BIT_RTC_ALARM_ENABLE BIT(7)
+#define BIT_RTC_ABORT_ENABLE BIT(0)
+#define BIT_RTC_ALARM_CLEAR BIT(0)
+
+/* RTC/ALARM peripheral subtype values */
+#define RTC_PERPH_SUBTYPE 0x1
+#define ALARM_PERPH_SUBTYPE 0x3
+
+#define NUM_8_BIT_RTC_REGS 0x4
+
+#define TO_SECS(arr) (arr[0] | (arr[1] << 8) | (arr[2] << 16) | \
+ (arr[3] << 24))
+
+/* Module parameter to control power-on-alarm */
+bool poweron_alarm;
+EXPORT_SYMBOL(poweron_alarm);
+module_param(poweron_alarm, bool, 0644);
+MODULE_PARM_DESC(poweron_alarm, "Enable/Disable power-on alarm");
+
+/* rtc driver internal structure */
+struct qpnp_rtc {
+ u8 rtc_ctrl_reg;
+ u8 alarm_ctrl_reg1;
+ u16 rtc_base;
+ u16 alarm_base;
+ u32 rtc_write_enable;
+ u32 rtc_alarm_powerup;
+ int rtc_alarm_irq;
+ struct device *rtc_dev;
+ struct rtc_device *rtc;
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ spinlock_t alarm_ctrl_lock;
+};
+
+static int qpnp_read_wrapper(struct qpnp_rtc *rtc_dd, u8 *rtc_val,
+ u16 base, int count)
+{
+ int rc;
+
+ rc = regmap_bulk_read(rtc_dd->regmap, base, rtc_val, count);
+ if (rc) {
+ dev_err(rtc_dd->rtc_dev, "SPMI read failed\n");
+ return rc;
+ }
+ return 0;
+}
+
+static int qpnp_write_wrapper(struct qpnp_rtc *rtc_dd, u8 *rtc_val,
+ u16 base, int count)
+{
+ int rc;
+
+ rc = regmap_bulk_write(rtc_dd->regmap, base, rtc_val, count);
+ if (rc) {
+ dev_err(rtc_dd->rtc_dev, "SPMI write failed\n");
+ return rc;
+ }
+
+ return 0;
+}
+
+static int
+qpnp_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+ int rc;
+ unsigned long secs, irq_flags;
+ u8 value[4], reg = 0, alarm_enabled = 0, ctrl_reg;
+ u8 rtc_disabled = 0, rtc_ctrl_reg;
+ struct qpnp_rtc *rtc_dd = dev_get_drvdata(dev);
+
+ rtc_tm_to_time(tm, &secs);
+
+ value[0] = secs & 0xFF;
+ value[1] = (secs >> 8) & 0xFF;
+ value[2] = (secs >> 16) & 0xFF;
+ value[3] = (secs >> 24) & 0xFF;
+
+ dev_dbg(dev, "Seconds value to be written to RTC = %lu\n", secs);
+
+ spin_lock_irqsave(&rtc_dd->alarm_ctrl_lock, irq_flags);
+ ctrl_reg = rtc_dd->alarm_ctrl_reg1;
+
+ if (ctrl_reg & BIT_RTC_ALARM_ENABLE) {
+ alarm_enabled = 1;
+ ctrl_reg &= ~BIT_RTC_ALARM_ENABLE;
+ rc = qpnp_write_wrapper(rtc_dd, &ctrl_reg,
+ rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);
+ if (rc) {
+ dev_err(dev, "Write to ALARM ctrl reg failed\n");
+ goto rtc_rw_fail;
+ }
+ } else
+ spin_unlock_irqrestore(&rtc_dd->alarm_ctrl_lock, irq_flags);
+
+ /*
+ * 32 bit seconds value is coverted to four 8 bit values
+ * |<------ 32 bit time value in seconds ------>|
+ * <- 8 bit ->|<- 8 bit ->|<- 8 bit ->|<- 8 bit ->|
+ * ----------------------------------------------
+ * | BYTE[3] | BYTE[2] | BYTE[1] | BYTE[0] |
+ * ----------------------------------------------
+ *
+ * RTC has four 8 bit registers for writing time in seconds:
+ * WDATA[3], WDATA[2], WDATA[1], WDATA[0]
+ *
+ * Write to the RTC registers should be done in following order
+ * Clear WDATA[0] register
+ *
+ * Write BYTE[1], BYTE[2] and BYTE[3] of time to
+ * RTC WDATA[3], WDATA[2], WDATA[1] registers
+ *
+ * Write BYTE[0] of time to RTC WDATA[0] register
+ *
+ * Clearing BYTE[0] and writing in the end will prevent any
+ * unintentional overflow from WDATA[0] to higher bytes during the
+ * write operation
+ */
+
+ /* Disable RTC H/w before writing on RTC register*/
+ rtc_ctrl_reg = rtc_dd->rtc_ctrl_reg;
+ if (rtc_ctrl_reg & BIT_RTC_ENABLE) {
+ rtc_disabled = 1;
+ rtc_ctrl_reg &= ~BIT_RTC_ENABLE;
+ rc = qpnp_write_wrapper(rtc_dd, &rtc_ctrl_reg,
+ rtc_dd->rtc_base + REG_OFFSET_RTC_CTRL, 1);
+ if (rc) {
+ dev_err(dev, "Disabling of RTC control reg failed with error:%d\n",
+ rc);
+ goto rtc_rw_fail;
+ }
+ rtc_dd->rtc_ctrl_reg = rtc_ctrl_reg;
+ }
+
+ /* Clear WDATA[0] */
+ reg = 0x0;
+ rc = qpnp_write_wrapper(rtc_dd, ®,
+ rtc_dd->rtc_base + REG_OFFSET_RTC_WRITE, 1);
+ if (rc) {
+ dev_err(dev, "Write to RTC reg failed\n");
+ goto rtc_rw_fail;
+ }
+
+ /* Write to WDATA[3], WDATA[2] and WDATA[1] */
+ rc = qpnp_write_wrapper(rtc_dd, &value[1],
+ rtc_dd->rtc_base + REG_OFFSET_RTC_WRITE + 1, 3);
+ if (rc) {
+ dev_err(dev, "Write to RTC reg failed\n");
+ goto rtc_rw_fail;
+ }
+
+ /* Write to WDATA[0] */
+ rc = qpnp_write_wrapper(rtc_dd, value,
+ rtc_dd->rtc_base + REG_OFFSET_RTC_WRITE, 1);
+ if (rc) {
+ dev_err(dev, "Write to RTC reg failed\n");
+ goto rtc_rw_fail;
+ }
+
+ /* Enable RTC H/w after writing on RTC register*/
+ if (rtc_disabled) {
+ rtc_ctrl_reg |= BIT_RTC_ENABLE;
+ rc = qpnp_write_wrapper(rtc_dd, &rtc_ctrl_reg,
+ rtc_dd->rtc_base + REG_OFFSET_RTC_CTRL, 1);
+ if (rc) {
+ dev_err(dev, "Enabling of RTC control reg failed with error:%d\n",
+ rc);
+ goto rtc_rw_fail;
+ }
+ rtc_dd->rtc_ctrl_reg = rtc_ctrl_reg;
+ }
+
+ if (alarm_enabled) {
+ ctrl_reg |= BIT_RTC_ALARM_ENABLE;
+ rc = qpnp_write_wrapper(rtc_dd, &ctrl_reg,
+ rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);
+ if (rc) {
+ dev_err(dev, "Write to ALARM ctrl reg failed\n");
+ goto rtc_rw_fail;
+ }
+ }
+
+ rtc_dd->alarm_ctrl_reg1 = ctrl_reg;
+
+rtc_rw_fail:
+ if (alarm_enabled)
+ spin_unlock_irqrestore(&rtc_dd->alarm_ctrl_lock, irq_flags);
+
+ return rc;
+}
+
+static int
+qpnp_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ int rc;
+ u8 value[4], reg;
+ unsigned long secs;
+ struct qpnp_rtc *rtc_dd = dev_get_drvdata(dev);
+
+ rc = qpnp_read_wrapper(rtc_dd, value,
+ rtc_dd->rtc_base + REG_OFFSET_RTC_READ,
+ NUM_8_BIT_RTC_REGS);
+ if (rc) {
+ dev_err(dev, "Read from RTC reg failed\n");
+ return rc;
+ }
+
+ /*
+ * Read the LSB again and check if there has been a carry over
+ * If there is, redo the read operation
+ */
+ rc = qpnp_read_wrapper(rtc_dd, ®,
+ rtc_dd->rtc_base + REG_OFFSET_RTC_READ, 1);
+ if (rc) {
+ dev_err(dev, "Read from RTC reg failed\n");
+ return rc;
+ }
+
+ if (reg < value[0]) {
+ rc = qpnp_read_wrapper(rtc_dd, value,
+ rtc_dd->rtc_base + REG_OFFSET_RTC_READ,
+ NUM_8_BIT_RTC_REGS);
+ if (rc) {
+ dev_err(dev, "Read from RTC reg failed\n");
+ return rc;
+ }
+ }
+
+ secs = TO_SECS(value);
+
+ rtc_time_to_tm(secs, tm);
+
+ rc = rtc_valid_tm(tm);
+ if (rc) {
+ dev_err(dev, "Invalid time read from RTC\n");
+ return rc;
+ }
+
+ dev_dbg(dev, "secs = %lu, h:m:s == %d:%d:%d, d/m/y = %d/%d/%d\n",
+ secs, tm->tm_hour, tm->tm_min, tm->tm_sec,
+ tm->tm_mday, tm->tm_mon, tm->tm_year);
+
+ return 0;
+}
+
+static int
+qpnp_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+ int rc;
+ u8 value[4], ctrl_reg;
+ unsigned long secs, secs_rtc, irq_flags;
+ struct qpnp_rtc *rtc_dd = dev_get_drvdata(dev);
+ struct rtc_time rtc_tm;
+
+ rtc_tm_to_time(&alarm->time, &secs);
+
+ /*
+ * Read the current RTC time and verify if the alarm time is in the
+ * past. If yes, return invalid
+ */
+ rc = qpnp_rtc_read_time(dev, &rtc_tm);
+ if (rc) {
+ dev_err(dev, "Unable to read RTC time\n");
+ return -EINVAL;
+ }
+
+ rtc_tm_to_time(&rtc_tm, &secs_rtc);
+ if (secs < secs_rtc) {
+ dev_err(dev, "Trying to set alarm in the past\n");
+ return -EINVAL;
+ }
+
+ value[0] = secs & 0xFF;
+ value[1] = (secs >> 8) & 0xFF;
+ value[2] = (secs >> 16) & 0xFF;
+ value[3] = (secs >> 24) & 0xFF;
+
+ spin_lock_irqsave(&rtc_dd->alarm_ctrl_lock, irq_flags);
+
+ rc = qpnp_write_wrapper(rtc_dd, value,
+ rtc_dd->alarm_base + REG_OFFSET_ALARM_RW,
+ NUM_8_BIT_RTC_REGS);
+ if (rc) {
+ dev_err(dev, "Write to ALARM reg failed\n");
+ goto rtc_rw_fail;
+ }
+
+ ctrl_reg = (alarm->enabled) ?
+ (rtc_dd->alarm_ctrl_reg1 | BIT_RTC_ALARM_ENABLE) :
+ (rtc_dd->alarm_ctrl_reg1 & ~BIT_RTC_ALARM_ENABLE);
+
+ rc = qpnp_write_wrapper(rtc_dd, &ctrl_reg,
+ rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);
+ if (rc) {
+ dev_err(dev, "Write to ALARM cntrol reg failed\n");
+ goto rtc_rw_fail;
+ }
+
+ rtc_dd->alarm_ctrl_reg1 = ctrl_reg;
+
+ dev_dbg(dev, "Alarm Set for h:r:s=%d:%d:%d, d/m/y=%d/%d/%d\n",
+ alarm->time.tm_hour, alarm->time.tm_min,
+ alarm->time.tm_sec, alarm->time.tm_mday,
+ alarm->time.tm_mon, alarm->time.tm_year);
+rtc_rw_fail:
+ spin_unlock_irqrestore(&rtc_dd->alarm_ctrl_lock, irq_flags);
+ return rc;
+}
+
+static int
+qpnp_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+ int rc;
+ u8 value[4];
+ unsigned long secs;
+ struct qpnp_rtc *rtc_dd = dev_get_drvdata(dev);
+
+ rc = qpnp_read_wrapper(rtc_dd, value,
+ rtc_dd->alarm_base + REG_OFFSET_ALARM_RW,
+ NUM_8_BIT_RTC_REGS);
+ if (rc) {
+ dev_err(dev, "Read from ALARM reg failed\n");
+ return rc;
+ }
+
+ secs = TO_SECS(value);
+ rtc_time_to_tm(secs, &alarm->time);
+
+ rc = rtc_valid_tm(&alarm->time);
+ if (rc) {
+ dev_err(dev, "Invalid time read from RTC\n");
+ return rc;
+ }
+
+ dev_dbg(dev, "Alarm set for - h:r:s=%d:%d:%d, d/m/y=%d/%d/%d\n",
+ alarm->time.tm_hour, alarm->time.tm_min,
+ alarm->time.tm_sec, alarm->time.tm_mday,
+ alarm->time.tm_mon, alarm->time.tm_year);
+
+ return 0;
+}
+
+
+static int
+qpnp_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
+{
+ int rc;
+ unsigned long irq_flags;
+ struct qpnp_rtc *rtc_dd = dev_get_drvdata(dev);
+ u8 ctrl_reg;
+ u8 value[4] = {0};
+
+ spin_lock_irqsave(&rtc_dd->alarm_ctrl_lock, irq_flags);
+ ctrl_reg = rtc_dd->alarm_ctrl_reg1;
+ ctrl_reg = enabled ? (ctrl_reg | BIT_RTC_ALARM_ENABLE) :
+ (ctrl_reg & ~BIT_RTC_ALARM_ENABLE);
+
+ rc = qpnp_write_wrapper(rtc_dd, &ctrl_reg,
+ rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);
+ if (rc) {
+ dev_err(dev, "Write to ALARM control reg failed\n");
+ goto rtc_rw_fail;
+ }
+
+ rtc_dd->alarm_ctrl_reg1 = ctrl_reg;
+
+ /* Clear Alarm register */
+ if (!enabled) {
+ rc = qpnp_write_wrapper(rtc_dd, value,
+ rtc_dd->alarm_base + REG_OFFSET_ALARM_RW,
+ NUM_8_BIT_RTC_REGS);
+ if (rc)
+ dev_err(dev, "Clear ALARM value reg failed\n");
+ }
+
+rtc_rw_fail:
+ spin_unlock_irqrestore(&rtc_dd->alarm_ctrl_lock, irq_flags);
+ return rc;
+}
+
+static const struct rtc_class_ops qpnp_rtc_ro_ops = {
+ .read_time = qpnp_rtc_read_time,
+ .set_alarm = qpnp_rtc_set_alarm,
+ .read_alarm = qpnp_rtc_read_alarm,
+ .alarm_irq_enable = qpnp_rtc_alarm_irq_enable,
+};
+
+static const struct rtc_class_ops qpnp_rtc_rw_ops = {
+ .read_time = qpnp_rtc_read_time,
+ .set_alarm = qpnp_rtc_set_alarm,
+ .read_alarm = qpnp_rtc_read_alarm,
+ .alarm_irq_enable = qpnp_rtc_alarm_irq_enable,
+ .set_time = qpnp_rtc_set_time,
+};
+
+static irqreturn_t qpnp_alarm_trigger(int irq, void *dev_id)
+{
+ struct qpnp_rtc *rtc_dd = dev_id;
+ u8 ctrl_reg;
+ int rc;
+ unsigned long irq_flags;
+
+ rtc_update_irq(rtc_dd->rtc, 1, RTC_IRQF | RTC_AF);
+
+ spin_lock_irqsave(&rtc_dd->alarm_ctrl_lock, irq_flags);
+
+ /* Clear the alarm enable bit */
+ ctrl_reg = rtc_dd->alarm_ctrl_reg1;
+ ctrl_reg &= ~BIT_RTC_ALARM_ENABLE;
+
+ rc = qpnp_write_wrapper(rtc_dd, &ctrl_reg,
+ rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);
+ if (rc) {
+ spin_unlock_irqrestore(&rtc_dd->alarm_ctrl_lock, irq_flags);
+ dev_err(rtc_dd->rtc_dev,
+ "Write to ALARM control reg failed\n");
+ goto rtc_alarm_handled;
+ }
+
+ rtc_dd->alarm_ctrl_reg1 = ctrl_reg;
+ spin_unlock_irqrestore(&rtc_dd->alarm_ctrl_lock, irq_flags);
+
+ /* Set ALARM_CLR bit */
+ ctrl_reg = 0x1;
+ rc = qpnp_write_wrapper(rtc_dd, &ctrl_reg,
+ rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL2, 1);
+ if (rc)
+ dev_err(rtc_dd->rtc_dev,
+ "Write to ALARM control reg failed\n");
+
+rtc_alarm_handled:
+ return IRQ_HANDLED;
+}
+
+static int qpnp_rtc_probe(struct platform_device *pdev)
+{
+ const struct rtc_class_ops *rtc_ops = &qpnp_rtc_ro_ops;
+ int rc;
+ u8 subtype;
+ struct qpnp_rtc *rtc_dd;
+ unsigned int base;
+ struct device_node *child;
+
+ rtc_dd = devm_kzalloc(&pdev->dev, sizeof(*rtc_dd), GFP_KERNEL);
+ if (rtc_dd == NULL)
+ return -ENOMEM;
+
+ rtc_dd->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!rtc_dd->regmap) {
+ dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+ return -EINVAL;
+ }
+
+ /* Get the rtc write property */
+ rc = of_property_read_u32(pdev->dev.of_node, "qcom,qpnp-rtc-write",
+ &rtc_dd->rtc_write_enable);
+ if (rc && rc != -EINVAL) {
+ dev_err(&pdev->dev,
+ "Error reading rtc_write_enable property %d\n", rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(pdev->dev.of_node,
+ "qcom,qpnp-rtc-alarm-pwrup",
+ &rtc_dd->rtc_alarm_powerup);
+ if (rc && rc != -EINVAL) {
+ dev_err(&pdev->dev,
+ "Error reading rtc_alarm_powerup property %d\n", rc);
+ return rc;
+ }
+
+ /* Initialise spinlock to protect RTC control register */
+ spin_lock_init(&rtc_dd->alarm_ctrl_lock);
+
+ rtc_dd->rtc_dev = &(pdev->dev);
+ rtc_dd->pdev = pdev;
+
+
+ if (of_get_available_child_count(pdev->dev.of_node) == 0) {
+ pr_err("no child nodes\n");
+ rc = -ENXIO;
+ goto fail_rtc_enable;
+ }
+
+ /* Get RTC/ALARM resources */
+ for_each_available_child_of_node(pdev->dev.of_node, child) {
+ rc = of_property_read_u32(child, "reg", &base);
+ if (rc < 0) {
+ dev_err(&pdev->dev,
+ "Couldn't find reg in node = %s rc = %d\n",
+ child->full_name, rc);
+ goto fail_rtc_enable;
+ }
+
+ rc = qpnp_read_wrapper(rtc_dd, &subtype,
+ base + REG_OFFSET_PERP_SUBTYPE, 1);
+ if (rc) {
+ dev_err(&pdev->dev,
+ "Peripheral subtype read failed\n");
+ goto fail_rtc_enable;
+ }
+
+ switch (subtype) {
+ case RTC_PERPH_SUBTYPE:
+ rtc_dd->rtc_base = base;
+ break;
+ case ALARM_PERPH_SUBTYPE:
+ rtc_dd->alarm_base = base;
+ rtc_dd->rtc_alarm_irq = of_irq_get(child, 0);
+ if (rtc_dd->rtc_alarm_irq < 0) {
+ dev_err(&pdev->dev, "ALARM IRQ absent\n");
+ rc = -ENXIO;
+ goto fail_rtc_enable;
+ }
+ break;
+ default:
+ dev_err(&pdev->dev, "Invalid peripheral subtype\n");
+ rc = -EINVAL;
+ goto fail_rtc_enable;
+ }
+ }
+
+ rc = qpnp_read_wrapper(rtc_dd, &rtc_dd->rtc_ctrl_reg,
+ rtc_dd->rtc_base + REG_OFFSET_RTC_CTRL, 1);
+ if (rc) {
+ dev_err(&pdev->dev, "Read from RTC control reg failed\n");
+ goto fail_rtc_enable;
+ }
+
+ if (!(rtc_dd->rtc_ctrl_reg & BIT_RTC_ENABLE)) {
+ dev_err(&pdev->dev, "RTC h/w disabled, rtc not registered\n");
+ goto fail_rtc_enable;
+ }
+
+ rc = qpnp_read_wrapper(rtc_dd, &rtc_dd->alarm_ctrl_reg1,
+ rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);
+ if (rc) {
+ dev_err(&pdev->dev, "Read from Alarm control reg failed\n");
+ goto fail_rtc_enable;
+ }
+ /* Enable abort enable feature */
+ rtc_dd->alarm_ctrl_reg1 |= BIT_RTC_ABORT_ENABLE;
+ rc = qpnp_write_wrapper(rtc_dd, &rtc_dd->alarm_ctrl_reg1,
+ rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);
+ if (rc) {
+ dev_err(&pdev->dev, "SPMI write failed!\n");
+ goto fail_rtc_enable;
+ }
+
+ if (rtc_dd->rtc_write_enable == true)
+ rtc_ops = &qpnp_rtc_rw_ops;
+
+ dev_set_drvdata(&pdev->dev, rtc_dd);
+
+ /* Register the RTC device */
+ rtc_dd->rtc = rtc_device_register("qpnp_rtc", &pdev->dev,
+ rtc_ops, THIS_MODULE);
+ if (IS_ERR(rtc_dd->rtc)) {
+ dev_err(&pdev->dev, "%s: RTC registration failed (%ld)\n",
+ __func__, PTR_ERR(rtc_dd->rtc));
+ rc = PTR_ERR(rtc_dd->rtc);
+ goto fail_rtc_enable;
+ }
+
+ /* Init power_on_alarm after adding rtc device */
+ power_on_alarm_init();
+
+ /* Request the alarm IRQ */
+ rc = request_any_context_irq(rtc_dd->rtc_alarm_irq,
+ qpnp_alarm_trigger, IRQF_TRIGGER_RISING,
+ "qpnp_rtc_alarm", rtc_dd);
+ if (rc) {
+ dev_err(&pdev->dev, "Request IRQ failed (%d)\n", rc);
+ goto fail_req_irq;
+ }
+
+ device_init_wakeup(&pdev->dev, 1);
+ enable_irq_wake(rtc_dd->rtc_alarm_irq);
+
+ dev_dbg(&pdev->dev, "Probe success !!\n");
+
+ return 0;
+
+fail_req_irq:
+ rtc_device_unregister(rtc_dd->rtc);
+fail_rtc_enable:
+ dev_set_drvdata(&pdev->dev, NULL);
+
+ return rc;
+}
+
+static int qpnp_rtc_remove(struct platform_device *pdev)
+{
+ struct qpnp_rtc *rtc_dd = dev_get_drvdata(&pdev->dev);
+
+ device_init_wakeup(&pdev->dev, 0);
+ free_irq(rtc_dd->rtc_alarm_irq, rtc_dd);
+ rtc_device_unregister(rtc_dd->rtc);
+ dev_set_drvdata(&pdev->dev, NULL);
+
+ return 0;
+}
+
+static void qpnp_rtc_shutdown(struct platform_device *pdev)
+{
+ u8 value[4] = {0};
+ u8 reg;
+ int rc;
+ unsigned long irq_flags;
+ struct qpnp_rtc *rtc_dd;
+ bool rtc_alarm_powerup;
+
+ if (!pdev) {
+ pr_err("qpnp-rtc: spmi device not found\n");
+ return;
+ }
+ rtc_dd = dev_get_drvdata(&pdev->dev);
+ if (!rtc_dd) {
+ pr_err("qpnp-rtc: rtc driver data not found\n");
+ return;
+ }
+ rtc_alarm_powerup = rtc_dd->rtc_alarm_powerup;
+ if (!rtc_alarm_powerup && !poweron_alarm) {
+ spin_lock_irqsave(&rtc_dd->alarm_ctrl_lock, irq_flags);
+ dev_dbg(&pdev->dev, "Disabling alarm interrupts\n");
+
+ /* Disable RTC alarms */
+ reg = rtc_dd->alarm_ctrl_reg1;
+ reg &= ~BIT_RTC_ALARM_ENABLE;
+ rc = qpnp_write_wrapper(rtc_dd, ®,
+ rtc_dd->alarm_base + REG_OFFSET_ALARM_CTRL1, 1);
+ if (rc) {
+ dev_err(rtc_dd->rtc_dev, "SPMI write failed\n");
+ goto fail_alarm_disable;
+ }
+
+ /* Clear Alarm register */
+ rc = qpnp_write_wrapper(rtc_dd, value,
+ rtc_dd->alarm_base + REG_OFFSET_ALARM_RW,
+ NUM_8_BIT_RTC_REGS);
+ if (rc)
+ dev_err(rtc_dd->rtc_dev, "SPMI write failed\n");
+
+fail_alarm_disable:
+ spin_unlock_irqrestore(&rtc_dd->alarm_ctrl_lock, irq_flags);
+ }
+}
+
+static const struct of_device_id spmi_match_table[] = {
+ {
+ .compatible = "qcom,qpnp-rtc",
+ },
+ {}
+};
+
+static struct platform_driver qpnp_rtc_driver = {
+ .probe = qpnp_rtc_probe,
+ .remove = qpnp_rtc_remove,
+ .shutdown = qpnp_rtc_shutdown,
+ .driver = {
+ .name = "qcom,qpnp-rtc",
+ .owner = THIS_MODULE,
+ .of_match_table = spmi_match_table,
+ },
+};
+
+static int __init qpnp_rtc_init(void)
+{
+ return platform_driver_register(&qpnp_rtc_driver);
+}
+module_init(qpnp_rtc_init);
+
+static void __exit qpnp_rtc_exit(void)
+{
+ platform_driver_unregister(&qpnp_rtc_driver);
+}
+module_exit(qpnp_rtc_exit);
+
+MODULE_DESCRIPTION("SMPI PMIC RTC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index a13541b..68b3b24 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -419,6 +419,20 @@
real time die temperature if an ADC is present or an estimate of the
temperature based upon the over temperature stage value.
+config THERMAL_QPNP
+ tristate "Qualcomm Technologies, Inc. QPNP PMIC Temperature Alarm"
+ depends on OF && SPMI
+ help
+ This enables a thermal Sysfs driver for Qualcomm Technologies, Inc.
+ QPNP PMIC devices. It shows up in Sysfs as a thermal zone with
+ multiple trip points. The temperature reported by the thermal zone
+ reflects the real time die temperature if an ADC is present or an
+ estimate of the temperature based upon the over temperature stage
+ value if no ADC is available. If allowed via compile time
+ configuration; enabling the thermal zone device via the mode file
+ results in shifting PMIC over temperature shutdown control from
+ hardware to software.
+
config GENERIC_ADC_THERMAL
tristate "Generic ADC based thermal sensor"
depends on IIO
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index c92eb22..7d3b5312 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -27,6 +27,7 @@
# platform thermal drivers
obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o
+obj-$(CONFIG_THERMAL_QPNP) += qpnp-temp-alarm.o
obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o
obj-$(CONFIG_ROCKCHIP_THERMAL) += rockchip_thermal.o
obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o
diff --git a/drivers/thermal/qpnp-temp-alarm.c b/drivers/thermal/qpnp-temp-alarm.c
new file mode 100644
index 0000000..e86a297
--- /dev/null
+++ b/drivers/thermal/qpnp-temp-alarm.c
@@ -0,0 +1,813 @@
+/*
+ * Copyright (c) 2011-2017, The Linux Foundation. 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.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/string.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/thermal.h>
+#include <linux/qpnp/qpnp-adc.h>
+
+#define QPNP_TM_DRIVER_NAME "qcom,qpnp-temp-alarm"
+
+enum qpnp_tm_registers {
+ QPNP_TM_REG_TYPE = 0x04,
+ QPNP_TM_REG_SUBTYPE = 0x05,
+ QPNP_TM_REG_STATUS = 0x08,
+ QPNP_TM_REG_SHUTDOWN_CTRL1 = 0x40,
+ QPNP_TM_REG_SHUTDOWN_CTRL2 = 0x42,
+ QPNP_TM_REG_ALARM_CTRL = 0x46,
+};
+
+#define QPNP_TM_TYPE 0x09
+#define QPNP_TM_SUBTYPE_GEN1 0x08
+#define QPNP_TM_SUBTYPE_GEN2 0x09
+
+#define STATUS_STATE_MASK 0x70
+#define STATUS_STATE_SHIFT 4
+#define STATUS_STAGE_MASK 0x03
+
+#define SHUTDOWN_CTRL1_OVERRIDE_STAGE3 0x80
+#define SHUTDOWN_CTRL1_OVERRIDE_STAGE2 0x40
+#define SHUTDOWN_CTRL1_CLK_RATE_MASK 0x0C
+#define SHUTDOWN_CTRL1_CLK_RATE_SHIFT 2
+#define SHUTDOWN_CTRL1_THRESHOLD_MASK 0x03
+
+#define SHUTDOWN_CTRL2_CLEAR_STAGE3 0x80
+#define SHUTDOWN_CTRL2_CLEAR_STAGE2 0x40
+
+#define ALARM_CTRL_FORCE_ENABLE 0x80
+#define ALARM_CTRL_FOLLOW_HW_ENABLE 0x01
+
+#define TEMP_STAGE_STEP 20000 /* Stage step: 20.000 C */
+#define TEMP_STAGE_HYSTERESIS 2000
+
+#define TEMP_THRESH_MIN 105000 /* Threshold Min: 105 C */
+#define TEMP_THRESH_STEP 5000 /* Threshold step: 5 C */
+
+#define THRESH_MIN 0
+#define THRESH_MAX 3
+
+#define CLOCK_RATE_MIN 0
+#define CLOCK_RATE_MAX 3
+
+/* Trip points from most critical to least critical */
+#define TRIP_STAGE3 0
+#define TRIP_STAGE2 1
+#define TRIP_STAGE1 2
+#define TRIP_NUM 3
+
+enum qpnp_tm_adc_type {
+ QPNP_TM_ADC_NONE, /* Estimates temp based on overload level. */
+ QPNP_TM_ADC_QPNP_ADC,
+};
+
+/*
+ * Temperature in millicelcius reported during stage 0 if no ADC is present and
+ * no value has been specified via device tree.
+ */
+#define DEFAULT_NO_ADC_TEMP 37000
+
+struct qpnp_tm_chip {
+ struct delayed_work irq_work;
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ struct thermal_zone_device *tz_dev;
+ const char *tm_name;
+ unsigned int subtype;
+ enum qpnp_tm_adc_type adc_type;
+ int temperature;
+ enum thermal_device_mode mode;
+ unsigned int thresh;
+ unsigned int clock_rate;
+ unsigned int stage;
+ unsigned int prev_stage;
+ int irq;
+ enum qpnp_vadc_channels adc_channel;
+ u16 base_addr;
+ bool allow_software_override;
+ struct qpnp_vadc_chip *vadc_dev;
+};
+
+/* Delay between TEMP_STAT IRQ going high and status value changing in ms. */
+#define STATUS_REGISTER_DELAY_MS 40
+
+enum pmic_thermal_override_mode {
+ SOFTWARE_OVERRIDE_DISABLED = 0,
+ SOFTWARE_OVERRIDE_ENABLED,
+};
+
+/* This array maps from GEN2 alarm state to GEN1 alarm stage */
+const unsigned int alarm_state_map[8] = {0, 1, 1, 2, 2, 3, 3, 3};
+
+static inline int qpnp_tm_read(struct qpnp_tm_chip *chip, u16 addr, u8 *buf,
+ int len)
+{
+ int rc;
+
+ rc = regmap_bulk_read(chip->regmap, chip->base_addr + addr, buf, len);
+
+ if (rc)
+ dev_err(&chip->pdev->dev,
+ "%s: regmap_bulk_readl failed. sid=%d, addr=%04X, len=%d, rc=%d\n",
+ __func__,
+ to_spmi_device(chip->pdev->dev.parent)->usid,
+ chip->base_addr + addr,
+ len, rc);
+
+ return rc;
+}
+
+static inline int qpnp_tm_write(struct qpnp_tm_chip *chip, u16 addr, u8 *buf,
+ int len)
+{
+ int rc;
+
+ rc = regmap_bulk_write(chip->regmap, chip->base_addr + addr, buf, len);
+
+ if (rc)
+ dev_err(&chip->pdev->dev,
+ "%s: regmap_bulk_write failed. sid=%d, addr=%04X, len=%d, rc=%d\n",
+ __func__,
+ to_spmi_device(chip->pdev->dev.parent)->usid,
+ chip->base_addr + addr,
+ len, rc);
+
+ return rc;
+}
+
+
+static inline int qpnp_tm_shutdown_override(struct qpnp_tm_chip *chip,
+ enum pmic_thermal_override_mode mode)
+{
+ int rc = 0;
+ u8 reg;
+
+ if (chip->allow_software_override) {
+ reg = chip->thresh & SHUTDOWN_CTRL1_THRESHOLD_MASK;
+ reg |= (chip->clock_rate << SHUTDOWN_CTRL1_CLK_RATE_SHIFT)
+ & SHUTDOWN_CTRL1_CLK_RATE_MASK;
+
+ if (mode == SOFTWARE_OVERRIDE_ENABLED)
+ reg |= SHUTDOWN_CTRL1_OVERRIDE_STAGE2
+ | SHUTDOWN_CTRL1_OVERRIDE_STAGE3;
+
+ rc = qpnp_tm_write(chip, QPNP_TM_REG_SHUTDOWN_CTRL1, ®, 1);
+ }
+
+ return rc;
+}
+
+static int qpnp_tm_update_temp(struct qpnp_tm_chip *chip)
+{
+ struct qpnp_vadc_result adc_result;
+ int rc;
+
+ rc = qpnp_vadc_read(chip->vadc_dev, chip->adc_channel, &adc_result);
+ if (!rc)
+ chip->temperature = adc_result.physical;
+ else
+ dev_err(&chip->pdev->dev,
+ "%s: qpnp_vadc_read(%d) failed, rc=%d\n",
+ __func__, chip->adc_channel, rc);
+
+ return rc;
+}
+
+static int qpnp_tm_get_temp_stage(struct qpnp_tm_chip *chip,
+ unsigned int *stage)
+{
+ int rc;
+ u8 reg;
+
+ rc = qpnp_tm_read(chip, QPNP_TM_REG_STATUS, ®, 1);
+ if (rc < 0)
+ return rc;
+
+ if (chip->subtype == QPNP_TM_SUBTYPE_GEN1)
+ *stage = reg & STATUS_STAGE_MASK;
+ else
+ *stage = (reg & STATUS_STATE_MASK) >> STATUS_STATE_SHIFT;
+
+ return 0;
+}
+
+/*
+ * This function initializes the internal temperature value based on only the
+ * current thermal stage and threshold.
+ */
+static int qpnp_tm_init_temp_no_adc(struct qpnp_tm_chip *chip)
+{
+ unsigned int stage;
+ int rc;
+
+ rc = qpnp_tm_get_temp_stage(chip, &chip->stage);
+ if (rc < 0)
+ return rc;
+
+ stage = chip->subtype == QPNP_TM_SUBTYPE_GEN1
+ ? chip->stage : alarm_state_map[chip->stage];
+
+ if (stage)
+ chip->temperature = chip->thresh * TEMP_THRESH_STEP +
+ (stage - 1) * TEMP_STAGE_STEP +
+ TEMP_THRESH_MIN;
+
+ return 0;
+}
+
+/*
+ * This function updates the internal temperature value based on the
+ * current thermal stage and threshold as well as the previous stage
+ */
+static int qpnp_tm_update_temp_no_adc(struct qpnp_tm_chip *chip)
+{
+ unsigned int stage, stage_new, stage_old;
+ int rc;
+
+ rc = qpnp_tm_get_temp_stage(chip, &stage);
+ if (rc < 0)
+ return rc;
+
+ if (chip->subtype == QPNP_TM_SUBTYPE_GEN1) {
+ stage_new = stage;
+ stage_old = chip->stage;
+ } else {
+ stage_new = alarm_state_map[stage];
+ stage_old = alarm_state_map[chip->stage];
+ }
+
+ if (stage_new > stage_old) {
+ /* increasing stage, use lower bound */
+ chip->temperature = (stage_new - 1) * TEMP_STAGE_STEP
+ + chip->thresh * TEMP_THRESH_STEP
+ + TEMP_STAGE_HYSTERESIS + TEMP_THRESH_MIN;
+ } else if (stage_new < stage_old) {
+ /* decreasing stage, use upper bound */
+ chip->temperature = stage_new * TEMP_STAGE_STEP
+ + chip->thresh * TEMP_THRESH_STEP
+ - TEMP_STAGE_HYSTERESIS + TEMP_THRESH_MIN;
+ }
+
+ chip->stage = stage;
+
+ return 0;
+}
+
+static int qpnp_tz_get_temp_no_adc(struct thermal_zone_device *thermal,
+ int *temperature)
+{
+ struct qpnp_tm_chip *chip = thermal->devdata;
+ int rc;
+
+ if (!temperature)
+ return -EINVAL;
+
+ rc = qpnp_tm_update_temp_no_adc(chip);
+ if (rc < 0)
+ return rc;
+
+ *temperature = chip->temperature;
+
+ return 0;
+}
+
+static int qpnp_tz_get_temp_qpnp_adc(struct thermal_zone_device *thermal,
+ int *temperature)
+{
+ struct qpnp_tm_chip *chip = thermal->devdata;
+ int rc;
+
+ if (!temperature)
+ return -EINVAL;
+
+ rc = qpnp_tm_update_temp(chip);
+ if (rc < 0) {
+ dev_err(&chip->pdev->dev,
+ "%s: %s: adc read failed, rc = %d\n",
+ __func__, chip->tm_name, rc);
+ return rc;
+ }
+
+ *temperature = chip->temperature;
+
+ return 0;
+}
+
+static int qpnp_tz_get_mode(struct thermal_zone_device *thermal,
+ enum thermal_device_mode *mode)
+{
+ struct qpnp_tm_chip *chip = thermal->devdata;
+
+ if (!mode)
+ return -EINVAL;
+
+ *mode = chip->mode;
+
+ return 0;
+}
+
+static int qpnp_tz_set_mode(struct thermal_zone_device *thermal,
+ enum thermal_device_mode mode)
+{
+ struct qpnp_tm_chip *chip = thermal->devdata;
+ int rc = 0;
+
+ if (mode != chip->mode) {
+ if (mode == THERMAL_DEVICE_ENABLED)
+ rc = qpnp_tm_shutdown_override(chip,
+ SOFTWARE_OVERRIDE_ENABLED);
+ else
+ rc = qpnp_tm_shutdown_override(chip,
+ SOFTWARE_OVERRIDE_DISABLED);
+
+ chip->mode = mode;
+ }
+
+ return rc;
+}
+
+static int qpnp_tz_get_trip_type(struct thermal_zone_device *thermal,
+ int trip, enum thermal_trip_type *type)
+{
+ if (trip < 0 || !type)
+ return -EINVAL;
+
+ switch (trip) {
+ case TRIP_STAGE3:
+ *type = THERMAL_TRIP_CRITICAL;
+ break;
+ case TRIP_STAGE2:
+ *type = THERMAL_TRIP_HOT;
+ break;
+ case TRIP_STAGE1:
+ *type = THERMAL_TRIP_HOT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int qpnp_tz_get_trip_temp(struct thermal_zone_device *thermal,
+ int trip, int *temperature)
+{
+ struct qpnp_tm_chip *chip = thermal->devdata;
+ int thresh_temperature;
+
+ if (trip < 0 || !temperature)
+ return -EINVAL;
+
+ thresh_temperature = chip->thresh * TEMP_THRESH_STEP + TEMP_THRESH_MIN;
+
+ switch (trip) {
+ case TRIP_STAGE3:
+ thresh_temperature += 2 * TEMP_STAGE_STEP;
+ break;
+ case TRIP_STAGE2:
+ thresh_temperature += TEMP_STAGE_STEP;
+ break;
+ case TRIP_STAGE1:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ *temperature = thresh_temperature;
+
+ return 0;
+}
+
+static int qpnp_tz_get_crit_temp(struct thermal_zone_device *thermal,
+ int *temperature)
+{
+ struct qpnp_tm_chip *chip = thermal->devdata;
+
+ if (!temperature)
+ return -EINVAL;
+
+ *temperature = chip->thresh * TEMP_THRESH_STEP + TEMP_THRESH_MIN +
+ 2 * TEMP_STAGE_STEP;
+
+ return 0;
+}
+
+static struct thermal_zone_device_ops qpnp_thermal_zone_ops_no_adc = {
+ .get_temp = qpnp_tz_get_temp_no_adc,
+ .get_mode = qpnp_tz_get_mode,
+ .set_mode = qpnp_tz_set_mode,
+ .get_trip_type = qpnp_tz_get_trip_type,
+ .get_trip_temp = qpnp_tz_get_trip_temp,
+ .get_crit_temp = qpnp_tz_get_crit_temp,
+};
+
+static struct thermal_zone_device_ops qpnp_thermal_zone_ops_qpnp_adc = {
+ .get_temp = qpnp_tz_get_temp_qpnp_adc,
+ .get_mode = qpnp_tz_get_mode,
+ .set_mode = qpnp_tz_set_mode,
+ .get_trip_type = qpnp_tz_get_trip_type,
+ .get_trip_temp = qpnp_tz_get_trip_temp,
+ .get_crit_temp = qpnp_tz_get_crit_temp,
+};
+
+static void qpnp_tm_work(struct work_struct *work)
+{
+ struct delayed_work *dwork
+ = container_of(work, struct delayed_work, work);
+ struct qpnp_tm_chip *chip
+ = container_of(dwork, struct qpnp_tm_chip, irq_work);
+ unsigned int stage_new, stage_old;
+ int rc;
+
+ if (chip->adc_type == QPNP_TM_ADC_NONE) {
+ rc = qpnp_tm_update_temp_no_adc(chip);
+ if (rc < 0)
+ goto bail;
+ } else {
+ rc = qpnp_tm_get_temp_stage(chip, &chip->stage);
+ if (rc < 0)
+ goto bail;
+
+ rc = qpnp_tm_update_temp(chip);
+ if (rc < 0)
+ goto bail;
+ }
+
+ if (chip->subtype == QPNP_TM_SUBTYPE_GEN1) {
+ stage_new = chip->stage;
+ stage_old = chip->prev_stage;
+ } else {
+ stage_new = alarm_state_map[chip->stage];
+ stage_old = alarm_state_map[chip->prev_stage];
+ }
+
+ chip->prev_stage = chip->stage;
+
+ if (stage_new != stage_old) {
+ if (chip->subtype == QPNP_TM_SUBTYPE_GEN1)
+ pr_crit("%s: PMIC Temp Alarm - stage=%u, threshold=%u, temperature=%d mC\n",
+ chip->tm_name, chip->stage, chip->thresh,
+ chip->temperature);
+ else
+ pr_crit("%s: PMIC Temp Alarm - stage=%u, state=%u, threshold=%u, temperature=%d mC\n",
+ chip->tm_name, stage_new, chip->stage,
+ chip->thresh, chip->temperature);
+
+ thermal_zone_device_update(chip->tz_dev,
+ THERMAL_EVENT_UNSPECIFIED);
+
+ /* Notify user space */
+ sysfs_notify(&chip->tz_dev->device.kobj, NULL, "type");
+ }
+
+bail:
+ return;
+}
+
+static irqreturn_t qpnp_tm_isr(int irq, void *data)
+{
+ struct qpnp_tm_chip *chip = data;
+
+ schedule_delayed_work(&chip->irq_work,
+ msecs_to_jiffies(STATUS_REGISTER_DELAY_MS) + 1);
+
+ return IRQ_HANDLED;
+}
+
+static int qpnp_tm_init_reg(struct qpnp_tm_chip *chip)
+{
+ int rc = 0;
+ u8 reg;
+
+ rc = qpnp_tm_read(chip, QPNP_TM_REG_SHUTDOWN_CTRL1, ®, 1);
+ if (rc < 0)
+ return rc;
+
+ if (chip->thresh < THRESH_MIN || chip->thresh > THRESH_MAX) {
+ /* Use hardware threshold value if configuration is invalid. */
+ chip->thresh = reg & SHUTDOWN_CTRL1_THRESHOLD_MASK;
+ }
+
+ if (chip->clock_rate < CLOCK_RATE_MIN
+ || chip->clock_rate > CLOCK_RATE_MAX) {
+ /* Use hardware clock rate value if configuration is invalid. */
+ chip->clock_rate = (reg & SHUTDOWN_CTRL1_CLK_RATE_MASK)
+ >> SHUTDOWN_CTRL1_CLK_RATE_SHIFT;
+ }
+
+ /*
+ * Set threshold and clock rate and also disable software override of
+ * stage 2 and 3 shutdowns.
+ */
+ reg = chip->thresh & SHUTDOWN_CTRL1_THRESHOLD_MASK;
+ reg |= (chip->clock_rate << SHUTDOWN_CTRL1_CLK_RATE_SHIFT)
+ & SHUTDOWN_CTRL1_CLK_RATE_MASK;
+ rc = qpnp_tm_write(chip, QPNP_TM_REG_SHUTDOWN_CTRL1, ®, 1);
+ if (rc < 0)
+ return rc;
+
+ /* Enable the thermal alarm PMIC module in always-on mode. */
+ reg = ALARM_CTRL_FORCE_ENABLE;
+ rc = qpnp_tm_write(chip, QPNP_TM_REG_ALARM_CTRL, ®, 1);
+
+ return rc;
+}
+
+static int qpnp_tm_probe(struct platform_device *pdev)
+{
+ struct device_node *node;
+ unsigned int base;
+ struct qpnp_tm_chip *chip;
+ struct thermal_zone_device_ops *tz_ops;
+ char *tm_name;
+ u32 default_temperature;
+ int rc = 0;
+ u8 raw_type[2], type, subtype;
+
+ if (!pdev || !(&pdev->dev) || !pdev->dev.of_node) {
+ dev_err(&pdev->dev, "%s: device tree node not found\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ node = pdev->dev.of_node;
+
+ chip = kzalloc(sizeof(struct qpnp_tm_chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!chip->regmap) {
+ dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+ return -EINVAL;
+ }
+
+ dev_set_drvdata(&pdev->dev, chip);
+
+ rc = of_property_read_u32(pdev->dev.of_node, "reg", &base);
+ if (rc < 0) {
+ dev_err(&pdev->dev,
+ "Couldn't find reg in node = %s rc = %d\n",
+ pdev->dev.of_node->full_name, rc);
+ goto free_chip;
+ }
+ chip->base_addr = base;
+ chip->pdev = pdev;
+
+ chip->irq = platform_get_irq(pdev, 0);
+ if (chip->irq < 0) {
+ rc = chip->irq;
+ dev_err(&pdev->dev, "%s: node is missing irq, rc=%d\n",
+ __func__, rc);
+ goto free_chip;
+ }
+
+ chip->tm_name = of_get_property(node, "label", NULL);
+ if (chip->tm_name == NULL) {
+ dev_err(&pdev->dev, "%s: node is missing label\n", __func__);
+ rc = -EINVAL;
+ goto free_chip;
+ }
+
+ tm_name = kstrdup(chip->tm_name, GFP_KERNEL);
+ if (tm_name == NULL) {
+ rc = -ENOMEM;
+ goto free_chip;
+ }
+ chip->tm_name = tm_name;
+
+ INIT_DELAYED_WORK(&chip->irq_work, qpnp_tm_work);
+
+ /* These bindings are optional, so it is okay if they are not found. */
+ chip->thresh = THRESH_MAX + 1;
+ rc = of_property_read_u32(node, "qcom,threshold-set", &chip->thresh);
+ if (!rc && (chip->thresh < THRESH_MIN || chip->thresh > THRESH_MAX))
+ dev_err(&pdev->dev,
+ "%s: invalid qcom,threshold-set=%u specified\n",
+ __func__, chip->thresh);
+
+ chip->clock_rate = CLOCK_RATE_MAX + 1;
+ rc = of_property_read_u32(node, "qcom,clock-rate", &chip->clock_rate);
+ if (!rc && (chip->clock_rate < CLOCK_RATE_MIN
+ || chip->clock_rate > CLOCK_RATE_MAX))
+ dev_err(&pdev->dev,
+ "%s: invalid qcom,clock-rate=%u specified\n", __func__,
+ chip->clock_rate);
+
+ chip->adc_type = QPNP_TM_ADC_NONE;
+ rc = of_property_read_u32(node, "qcom,channel-num", &chip->adc_channel);
+ if (!rc) {
+ if (chip->adc_channel < 0 || chip->adc_channel >= ADC_MAX_NUM) {
+ dev_err(&pdev->dev,
+ "%s: invalid qcom,channel-num=%d specified\n",
+ __func__, chip->adc_channel);
+ } else {
+ chip->adc_type = QPNP_TM_ADC_QPNP_ADC;
+ chip->vadc_dev = qpnp_get_vadc(&pdev->dev,
+ "temp_alarm");
+ if (IS_ERR(chip->vadc_dev)) {
+ rc = PTR_ERR(chip->vadc_dev);
+ if (rc != -EPROBE_DEFER)
+ pr_err("vadc property missing\n");
+ goto err_cancel_work;
+ }
+ }
+ }
+
+ if (chip->adc_type == QPNP_TM_ADC_QPNP_ADC)
+ tz_ops = &qpnp_thermal_zone_ops_qpnp_adc;
+ else
+ tz_ops = &qpnp_thermal_zone_ops_no_adc;
+
+ chip->allow_software_override
+ = of_property_read_bool(node, "qcom,allow-override");
+
+ default_temperature = DEFAULT_NO_ADC_TEMP;
+ rc = of_property_read_u32(node, "qcom,default-temp",
+ &default_temperature);
+ chip->temperature = default_temperature;
+
+ rc = qpnp_tm_read(chip, QPNP_TM_REG_TYPE, raw_type, 2);
+ if (rc) {
+ dev_err(&pdev->dev,
+ "%s: could not read type register, rc=%d\n",
+ __func__, rc);
+ goto err_cancel_work;
+ }
+ type = raw_type[0];
+ subtype = raw_type[1];
+
+ if (type != QPNP_TM_TYPE || (subtype != QPNP_TM_SUBTYPE_GEN1
+ && subtype != QPNP_TM_SUBTYPE_GEN2)) {
+ dev_err(&pdev->dev,
+ "%s: invalid type=%02X or subtype=%02X register value\n",
+ __func__, type, subtype);
+ rc = -ENODEV;
+ goto err_cancel_work;
+ }
+
+ chip->subtype = subtype;
+
+ rc = qpnp_tm_init_reg(chip);
+ if (rc) {
+ dev_err(&pdev->dev, "%s: qpnp_tm_init_reg() failed, rc=%d\n",
+ __func__, rc);
+ goto err_cancel_work;
+ }
+
+ if (chip->adc_type == QPNP_TM_ADC_NONE) {
+ rc = qpnp_tm_init_temp_no_adc(chip);
+ if (rc) {
+ dev_err(&pdev->dev,
+ "%s: qpnp_tm_init_temp_no_adc() failed, rc=%d\n",
+ __func__, rc);
+ goto err_cancel_work;
+ }
+ }
+
+ /* Start in HW control; switch to SW control when user changes mode. */
+ chip->mode = THERMAL_DEVICE_DISABLED;
+ rc = qpnp_tm_shutdown_override(chip, SOFTWARE_OVERRIDE_DISABLED);
+ if (rc) {
+ dev_err(&pdev->dev,
+ "%s: qpnp_tm_shutdown_override() failed, rc=%d\n",
+ __func__, rc);
+ goto err_cancel_work;
+ }
+
+ chip->tz_dev = thermal_zone_device_register(tm_name, TRIP_NUM, 0, chip,
+ tz_ops, NULL, 0, 0);
+ if (chip->tz_dev == NULL) {
+ dev_err(&pdev->dev,
+ "%s: thermal_zone_device_register() failed.\n",
+ __func__);
+ rc = -ENODEV;
+ goto err_cancel_work;
+ }
+
+ rc = request_irq(chip->irq, qpnp_tm_isr, IRQF_TRIGGER_RISING, tm_name,
+ chip);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "%s: request_irq(%d) failed: %d\n",
+ __func__, chip->irq, rc);
+ goto err_free_tz;
+ }
+
+ return 0;
+
+err_free_tz:
+ thermal_zone_device_unregister(chip->tz_dev);
+err_cancel_work:
+ cancel_delayed_work_sync(&chip->irq_work);
+ kfree(chip->tm_name);
+free_chip:
+ dev_set_drvdata(&pdev->dev, NULL);
+ kfree(chip);
+ return rc;
+}
+
+static int qpnp_tm_remove(struct platform_device *pdev)
+{
+ struct qpnp_tm_chip *chip = dev_get_drvdata(&pdev->dev);
+
+ dev_set_drvdata(&pdev->dev, NULL);
+ thermal_zone_device_unregister(chip->tz_dev);
+ kfree(chip->tm_name);
+ qpnp_tm_shutdown_override(chip, SOFTWARE_OVERRIDE_DISABLED);
+ free_irq(chip->irq, chip);
+ cancel_delayed_work_sync(&chip->irq_work);
+ kfree(chip);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int qpnp_tm_suspend(struct device *dev)
+{
+ struct qpnp_tm_chip *chip = dev_get_drvdata(dev);
+
+ /* Clear override bits in suspend to allow hardware control */
+ qpnp_tm_shutdown_override(chip, SOFTWARE_OVERRIDE_DISABLED);
+
+ return 0;
+}
+
+static int qpnp_tm_resume(struct device *dev)
+{
+ struct qpnp_tm_chip *chip = dev_get_drvdata(dev);
+
+ /* Override hardware actions so software can control */
+ if (chip->mode == THERMAL_DEVICE_ENABLED)
+ qpnp_tm_shutdown_override(chip, SOFTWARE_OVERRIDE_ENABLED);
+
+ return 0;
+}
+
+static const struct dev_pm_ops qpnp_tm_pm_ops = {
+ .suspend = qpnp_tm_suspend,
+ .resume = qpnp_tm_resume,
+};
+
+#define QPNP_TM_PM_OPS (&qpnp_tm_pm_ops)
+#else
+#define QPNP_TM_PM_OPS NULL
+#endif
+
+static const struct of_device_id qpnp_tm_match_table[] = {
+ { .compatible = QPNP_TM_DRIVER_NAME, },
+ {}
+};
+
+static const struct platform_device_id qpnp_tm_id[] = {
+ { QPNP_TM_DRIVER_NAME, 0 },
+ {}
+};
+
+static struct platform_driver qpnp_tm_driver = {
+ .driver = {
+ .name = QPNP_TM_DRIVER_NAME,
+ .of_match_table = qpnp_tm_match_table,
+ .owner = THIS_MODULE,
+ .pm = QPNP_TM_PM_OPS,
+ },
+ .probe = qpnp_tm_probe,
+ .remove = qpnp_tm_remove,
+ .id_table = qpnp_tm_id,
+};
+
+int __init qpnp_tm_init(void)
+{
+ return platform_driver_register(&qpnp_tm_driver);
+}
+
+static void __exit qpnp_tm_exit(void)
+{
+ platform_driver_unregister(&qpnp_tm_driver);
+}
+
+module_init(qpnp_tm_init);
+module_exit(qpnp_tm_exit);
+
+MODULE_DESCRIPTION("QPNP PMIC Temperature Alarm driver");
+MODULE_LICENSE("GPL v2");