From d1ac2f1c83769a8722d4441d75fbfd39d39b0113 Mon Sep 17 00:00:00 2001 From: David Collins Date: Tue, 14 Feb 2012 13:34:18 -0800 Subject: [PATCH] regulator: Add qpnp-regulator driver Add the qpnp-regulator driver to support regulators found in Qualcomm plug-and-play (QPNP) PMIC chips. QPNP chips make use of Qualcomm's SPMI register convention. The particular hardware characteristics of a given regulator can be derived from the values present in the type and subtype registers. The qpnp-regulator driver supports probing with either device tree device specification or with board file specified platform data. Change-Id: I4f74431a50949763d651faf992b5d2567d05758e Signed-off-by: David Collins --- drivers/regulator/Kconfig | 10 + drivers/regulator/Makefile | 1 + drivers/regulator/qpnp-regulator.c | 1459 ++++++++++++++++++++++ include/linux/regulator/qpnp-regulator.h | 173 +++ 4 files changed, 1643 insertions(+) create mode 100644 drivers/regulator/qpnp-regulator.c create mode 100644 include/linux/regulator/qpnp-regulator.h diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index fb1a99f6f91..72b7880579b 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -354,4 +354,14 @@ config REGULATOR_STUB when the real hardware based implementation may not be yet available. Clients can use the real regulator device names with proper constraint checking while the real driver is being developed. + +config REGULATOR_QPNP + depends on OF_SPMI + depends on MSM_QPNP + tristate "Qualcomm QPNP regulator support" + help + This driver supports voltage regulators in Qualcomm PMIC chips which + comply with QPNP. QPNP is a SPMI based PMIC implementation. These + chips provide several different varieties of LDO and switching + regulators. They also provide voltage switches and boost regulators. endif diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 7e529c728bc..e6286225566 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -50,5 +50,6 @@ obj-$(CONFIG_REGULATOR_GPIO) += gpio-regulator.o obj-$(CONFIG_REGULATOR_PM8058_XO) += pm8058-xo.o obj-$(CONFIG_REGULATOR_PM8XXX) += pm8xxx-regulator.o obj-$(CONFIG_REGULATOR_STUB) += stub-regulator.o +obj-$(CONFIG_REGULATOR_QPNP) += qpnp-regulator.o ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG diff --git a/drivers/regulator/qpnp-regulator.c b/drivers/regulator/qpnp-regulator.c new file mode 100644 index 00000000000..fe728b27cf6 --- /dev/null +++ b/drivers/regulator/qpnp-regulator.c @@ -0,0 +1,1459 @@ +/* + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* Debug Flag Definitions */ +enum { + QPNP_VREG_DEBUG_REQUEST = BIT(0), /* Show requests */ + QPNP_VREG_DEBUG_DUPLICATE = BIT(1), /* Show duplicate requests */ + QPNP_VREG_DEBUG_INIT = BIT(2), /* Show state after probe */ + QPNP_VREG_DEBUG_WRITES = BIT(3), /* Show SPMI writes */ + QPNP_VREG_DEBUG_READS = BIT(4), /* Show SPMI reads */ +}; + +static int qpnp_vreg_debug_mask; +module_param_named( + debug_mask, qpnp_vreg_debug_mask, int, S_IRUSR | S_IWUSR +); + +#define vreg_err(vreg, fmt, ...) \ + pr_err("%s: " fmt, vreg->rdesc.name, ##__VA_ARGS__) + +/* These types correspond to unique register layouts. */ +enum qpnp_regulator_logical_type { + QPNP_REGULATOR_LOGICAL_TYPE_SMPS, + QPNP_REGULATOR_LOGICAL_TYPE_LDO, + QPNP_REGULATOR_LOGICAL_TYPE_VS, + QPNP_REGULATOR_LOGICAL_TYPE_BOOST, + QPNP_REGULATOR_LOGICAL_TYPE_FTSMPS, +}; + +enum qpnp_regulator_type { + QPNP_REGULATOR_TYPE_HF_BUCK = 0x03, + QPNP_REGULATOR_TYPE_LDO = 0x04, + QPNP_REGULATOR_TYPE_VS = 0x05, + QPNP_REGULATOR_TYPE_BOOST = 0x1B, + QPNP_REGULATOR_TYPE_FTS = 0x1C, +}; + +enum qpnp_regulator_subtype { + QPNP_REGULATOR_SUBTYPE_GP_CTL = 0x08, + QPNP_REGULATOR_SUBTYPE_RF_CTL = 0x09, + QPNP_REGULATOR_SUBTYPE_N50 = 0x01, + QPNP_REGULATOR_SUBTYPE_N150 = 0x02, + QPNP_REGULATOR_SUBTYPE_N300 = 0x03, + QPNP_REGULATOR_SUBTYPE_N600 = 0x04, + QPNP_REGULATOR_SUBTYPE_N1200 = 0x05, + QPNP_REGULATOR_SUBTYPE_P50 = 0x08, + QPNP_REGULATOR_SUBTYPE_P150 = 0x09, + QPNP_REGULATOR_SUBTYPE_P300 = 0x0A, + QPNP_REGULATOR_SUBTYPE_P600 = 0x0B, + QPNP_REGULATOR_SUBTYPE_P1200 = 0x0C, + QPNP_REGULATOR_SUBTYPE_LV100 = 0x01, + QPNP_REGULATOR_SUBTYPE_LV300 = 0x02, + QPNP_REGULATOR_SUBTYPE_MV300 = 0x08, + QPNP_REGULATOR_SUBTYPE_MV500 = 0x09, + QPNP_REGULATOR_SUBTYPE_HDMI = 0x10, + QPNP_REGULATOR_SUBTYPE_OTG = 0x11, + QPNP_REGULATOR_SUBTYPE_5V_BOOST = 0x01, + QPNP_REGULATOR_SUBTYPE_FTS_CTL = 0x08, +}; + +enum qpnp_common_regulator_registers { + QPNP_COMMON_REG_TYPE = 0x04, + QPNP_COMMON_REG_SUBTYPE = 0x05, + QPNP_COMMON_REG_VOLTAGE_RANGE = 0x40, + QPNP_COMMON_REG_VOLTAGE_SET = 0x41, + QPNP_COMMON_REG_MODE = 0x45, + QPNP_COMMON_REG_ENABLE = 0x46, + QPNP_COMMON_REG_PULL_DOWN = 0x48, +}; + +enum qpnp_ldo_registers { + QPNP_LDO_REG_SOFT_START = 0x4C, +}; + +enum qpnp_vs_registers { + QPNP_VS_REG_OCP = 0x4A, + QPNP_VS_REG_SOFT_START = 0x4C, +}; + +enum qpnp_boost_registers { + QPNP_BOOST_REG_CURRENT_LIMIT = 0x40, +}; + +/* Used for indexing into ctrl_reg. These are offets from 0x40 */ +enum qpnp_common_control_register_index { + QPNP_COMMON_IDX_VOLTAGE_RANGE = 0, + QPNP_COMMON_IDX_VOLTAGE_SET = 1, + QPNP_COMMON_IDX_MODE = 5, + QPNP_COMMON_IDX_ENABLE = 6, +}; + +enum qpnp_boost_control_register_index { + QPNP_BOOST_IDX_CURRENT_LIMIT = 0, +}; + +/* Common regulator control register layout */ +#define QPNP_COMMON_ENABLE_MASK 0x80 +#define QPNP_COMMON_ENABLE 0x80 +#define QPNP_COMMON_DISABLE 0x00 +#define QPNP_COMMON_ENABLE_FOLLOW_HW_EN3_MASK 0x08 +#define QPNP_COMMON_ENABLE_FOLLOW_HW_EN2_MASK 0x04 +#define QPNP_COMMON_ENABLE_FOLLOW_HW_EN1_MASK 0x02 +#define QPNP_COMMON_ENABLE_FOLLOW_HW_EN0_MASK 0x01 +#define QPNP_COMMON_ENABLE_FOLLOW_ALL_MASK 0x0F + +/* Common regulator mode register layout */ +#define QPNP_COMMON_MODE_HPM_MASK 0x80 +#define QPNP_COMMON_MODE_AUTO_MASK 0x40 +#define QPNP_COMMON_MODE_BYPASS_MASK 0x20 +#define QPNP_COMMON_MODE_FOLLOW_AWAKE_MASK 0x10 +#define QPNP_COMMON_MODE_FOLLOW_HW_EN3_MASK 0x08 +#define QPNP_COMMON_MODE_FOLLOW_HW_EN2_MASK 0x04 +#define QPNP_COMMON_MODE_FOLLOW_HW_EN1_MASK 0x02 +#define QPNP_COMMON_MODE_FOLLOW_HW_EN0_MASK 0x01 +#define QPNP_COMMON_MODE_FOLLOW_ALL_MASK 0x1F + +/* Common regulator pull down control register layout */ +#define QPNP_COMMON_PULL_DOWN_ENABLE_MASK 0x80 + +/* LDO regulator current limit control register layout */ +#define QPNP_LDO_CURRENT_LIMIT_ENABLE_MASK 0x80 + +/* LDO regulator soft start control register layout */ +#define QPNP_LDO_SOFT_START_ENABLE_MASK 0x80 + +/* VS regulator over current protection control register layout */ +#define QPNP_VS_OCP_ENABLE_MASK 0x80 +#define QPNP_VS_OCP_OVERRIDE_MASK 0x01 +#define QPNP_VS_OCP_DISABLE 0x00 + +/* VS regulator soft start control register layout */ +#define QPNP_VS_SOFT_START_ENABLE_MASK 0x80 +#define QPNP_VS_SOFT_START_SEL_MASK 0x03 + +/* Boost regulator current limit control register layout */ +#define QPNP_BOOST_CURRENT_LIMIT_ENABLE_MASK 0x80 +#define QPNP_BOOST_CURRENT_LIMIT_MASK 0x07 + +struct qpnp_voltage_range { + int min_uV; + int max_uV; + int step_uV; + int set_point_min_uV; + unsigned n_voltages; + u8 range_sel; +}; + +struct qpnp_voltage_set_points { + struct qpnp_voltage_range *range; + int count; + unsigned n_voltages; +}; + +struct qpnp_regulator_mapping { + enum qpnp_regulator_type type; + enum qpnp_regulator_subtype subtype; + enum qpnp_regulator_logical_type logical_type; + struct regulator_ops *ops; + struct qpnp_voltage_set_points *set_points; + int hpm_min_load; +}; + +struct qpnp_regulator { + struct regulator_desc rdesc; + struct spmi_device *spmi_dev; + struct regulator_dev *rdev; + struct qpnp_voltage_set_points *set_points; + enum qpnp_regulator_logical_type logical_type; + int enable_time; + int ocp_enable_time; + int ocp_enable; + int system_load; + int hpm_min_load; + u32 write_count; + u32 prev_write_count; + u16 base_addr; + /* ctrl_reg provides a shadow copy of register values 0x40 to 0x47. */ + u8 ctrl_reg[8]; +}; + +#define QPNP_VREG_MAP(_type, _subtype, _logical_type, _ops_val, \ + _set_points_val, _hpm_min_load) \ + { \ + .type = QPNP_REGULATOR_TYPE_##_type, \ + .subtype = QPNP_REGULATOR_SUBTYPE_##_subtype, \ + .logical_type = QPNP_REGULATOR_LOGICAL_TYPE_##_logical_type, \ + .ops = &qpnp_##_ops_val##_ops, \ + .set_points = &_set_points_val##_set_points, \ + .hpm_min_load = _hpm_min_load, \ + } + +#define VOLTAGE_RANGE(_range_sel, _min_uV, _set_point_min_uV, _max_uV, \ + _step_uV) \ + { \ + .min_uV = _min_uV, \ + .set_point_min_uV = _set_point_min_uV, \ + .max_uV = _max_uV, \ + .step_uV = _step_uV, \ + .range_sel = _range_sel, \ + } + +#define SET_POINTS(_ranges) \ +{ \ + .range = _ranges, \ + .count = ARRAY_SIZE(_ranges), \ +}; + +/* + * These tables contain the physically available PMIC regulator voltage setpoint + * ranges. Where two ranges overlap in hardware, one of the ranges is trimmed + * to ensure that the setpoints available to software are monotonically + * increasing and unique. The set_voltage callback functions expect these + * properties to hold. + */ +static struct qpnp_voltage_range pldo_ranges[] = { + VOLTAGE_RANGE(0, 375000, 375000, 1512500, 12500), + VOLTAGE_RANGE(2, 750000, 1525000, 1537500, 12500), + VOLTAGE_RANGE(3, 1500000, 1550000, 3075000, 25000), + VOLTAGE_RANGE(4, 1750000, 3100000, 4900000, 50000), +}; + +static struct qpnp_voltage_range nldo_ranges[] = { + VOLTAGE_RANGE(0, 375000, 375000, 1512500, 12500), + VOLTAGE_RANGE(2, 750000, 1525000, 1537500, 12500), +}; + +static struct qpnp_voltage_range smps_ranges[] = { + VOLTAGE_RANGE(0, 375000, 375000, 1562500, 12500), + VOLTAGE_RANGE(1, 1550000, 1575000, 3125000, 25000), +}; + +static struct qpnp_voltage_range ftsmps_ranges[] = { + VOLTAGE_RANGE(0, 80000, 80000, 1355000, 5000), + VOLTAGE_RANGE(1, 160000, 1360000, 2710000, 10000), +}; + +static struct qpnp_voltage_range boost_ranges[] = { + VOLTAGE_RANGE(0, 4000000, 4000000, 5550000, 50000), +}; + +static struct qpnp_voltage_set_points pldo_set_points = SET_POINTS(pldo_ranges); +static struct qpnp_voltage_set_points nldo_set_points = SET_POINTS(nldo_ranges); +static struct qpnp_voltage_set_points smps_set_points = SET_POINTS(smps_ranges); +static struct qpnp_voltage_set_points ftsmps_set_points + = SET_POINTS(ftsmps_ranges); +static struct qpnp_voltage_set_points boost_set_points + = SET_POINTS(boost_ranges); +static struct qpnp_voltage_set_points none_set_points; + +static struct qpnp_voltage_set_points *all_set_points[] = { + &pldo_set_points, + &nldo_set_points, + &smps_set_points, + &ftsmps_set_points, + &boost_set_points, +}; + +/* Determines which label to add to a debug print statement. */ +enum qpnp_regulator_action { + QPNP_REGULATOR_ACTION_INIT, + QPNP_REGULATOR_ACTION_ENABLE, + QPNP_REGULATOR_ACTION_DISABLE, + QPNP_REGULATOR_ACTION_VOLTAGE, + QPNP_REGULATOR_ACTION_MODE, +}; + +static void qpnp_vreg_show_state(struct regulator_dev *rdev, + enum qpnp_regulator_action action); + +#define DEBUG_PRINT_BUFFER_SIZE 64 +static void fill_string(char *str, size_t str_len, u8 *buf, int buf_len) +{ + int pos = 0; + int i; + + for (i = 0; i < buf_len; i++) { + pos += scnprintf(str + pos, str_len - pos, "0x%02X", buf[i]); + if (i < buf_len - 1) + pos += scnprintf(str + pos, str_len - pos, ", "); + } +} + +static inline int qpnp_vreg_read(struct qpnp_regulator *vreg, u16 addr, u8 *buf, + int len) +{ + char str[DEBUG_PRINT_BUFFER_SIZE]; + int rc = 0; + + rc = spmi_ext_register_readl(vreg->spmi_dev->ctrl, vreg->spmi_dev->sid, + vreg->base_addr + addr, buf, len); + + if (!rc && (qpnp_vreg_debug_mask & QPNP_VREG_DEBUG_READS)) { + str[0] = '\0'; + fill_string(str, DEBUG_PRINT_BUFFER_SIZE, buf, len); + pr_info(" %-11s: read(0x%04X), sid=%d, len=%d; %s\n", + vreg->rdesc.name, vreg->base_addr + addr, + vreg->spmi_dev->sid, len, str); + } + + return rc; +} + +static inline int qpnp_vreg_write(struct qpnp_regulator *vreg, u16 addr, + u8 *buf, int len) +{ + char str[DEBUG_PRINT_BUFFER_SIZE]; + int rc = 0; + + if (qpnp_vreg_debug_mask & QPNP_VREG_DEBUG_WRITES) { + str[0] = '\0'; + fill_string(str, DEBUG_PRINT_BUFFER_SIZE, buf, len); + pr_info("%-11s: write(0x%04X), sid=%d, len=%d; %s\n", + vreg->rdesc.name, vreg->base_addr + addr, + vreg->spmi_dev->sid, len, str); + } + + rc = spmi_ext_register_writel(vreg->spmi_dev->ctrl, + vreg->spmi_dev->sid, vreg->base_addr + addr, buf, len); + if (!rc) + vreg->write_count += len; + + return rc; +} + +/* + * qpnp_vreg_write_optimized - write the minimum sized contiguous subset of buf + * @vreg: qpnp_regulator pointer for this regulator + * @addr: local SPMI address offset from this peripheral's base address + * @buf: new data to write into the SPMI registers + * @buf_save: old data in the registers + * @len: number of bytes to write + * + * This function checks for unchanged register values between buf and buf_save + * starting at both ends of buf. Only the contiguous subset in the middle of + * buf starting and ending with new values is sent. + * + * Consider the following example: + * buf offset: 0 1 2 3 4 5 6 7 + * reg state: U U C C U C U U + * (U = unchanged, C = changed) + * In this example registers 2 through 5 will be written with a single + * transaction. + */ +static inline int qpnp_vreg_write_optimized(struct qpnp_regulator *vreg, + u16 addr, u8 *buf, u8 *buf_save, int len) +{ + int i, rc, start, end; + + for (i = 0; i < len; i++) + if (buf[i] != buf_save[i]) + break; + start = i; + + for (i = len - 1; i >= 0; i--) + if (buf[i] != buf_save[i]) + break; + end = i; + + if (start > end) { + /* No modified register values present. */ + return 0; + } + + rc = qpnp_vreg_write(vreg, addr + start, &buf[start], end - start + 1); + if (!rc) + for (i = start; i <= end; i++) + buf_save[i] = buf[i]; + + return rc; +} + +/* + * Perform a masked write to a PMIC register only if the new value differs + * from the last value written to the register. This removes redundant + * register writing. + */ +static int qpnp_vreg_masked_write(struct qpnp_regulator *vreg, u16 addr, u8 val, + u8 mask, u8 *reg_save) +{ + int rc = 0; + u8 reg; + + reg = (*reg_save & ~mask) | (val & mask); + if (reg != *reg_save) { + rc = qpnp_vreg_write(vreg, addr, ®, 1); + + if (rc) { + vreg_err(vreg, "write failed; addr=0x%03X, rc=%d\n", + addr, rc); + } else { + *reg_save = reg; + } + } + + return rc; +} + +/* + * Perform a masked read-modify-write to a PMIC register only if the new value + * differs from the value currently in the register. This removes redundant + * register writing. + */ +static int qpnp_vreg_masked_read_write(struct qpnp_regulator *vreg, u16 addr, + u8 val, u8 mask) +{ + int rc; + u8 reg; + + rc = qpnp_vreg_read(vreg, addr, ®, 1); + if (rc) { + vreg_err(vreg, "read failed; addr=0x%03X, rc=%d\n", addr, rc); + return rc; + } + + return qpnp_vreg_masked_write(vreg, addr, val, mask, ®); +} + +static int qpnp_regulator_common_is_enabled(struct regulator_dev *rdev) +{ + struct qpnp_regulator *vreg = rdev_get_drvdata(rdev); + + return (vreg->ctrl_reg[QPNP_COMMON_IDX_ENABLE] + & QPNP_COMMON_ENABLE_MASK) + == QPNP_COMMON_ENABLE; +} + +static int qpnp_regulator_common_enable(struct regulator_dev *rdev) +{ + struct qpnp_regulator *vreg = rdev_get_drvdata(rdev); + int rc; + + rc = qpnp_vreg_masked_write(vreg, QPNP_COMMON_REG_ENABLE, + QPNP_COMMON_ENABLE, QPNP_COMMON_ENABLE_MASK, + &vreg->ctrl_reg[QPNP_COMMON_IDX_ENABLE]); + + if (rc) + vreg_err(vreg, "qpnp_vreg_masked_write failed, rc=%d\n", rc); + else + qpnp_vreg_show_state(rdev, QPNP_REGULATOR_ACTION_ENABLE); + + return rc; +} + +static int qpnp_regulator_vs_enable(struct regulator_dev *rdev) +{ + struct qpnp_regulator *vreg = rdev_get_drvdata(rdev); + int rc; + u8 reg; + + if (vreg->ocp_enable == QPNP_REGULATOR_ENABLE) { + /* Disable OCP */ + reg = QPNP_VS_OCP_DISABLE; + rc = qpnp_vreg_write(vreg, QPNP_VS_REG_OCP, ®, 1); + if (rc) + goto fail; + } + + rc = qpnp_regulator_common_enable(rdev); + if (rc) + goto fail; + + if (vreg->ocp_enable == QPNP_REGULATOR_ENABLE) { + /* Wait for inrush current to subsided, then enable OCP. */ + udelay(vreg->ocp_enable_time); + reg = QPNP_VS_OCP_ENABLE_MASK; + rc = qpnp_vreg_write(vreg, QPNP_VS_REG_OCP, ®, 1); + if (rc) + goto fail; + } + + return rc; +fail: + vreg_err(vreg, "qpnp_vreg_write failed, rc=%d\n", rc); + + return rc; +} + +static int qpnp_regulator_common_disable(struct regulator_dev *rdev) +{ + struct qpnp_regulator *vreg = rdev_get_drvdata(rdev); + int rc; + + rc = qpnp_vreg_masked_write(vreg, QPNP_COMMON_REG_ENABLE, + QPNP_COMMON_DISABLE, QPNP_COMMON_ENABLE_MASK, + &vreg->ctrl_reg[QPNP_COMMON_IDX_ENABLE]); + + if (rc) + vreg_err(vreg, "qpnp_vreg_masked_write failed, rc=%d\n", rc); + else + qpnp_vreg_show_state(rdev, QPNP_REGULATOR_ACTION_DISABLE); + + return rc; +} + +static int qpnp_regulator_select_voltage(struct qpnp_regulator *vreg, + int min_uV, int max_uV, int *range_sel, int *voltage_sel) +{ + struct qpnp_voltage_range *range; + int uV = min_uV; + int lim_min_uV, lim_max_uV, i; + + /* Check if request voltage is outside of physically settable range. */ + lim_min_uV = vreg->set_points->range[0].set_point_min_uV; + lim_max_uV = + vreg->set_points->range[vreg->set_points->count - 1].max_uV; + + if (uV < lim_min_uV && max_uV >= lim_min_uV) + uV = lim_min_uV; + + if (uV < lim_min_uV || uV > lim_max_uV) { + vreg_err(vreg, + "request v=[%d, %d] is outside possible v=[%d, %d]\n", + min_uV, max_uV, lim_min_uV, lim_max_uV); + return -EINVAL; + } + + /* Find the range which uV is inside of. */ + for (i = vreg->set_points->count - 1; i > 0; i--) + if (uV > vreg->set_points->range[i - 1].max_uV) + break; + range = &vreg->set_points->range[i]; + *range_sel = range->range_sel; + + /* + * Force uV to be an allowed set point by applying a ceiling function to + * the uV value. + */ + *voltage_sel = (uV - range->min_uV + range->step_uV - 1) + / range->step_uV; + uV = *voltage_sel * range->step_uV + range->min_uV; + + if (uV > max_uV) { + vreg_err(vreg, + "request v=[%d, %d] cannot be met by any set point; " + "next set point: %d\n", + min_uV, max_uV, uV); + return -EINVAL; + } + + return 0; +} + +static int qpnp_regulator_common_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV, unsigned *selector) +{ + struct qpnp_regulator *vreg = rdev_get_drvdata(rdev); + int rc, range_sel, voltage_sel; + u8 buf[2]; + + rc = qpnp_regulator_select_voltage(vreg, min_uV, max_uV, &range_sel, + &voltage_sel); + if (rc) { + vreg_err(vreg, "could not set voltage, rc=%d\n", rc); + return rc; + } + + buf[0] = range_sel; + buf[1] = voltage_sel; + if ((vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_RANGE] != range_sel) + && (vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_SET] == voltage_sel)) { + /* Handle latched range change. */ + rc = qpnp_vreg_write(vreg, QPNP_COMMON_REG_VOLTAGE_RANGE, + buf, 2); + if (!rc) { + vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_RANGE] = buf[0]; + vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_SET] = buf[1]; + } + } else { + /* Either write can be optimized away safely. */ + rc = qpnp_vreg_write_optimized(vreg, + QPNP_COMMON_REG_VOLTAGE_RANGE, buf, + &vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_RANGE], 2); + } + + if (rc) + vreg_err(vreg, "SPMI write failed, rc=%d\n", rc); + else + qpnp_vreg_show_state(rdev, QPNP_REGULATOR_ACTION_VOLTAGE); + + return rc; +} + +static int qpnp_regulator_common_get_voltage(struct regulator_dev *rdev) +{ + struct qpnp_regulator *vreg = rdev_get_drvdata(rdev); + struct qpnp_voltage_range *range = NULL; + int range_sel, voltage_sel, i; + + range_sel = vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_RANGE]; + voltage_sel = vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_SET]; + + for (i = 0; i < vreg->set_points->count; i++) { + if (vreg->set_points->range[i].range_sel == range_sel) { + range = &vreg->set_points->range[i]; + break; + } + } + + if (!range) { + vreg_err(vreg, "voltage unknown, range %d is invalid\n", + range_sel); + return -EINVAL; + } + + return range->step_uV * voltage_sel + range->min_uV; +} + +static int qpnp_regulator_boost_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV, unsigned *selector) +{ + struct qpnp_regulator *vreg = rdev_get_drvdata(rdev); + int rc, range_sel, voltage_sel; + + rc = qpnp_regulator_select_voltage(vreg, min_uV, max_uV, &range_sel, + &voltage_sel); + if (rc) { + vreg_err(vreg, "could not set voltage, rc=%d\n", rc); + return rc; + } + + /* + * Boost type regulators do not have range select register so only + * voltage set register needs to be written. + */ + rc = qpnp_vreg_masked_write(vreg, QPNP_COMMON_REG_VOLTAGE_SET, + voltage_sel, 0xFF, &vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_SET]); + + if (rc) + vreg_err(vreg, "SPMI write failed, rc=%d\n", rc); + else + qpnp_vreg_show_state(rdev, QPNP_REGULATOR_ACTION_VOLTAGE); + + return rc; +} + +static int qpnp_regulator_boost_get_voltage(struct regulator_dev *rdev) +{ + struct qpnp_regulator *vreg = rdev_get_drvdata(rdev); + int voltage_sel = vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_SET]; + + return boost_ranges[0].step_uV * voltage_sel + boost_ranges[0].min_uV; +} + +static int qpnp_regulator_common_list_voltage(struct regulator_dev *rdev, + unsigned selector) +{ + struct qpnp_regulator *vreg = rdev_get_drvdata(rdev); + int uV = 0; + int i; + + if (selector >= vreg->set_points->n_voltages) + return 0; + + for (i = 0; i < vreg->set_points->count; i++) { + if (selector < vreg->set_points->range[i].n_voltages) { + uV = selector * vreg->set_points->range[i].step_uV + + vreg->set_points->range[i].set_point_min_uV; + break; + } else { + selector -= vreg->set_points->range[i].n_voltages; + } + } + + return uV; +} + +static unsigned int qpnp_regulator_common_get_mode(struct regulator_dev *rdev) +{ + struct qpnp_regulator *vreg = rdev_get_drvdata(rdev); + + return (vreg->ctrl_reg[QPNP_COMMON_IDX_MODE] + & QPNP_COMMON_MODE_HPM_MASK) + ? REGULATOR_MODE_NORMAL : REGULATOR_MODE_IDLE; +} + +static int qpnp_regulator_common_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct qpnp_regulator *vreg = rdev_get_drvdata(rdev); + int rc = 0; + u8 val; + + if (mode != REGULATOR_MODE_NORMAL && mode != REGULATOR_MODE_IDLE) { + vreg_err(vreg, "invalid mode: %u\n", mode); + return -EINVAL; + } + + val = (mode == REGULATOR_MODE_NORMAL ? QPNP_COMMON_MODE_HPM_MASK : 0); + + rc = qpnp_vreg_masked_write(vreg, QPNP_COMMON_REG_MODE, val, + QPNP_COMMON_MODE_HPM_MASK, + &vreg->ctrl_reg[QPNP_COMMON_IDX_MODE]); + + if (rc) + vreg_err(vreg, "SPMI write failed, rc=%d\n", rc); + else + qpnp_vreg_show_state(rdev, QPNP_REGULATOR_ACTION_MODE); + + return rc; +} + +static unsigned int qpnp_regulator_common_get_optimum_mode( + struct regulator_dev *rdev, int input_uV, int output_uV, + int load_uA) +{ + struct qpnp_regulator *vreg = rdev_get_drvdata(rdev); + unsigned int mode; + + if (load_uA + vreg->system_load >= vreg->hpm_min_load) + mode = REGULATOR_MODE_NORMAL; + else + mode = REGULATOR_MODE_IDLE; + + return mode; +} + +static int qpnp_regulator_common_enable_time(struct regulator_dev *rdev) +{ + struct qpnp_regulator *vreg = rdev_get_drvdata(rdev); + + return vreg->enable_time; +} + +static const char const *qpnp_print_actions[] = { + [QPNP_REGULATOR_ACTION_INIT] = "initial ", + [QPNP_REGULATOR_ACTION_ENABLE] = "enable ", + [QPNP_REGULATOR_ACTION_DISABLE] = "disable ", + [QPNP_REGULATOR_ACTION_VOLTAGE] = "set voltage", + [QPNP_REGULATOR_ACTION_MODE] = "set mode ", +}; + +static void qpnp_vreg_show_state(struct regulator_dev *rdev, + enum qpnp_regulator_action action) +{ + struct qpnp_regulator *vreg = rdev_get_drvdata(rdev); + const char *action_label = qpnp_print_actions[action]; + unsigned int mode = 0; + int uV = 0; + const char *mode_label = ""; + enum qpnp_regulator_logical_type type; + const char *enable_label; + char pc_enable_label[5] = {'\0'}; + char pc_mode_label[8] = {'\0'}; + bool show_req, show_dupe, show_init, has_changed; + u8 en_reg, mode_reg; + + /* Do not print unless appropriate flags are set. */ + show_req = qpnp_vreg_debug_mask & QPNP_VREG_DEBUG_REQUEST; + show_dupe = qpnp_vreg_debug_mask & QPNP_VREG_DEBUG_DUPLICATE; + show_init = qpnp_vreg_debug_mask & QPNP_VREG_DEBUG_INIT; + has_changed = vreg->write_count != vreg->prev_write_count; + if (!((show_init && action == QPNP_REGULATOR_ACTION_INIT) + || (show_req && (has_changed || show_dupe)))) { + return; + } + + vreg->prev_write_count = vreg->write_count; + + type = vreg->logical_type; + + enable_label = qpnp_regulator_common_is_enabled(rdev) ? "on " : "off"; + + if (type == QPNP_REGULATOR_LOGICAL_TYPE_SMPS + || type == QPNP_REGULATOR_LOGICAL_TYPE_LDO + || type == QPNP_REGULATOR_LOGICAL_TYPE_FTSMPS) + uV = qpnp_regulator_common_get_voltage(rdev); + + if (type == QPNP_REGULATOR_LOGICAL_TYPE_BOOST) + uV = qpnp_regulator_boost_get_voltage(rdev); + + if (type == QPNP_REGULATOR_LOGICAL_TYPE_SMPS + || type == QPNP_REGULATOR_LOGICAL_TYPE_LDO + || type == QPNP_REGULATOR_LOGICAL_TYPE_FTSMPS) { + mode = qpnp_regulator_common_get_mode(rdev); + mode_label = mode == REGULATOR_MODE_NORMAL ? "HPM" : "LPM"; + } + + if (type == QPNP_REGULATOR_LOGICAL_TYPE_SMPS + || type == QPNP_REGULATOR_LOGICAL_TYPE_LDO + || type == QPNP_REGULATOR_LOGICAL_TYPE_VS) { + en_reg = vreg->ctrl_reg[QPNP_COMMON_IDX_ENABLE]; + pc_enable_label[0] = + en_reg & QPNP_COMMON_ENABLE_FOLLOW_HW_EN3_MASK ? '3' : '_'; + pc_enable_label[1] = + en_reg & QPNP_COMMON_ENABLE_FOLLOW_HW_EN2_MASK ? '2' : '_'; + pc_enable_label[2] = + en_reg & QPNP_COMMON_ENABLE_FOLLOW_HW_EN1_MASK ? '1' : '_'; + pc_enable_label[3] = + en_reg & QPNP_COMMON_ENABLE_FOLLOW_HW_EN0_MASK ? '0' : '_'; + } + + switch (type) { + case QPNP_REGULATOR_LOGICAL_TYPE_SMPS: + mode_reg = vreg->ctrl_reg[QPNP_COMMON_IDX_MODE]; + pc_mode_label[0] = + mode_reg & QPNP_COMMON_MODE_AUTO_MASK ? 'A' : '_'; + pc_mode_label[1] = + mode_reg & QPNP_COMMON_MODE_FOLLOW_AWAKE_MASK ? 'W' : '_'; + pc_mode_label[2] = + mode_reg & QPNP_COMMON_MODE_FOLLOW_HW_EN3_MASK ? '3' : '_'; + pc_mode_label[3] = + mode_reg & QPNP_COMMON_MODE_FOLLOW_HW_EN2_MASK ? '2' : '_'; + pc_mode_label[4] = + mode_reg & QPNP_COMMON_MODE_FOLLOW_HW_EN1_MASK ? '1' : '_'; + pc_mode_label[5] = + mode_reg & QPNP_COMMON_MODE_FOLLOW_HW_EN0_MASK ? '0' : '_'; + + pr_info("%s %-11s: %s, v=%7d uV, mode=%s, pc_en=%s, " + "alt_mode=%s\n", + action_label, vreg->rdesc.name, enable_label, uV, + mode_label, pc_enable_label, pc_mode_label); + break; + case QPNP_REGULATOR_LOGICAL_TYPE_LDO: + mode_reg = vreg->ctrl_reg[QPNP_COMMON_IDX_MODE]; + pc_mode_label[0] = + mode_reg & QPNP_COMMON_MODE_AUTO_MASK ? 'A' : '_'; + pc_mode_label[1] = + mode_reg & QPNP_COMMON_MODE_BYPASS_MASK ? 'B' : '_'; + pc_mode_label[2] = + mode_reg & QPNP_COMMON_MODE_FOLLOW_AWAKE_MASK ? 'W' : '_'; + pc_mode_label[3] = + mode_reg & QPNP_COMMON_MODE_FOLLOW_HW_EN3_MASK ? '3' : '_'; + pc_mode_label[4] = + mode_reg & QPNP_COMMON_MODE_FOLLOW_HW_EN2_MASK ? '2' : '_'; + pc_mode_label[5] = + mode_reg & QPNP_COMMON_MODE_FOLLOW_HW_EN1_MASK ? '1' : '_'; + pc_mode_label[6] = + mode_reg & QPNP_COMMON_MODE_FOLLOW_HW_EN0_MASK ? '0' : '_'; + + pr_info("%s %-11s: %s, v=%7d uV, mode=%s, pc_en=%s, " + "alt_mode=%s\n", + action_label, vreg->rdesc.name, enable_label, uV, + mode_label, pc_enable_label, pc_mode_label); + break; + case QPNP_REGULATOR_LOGICAL_TYPE_VS: + mode_reg = vreg->ctrl_reg[QPNP_COMMON_IDX_MODE]; + pc_mode_label[0] = + mode_reg & QPNP_COMMON_MODE_AUTO_MASK ? 'A' : '_'; + pc_mode_label[1] = + mode_reg & QPNP_COMMON_MODE_FOLLOW_AWAKE_MASK ? 'W' : '_'; + + pr_info("%s %-11s: %s, pc_en=%s, alt_mode=%s\n", + action_label, vreg->rdesc.name, enable_label, + pc_enable_label, pc_mode_label); + break; + case QPNP_REGULATOR_LOGICAL_TYPE_BOOST: + pr_info("%s %-11s: %s, v=%7d uV\n", + action_label, vreg->rdesc.name, enable_label, uV); + break; + case QPNP_REGULATOR_LOGICAL_TYPE_FTSMPS: + mode_reg = vreg->ctrl_reg[QPNP_COMMON_IDX_MODE]; + pc_mode_label[0] = + mode_reg & QPNP_COMMON_MODE_AUTO_MASK ? 'A' : '_'; + + pr_info("%s %-11s: %s, v=%7d uV, mode=%s, alt_mode=%s\n", + action_label, vreg->rdesc.name, enable_label, uV, + mode_label, pc_mode_label); + break; + default: + break; + } +} + +static struct regulator_ops qpnp_smps_ops = { + .enable = qpnp_regulator_common_enable, + .disable = qpnp_regulator_common_disable, + .is_enabled = qpnp_regulator_common_is_enabled, + .set_voltage = qpnp_regulator_common_set_voltage, + .get_voltage = qpnp_regulator_common_get_voltage, + .list_voltage = qpnp_regulator_common_list_voltage, + .set_mode = qpnp_regulator_common_set_mode, + .get_mode = qpnp_regulator_common_get_mode, + .get_optimum_mode = qpnp_regulator_common_get_optimum_mode, + .enable_time = qpnp_regulator_common_enable_time, +}; + +static struct regulator_ops qpnp_ldo_ops = { + .enable = qpnp_regulator_common_enable, + .disable = qpnp_regulator_common_disable, + .is_enabled = qpnp_regulator_common_is_enabled, + .set_voltage = qpnp_regulator_common_set_voltage, + .get_voltage = qpnp_regulator_common_get_voltage, + .list_voltage = qpnp_regulator_common_list_voltage, + .set_mode = qpnp_regulator_common_set_mode, + .get_mode = qpnp_regulator_common_get_mode, + .get_optimum_mode = qpnp_regulator_common_get_optimum_mode, + .enable_time = qpnp_regulator_common_enable_time, +}; + +static struct regulator_ops qpnp_vs_ops = { + .enable = qpnp_regulator_vs_enable, + .disable = qpnp_regulator_common_disable, + .is_enabled = qpnp_regulator_common_is_enabled, + .enable_time = qpnp_regulator_common_enable_time, +}; + +static struct regulator_ops qpnp_boost_ops = { + .enable = qpnp_regulator_common_enable, + .disable = qpnp_regulator_common_disable, + .is_enabled = qpnp_regulator_common_is_enabled, + .set_voltage = qpnp_regulator_boost_set_voltage, + .get_voltage = qpnp_regulator_boost_get_voltage, + .list_voltage = qpnp_regulator_common_list_voltage, + .enable_time = qpnp_regulator_common_enable_time, +}; + +static struct regulator_ops qpnp_ftsmps_ops = { + .enable = qpnp_regulator_common_enable, + .disable = qpnp_regulator_common_disable, + .is_enabled = qpnp_regulator_common_is_enabled, + .set_voltage = qpnp_regulator_common_set_voltage, + .get_voltage = qpnp_regulator_common_get_voltage, + .list_voltage = qpnp_regulator_common_list_voltage, + .set_mode = qpnp_regulator_common_set_mode, + .get_mode = qpnp_regulator_common_get_mode, + .get_optimum_mode = qpnp_regulator_common_get_optimum_mode, + .enable_time = qpnp_regulator_common_enable_time, +}; + +static const struct qpnp_regulator_mapping supported_regulators[] = { + QPNP_VREG_MAP(HF_BUCK, GP_CTL, SMPS, smps, smps, 100000), + QPNP_VREG_MAP(LDO, N50, LDO, ldo, nldo, 5000), + QPNP_VREG_MAP(LDO, N150, LDO, ldo, nldo, 10000), + QPNP_VREG_MAP(LDO, N300, LDO, ldo, nldo, 10000), + QPNP_VREG_MAP(LDO, N600, LDO, ldo, nldo, 10000), + QPNP_VREG_MAP(LDO, N1200, LDO, ldo, nldo, 10000), + QPNP_VREG_MAP(LDO, P50, LDO, ldo, pldo, 5000), + QPNP_VREG_MAP(LDO, P150, LDO, ldo, pldo, 10000), + QPNP_VREG_MAP(LDO, P300, LDO, ldo, pldo, 10000), + QPNP_VREG_MAP(LDO, P600, LDO, ldo, pldo, 10000), + QPNP_VREG_MAP(LDO, P1200, LDO, ldo, pldo, 10000), + QPNP_VREG_MAP(VS, LV100, VS, vs, none, 0), + QPNP_VREG_MAP(VS, LV300, VS, vs, none, 0), + QPNP_VREG_MAP(VS, MV300, VS, vs, none, 0), + QPNP_VREG_MAP(VS, MV500, VS, vs, none, 0), + QPNP_VREG_MAP(VS, HDMI, VS, vs, none, 0), + QPNP_VREG_MAP(VS, OTG, VS, vs, none, 0), + QPNP_VREG_MAP(BOOST, 5V_BOOST, BOOST, boost, boost, 0), + QPNP_VREG_MAP(FTS, FTS_CTL, FTSMPS, ftsmps, ftsmps, 100000), +}; + +static int qpnp_regulator_match(struct qpnp_regulator *vreg) +{ + const struct qpnp_regulator_mapping *mapping; + int rc, i; + u8 raw_type[2], type, subtype; + + rc = qpnp_vreg_read(vreg, QPNP_COMMON_REG_TYPE, raw_type, 2); + if (rc) { + vreg_err(vreg, "could not read type register, rc=%d\n", rc); + return rc; + } + type = raw_type[0]; + subtype = raw_type[1]; + + rc = -ENODEV; + for (i = 0; i < ARRAY_SIZE(supported_regulators); i++) { + mapping = &supported_regulators[i]; + if (mapping->type == type && mapping->subtype == subtype) { + vreg->logical_type = mapping->logical_type; + vreg->set_points = mapping->set_points; + vreg->hpm_min_load = mapping->hpm_min_load; + vreg->rdesc.ops = mapping->ops; + vreg->rdesc.n_voltages + = mapping->set_points->n_voltages; + rc = 0; + break; + } + } + + return rc; +} + +static int qpnp_regulator_init_registers(struct qpnp_regulator *vreg, + struct qpnp_regulator_platform_data *pdata) +{ + int rc, i; + enum qpnp_regulator_logical_type type; + u8 ctrl_reg[8], reg, mask; + + type = vreg->logical_type; + + rc = qpnp_vreg_read(vreg, QPNP_COMMON_REG_VOLTAGE_RANGE, + vreg->ctrl_reg, 8); + if (rc) { + vreg_err(vreg, "spmi read failed, rc=%d\n", rc); + return rc; + } + + for (i = 0; i < ARRAY_SIZE(ctrl_reg); i++) + ctrl_reg[i] = vreg->ctrl_reg[i]; + + /* Set up enable pin control. */ + if ((type == QPNP_REGULATOR_LOGICAL_TYPE_SMPS + || type == QPNP_REGULATOR_LOGICAL_TYPE_LDO + || type == QPNP_REGULATOR_LOGICAL_TYPE_VS) + && !(pdata->pin_ctrl_enable + & QPNP_REGULATOR_PIN_CTRL_ENABLE_HW_DEFAULT)) { + ctrl_reg[QPNP_COMMON_IDX_ENABLE] &= + ~QPNP_COMMON_ENABLE_FOLLOW_ALL_MASK; + ctrl_reg[QPNP_COMMON_IDX_ENABLE] |= + pdata->pin_ctrl_enable & QPNP_COMMON_ENABLE_FOLLOW_ALL_MASK; + } + + /* Set up auto mode control. */ + if ((type == QPNP_REGULATOR_LOGICAL_TYPE_SMPS + || type == QPNP_REGULATOR_LOGICAL_TYPE_LDO + || type == QPNP_REGULATOR_LOGICAL_TYPE_VS + || type == QPNP_REGULATOR_LOGICAL_TYPE_FTSMPS) + && (pdata->auto_mode_enable != QPNP_REGULATOR_USE_HW_DEFAULT)) { + ctrl_reg[QPNP_COMMON_IDX_MODE] &= + ~QPNP_COMMON_MODE_AUTO_MASK; + ctrl_reg[QPNP_COMMON_IDX_MODE] |= + (pdata->auto_mode_enable ? QPNP_COMMON_MODE_AUTO_MASK : 0); + } + + /* Set up mode pin control. */ + if ((type == QPNP_REGULATOR_LOGICAL_TYPE_SMPS + || type == QPNP_REGULATOR_LOGICAL_TYPE_LDO) + && !(pdata->pin_ctrl_hpm + & QPNP_REGULATOR_PIN_CTRL_HPM_HW_DEFAULT)) { + ctrl_reg[QPNP_COMMON_IDX_MODE] &= + ~QPNP_COMMON_MODE_FOLLOW_ALL_MASK; + ctrl_reg[QPNP_COMMON_IDX_MODE] |= + pdata->pin_ctrl_hpm & QPNP_COMMON_MODE_FOLLOW_ALL_MASK; + } + + if (type == QPNP_REGULATOR_LOGICAL_TYPE_VS + && !(pdata->pin_ctrl_hpm & QPNP_REGULATOR_PIN_CTRL_HPM_HW_DEFAULT)) { + ctrl_reg[QPNP_COMMON_IDX_MODE] &= + ~QPNP_COMMON_MODE_FOLLOW_AWAKE_MASK; + ctrl_reg[QPNP_COMMON_IDX_MODE] |= + pdata->pin_ctrl_hpm & QPNP_COMMON_MODE_FOLLOW_AWAKE_MASK; + } + + if (type == QPNP_REGULATOR_LOGICAL_TYPE_LDO + && pdata->bypass_mode_enable != QPNP_REGULATOR_USE_HW_DEFAULT) { + ctrl_reg[QPNP_COMMON_IDX_MODE] &= + ~QPNP_COMMON_MODE_BYPASS_MASK; + ctrl_reg[QPNP_COMMON_IDX_MODE] |= + (pdata->bypass_mode_enable + ? QPNP_COMMON_MODE_BYPASS_MASK : 0); + } + + /* Set boost current limit. */ + if (type == QPNP_REGULATOR_LOGICAL_TYPE_BOOST + && pdata->boost_current_limit + != QPNP_BOOST_CURRENT_LIMIT_HW_DEFAULT) { + ctrl_reg[QPNP_BOOST_IDX_CURRENT_LIMIT] &= + ~QPNP_BOOST_CURRENT_LIMIT_MASK; + ctrl_reg[QPNP_BOOST_IDX_CURRENT_LIMIT] |= + pdata->boost_current_limit & QPNP_BOOST_CURRENT_LIMIT_MASK; + } + + /* Write back any control register values that were modified. */ + rc = qpnp_vreg_write_optimized(vreg, QPNP_COMMON_REG_VOLTAGE_RANGE, + ctrl_reg, vreg->ctrl_reg, 8); + if (rc) { + vreg_err(vreg, "spmi write failed, rc=%d\n", rc); + return rc; + } + + /* Set pull down. */ + if ((type == QPNP_REGULATOR_LOGICAL_TYPE_SMPS + || type == QPNP_REGULATOR_LOGICAL_TYPE_LDO + || type == QPNP_REGULATOR_LOGICAL_TYPE_VS) + && pdata->pull_down_enable != QPNP_REGULATOR_USE_HW_DEFAULT) { + reg = pdata->pull_down_enable + ? QPNP_COMMON_PULL_DOWN_ENABLE_MASK : 0; + rc = qpnp_vreg_write(vreg, QPNP_COMMON_REG_PULL_DOWN, ®, 1); + if (rc) { + vreg_err(vreg, "spmi write failed, rc=%d\n", rc); + return rc; + } + } + + if (type == QPNP_REGULATOR_LOGICAL_TYPE_FTSMPS + && pdata->pull_down_enable != QPNP_REGULATOR_USE_HW_DEFAULT) { + /* FTSMPS has other bits in the pull down control register. */ + reg = pdata->pull_down_enable + ? QPNP_COMMON_PULL_DOWN_ENABLE_MASK : 0; + rc = qpnp_vreg_masked_read_write(vreg, + QPNP_COMMON_REG_PULL_DOWN, reg, + QPNP_COMMON_PULL_DOWN_ENABLE_MASK); + if (rc) { + vreg_err(vreg, "spmi write failed, rc=%d\n", rc); + return rc; + } + } + + /* Set soft start for LDO. */ + if (type == QPNP_REGULATOR_LOGICAL_TYPE_LDO + && pdata->soft_start_enable != QPNP_REGULATOR_USE_HW_DEFAULT) { + reg = pdata->soft_start_enable + ? QPNP_LDO_SOFT_START_ENABLE_MASK : 0; + rc = qpnp_vreg_write(vreg, QPNP_LDO_REG_SOFT_START, ®, 1); + if (rc) { + vreg_err(vreg, "spmi write failed, rc=%d\n", rc); + return rc; + } + } + + /* Set soft start strength and over current protection for VS. */ + if (type == QPNP_REGULATOR_LOGICAL_TYPE_VS) { + reg = 0; + mask = 0; + if (pdata->soft_start_enable != QPNP_REGULATOR_USE_HW_DEFAULT) { + reg |= pdata->soft_start_enable + ? QPNP_VS_SOFT_START_ENABLE_MASK : 0; + mask |= QPNP_VS_SOFT_START_ENABLE_MASK; + } + if (pdata->vs_soft_start_strength + != QPNP_VS_SOFT_START_STR_HW_DEFAULT) { + reg |= pdata->vs_soft_start_strength + & QPNP_VS_SOFT_START_SEL_MASK; + mask |= QPNP_VS_SOFT_START_SEL_MASK; + } + rc = qpnp_vreg_masked_read_write(vreg, QPNP_VS_REG_SOFT_START, + reg, mask); + if (rc) { + vreg_err(vreg, "spmi write failed, rc=%d\n", rc); + return rc; + } + + if (pdata->ocp_enable != QPNP_REGULATOR_USE_HW_DEFAULT) { + reg = pdata->ocp_enable ? QPNP_VS_OCP_ENABLE_MASK : 0; + rc = qpnp_vreg_write(vreg, QPNP_VS_REG_OCP, ®, 1); + if (rc) { + vreg_err(vreg, "spmi write failed, rc=%d\n", + rc); + return rc; + } + } + } + + return rc; +} + +/* Fill in pdata elements based on values found in device tree. */ +static int qpnp_regulator_get_dt_config(struct spmi_device *spmi, + struct qpnp_regulator_platform_data *pdata) +{ + struct resource *res; + struct device_node *node = spmi->dev.of_node; + int rc = 0; + + pdata->init_data.constraints.input_uV + = pdata->init_data.constraints.max_uV; + + res = qpnp_get_resource(spmi, 0, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&spmi->dev, "%s: node is missing base address\n", + __func__); + return -EINVAL; + } + pdata->base_addr = res->start; + + /* + * Initialize configuration parameters to use hardware default in case + * no value is specified via device tree. + */ + pdata->auto_mode_enable = QPNP_REGULATOR_USE_HW_DEFAULT; + pdata->bypass_mode_enable = QPNP_REGULATOR_USE_HW_DEFAULT; + pdata->ocp_enable = QPNP_REGULATOR_USE_HW_DEFAULT; + pdata->pull_down_enable = QPNP_REGULATOR_USE_HW_DEFAULT; + pdata->soft_start_enable = QPNP_REGULATOR_USE_HW_DEFAULT; + pdata->boost_current_limit = QPNP_BOOST_CURRENT_LIMIT_HW_DEFAULT; + pdata->pin_ctrl_enable = QPNP_REGULATOR_PIN_CTRL_ENABLE_HW_DEFAULT; + pdata->pin_ctrl_hpm = QPNP_REGULATOR_PIN_CTRL_HPM_HW_DEFAULT; + pdata->vs_soft_start_strength = QPNP_VS_SOFT_START_STR_HW_DEFAULT; + + /* These bindings are optional, so it is okay if they are not found. */ + of_property_read_u32(node, "qcom,auto-mode-enable", + &pdata->auto_mode_enable); + of_property_read_u32(node, "qcom,bypass-mode-enable", + &pdata->bypass_mode_enable); + of_property_read_u32(node, "qcom,ocp-enable", &pdata->ocp_enable); + of_property_read_u32(node, "qcom,pull-down-enable", + &pdata->pull_down_enable); + of_property_read_u32(node, "qcom,soft-start-enable", + &pdata->soft_start_enable); + of_property_read_u32(node, "qcom,boost-current-limit", + &pdata->boost_current_limit); + of_property_read_u32(node, "qcom,pin-ctrl-enable", + &pdata->pin_ctrl_enable); + of_property_read_u32(node, "qcom,pin-ctrl-hpm", &pdata->pin_ctrl_hpm); + of_property_read_u32(node, "qcom,vs-soft-start-strength", + &pdata->vs_soft_start_strength); + of_property_read_u32(node, "qcom,system-load", &pdata->system_load); + of_property_read_u32(node, "qcom,enable-time", &pdata->enable_time); + of_property_read_u32(node, "qcom,ocp-enable-time", + &pdata->ocp_enable_time); + + return rc; +} + +static struct of_device_id spmi_match_table[]; + +#define MAX_NAME_LEN 127 + +static int __devinit qpnp_regulator_probe(struct spmi_device *spmi) +{ + struct qpnp_regulator_platform_data *pdata; + struct qpnp_regulator *vreg; + struct regulator_desc *rdesc; + struct qpnp_regulator_platform_data of_pdata; + struct regulator_init_data *init_data; + char *reg_name; + int rc; + bool is_dt; + + vreg = kzalloc(sizeof(struct qpnp_regulator), GFP_KERNEL); + if (!vreg) { + dev_err(&spmi->dev, "%s: Can't allocate qpnp_regulator\n", + __func__); + return -ENOMEM; + } + + is_dt = of_match_device(spmi_match_table, &spmi->dev); + + /* Check if device tree is in use. */ + if (is_dt) { + init_data = of_get_regulator_init_data(&spmi->dev); + if (!init_data) { + dev_err(&spmi->dev, "%s: unable to allocate memory\n", + __func__); + kfree(vreg); + return -ENOMEM; + } + memset(&of_pdata, 0, + sizeof(struct qpnp_regulator_platform_data)); + memcpy(&of_pdata.init_data, init_data, + sizeof(struct regulator_init_data)); + + if (of_get_property(spmi->dev.of_node, "parent-supply", NULL)) + of_pdata.init_data.supply_regulator = "parent"; + + rc = qpnp_regulator_get_dt_config(spmi, &of_pdata); + if (rc) { + dev_err(&spmi->dev, "%s: DT parsing failed, rc=%d\n", + __func__, rc); + kfree(vreg); + return -ENOMEM; + } + + pdata = &of_pdata; + } else { + pdata = spmi->dev.platform_data; + } + + if (pdata == NULL) { + dev_err(&spmi->dev, "%s: no platform data specified\n", + __func__); + kfree(vreg); + return -EINVAL; + } + + vreg->spmi_dev = spmi; + vreg->prev_write_count = -1; + vreg->write_count = 0; + vreg->base_addr = pdata->base_addr; + vreg->enable_time = pdata->enable_time; + vreg->system_load = pdata->system_load; + vreg->ocp_enable = pdata->ocp_enable; + vreg->ocp_enable_time = pdata->ocp_enable_time; + + rdesc = &vreg->rdesc; + rdesc->id = spmi->ctrl->nr; + rdesc->owner = THIS_MODULE; + rdesc->type = REGULATOR_VOLTAGE; + + reg_name = kzalloc(strnlen(pdata->init_data.constraints.name, + MAX_NAME_LEN) + 1, GFP_KERNEL); + if (!reg_name) { + dev_err(&spmi->dev, "%s: Can't allocate regulator name\n", + __func__); + kfree(vreg); + return -ENOMEM; + } + strlcpy(reg_name, pdata->init_data.constraints.name, + strnlen(pdata->init_data.constraints.name, MAX_NAME_LEN) + 1); + rdesc->name = reg_name; + + dev_set_drvdata(&spmi->dev, vreg); + + rc = qpnp_regulator_match(vreg); + if (rc) { + vreg_err(vreg, "regulator type unknown, rc=%d\n", rc); + goto bail; + } + + if (is_dt && rdesc->ops) { + /* Fill in ops and mode masks when using device tree. */ + if (rdesc->ops->enable) + pdata->init_data.constraints.valid_ops_mask + |= REGULATOR_CHANGE_STATUS; + if (rdesc->ops->get_voltage) + pdata->init_data.constraints.valid_ops_mask + |= REGULATOR_CHANGE_VOLTAGE; + if (rdesc->ops->get_mode) { + pdata->init_data.constraints.valid_ops_mask + |= REGULATOR_CHANGE_MODE + | REGULATOR_CHANGE_DRMS; + pdata->init_data.constraints.valid_modes_mask + = REGULATOR_MODE_NORMAL | REGULATOR_MODE_IDLE; + } + } + + rc = qpnp_regulator_init_registers(vreg, pdata); + if (rc) { + vreg_err(vreg, "common initialization failed, rc=%d\n", rc); + goto bail; + } + + vreg->rdev = regulator_register(rdesc, &spmi->dev, + &(pdata->init_data), vreg, spmi->dev.of_node); + if (IS_ERR(vreg->rdev)) { + rc = PTR_ERR(vreg->rdev); + vreg_err(vreg, "regulator_register failed, rc=%d\n", rc); + goto bail; + } + + qpnp_vreg_show_state(vreg->rdev, QPNP_REGULATOR_ACTION_INIT); + + return 0; + +bail: + if (rc) + vreg_err(vreg, "probe failed, rc=%d\n", rc); + + kfree(vreg->rdesc.name); + kfree(vreg); + + return rc; +} + +static int __devexit qpnp_regulator_remove(struct spmi_device *spmi) +{ + struct qpnp_regulator *vreg; + + vreg = dev_get_drvdata(&spmi->dev); + dev_set_drvdata(&spmi->dev, NULL); + + if (vreg) { + regulator_unregister(vreg->rdev); + kfree(vreg->rdesc.name); + kfree(vreg); + } + + return 0; +} + +static struct of_device_id spmi_match_table[] = { + { .compatible = QPNP_REGULATOR_DRIVER_NAME, }, + {} +}; + +static const struct spmi_device_id qpnp_regulator_id[] = { + { QPNP_REGULATOR_DRIVER_NAME, 0 }, + { } +}; +MODULE_DEVICE_TABLE(spmi, qpnp_regulator_id); + +static struct spmi_driver qpnp_regulator_driver = { + .driver = { + .name = QPNP_REGULATOR_DRIVER_NAME, + .of_match_table = spmi_match_table, + .owner = THIS_MODULE, + }, + .probe = qpnp_regulator_probe, + .remove = __devexit_p(qpnp_regulator_remove), + .id_table = qpnp_regulator_id, +}; + +/* + * Pre-compute the number of set points available for each regulator type to + * avoid unnecessary calculations later in runtime. + */ +static void qpnp_regulator_set_point_init(void) +{ + struct qpnp_voltage_set_points **set_points; + int i, j, temp; + + set_points = all_set_points; + + for (i = 0; i < ARRAY_SIZE(all_set_points); i++) { + temp = 0; + for (j = 0; j < all_set_points[i]->count; j++) { + all_set_points[i]->range[j].n_voltages + = (all_set_points[i]->range[j].max_uV + - all_set_points[i]->range[j].set_point_min_uV) + / all_set_points[i]->range[j].step_uV + 1; + temp += all_set_points[i]->range[j].n_voltages; + } + all_set_points[i]->n_voltages = temp; + } +} + +/** + * qpnp_regulator_init() - register spmi driver for qpnp-regulator + * + * This initialization function should be called in systems in which driver + * registration ordering must be controlled precisely. + */ +int __init qpnp_regulator_init(void) +{ + static bool has_registered; + + if (has_registered) + return 0; + else + has_registered = true; + + qpnp_regulator_set_point_init(); + + return spmi_driver_register(&qpnp_regulator_driver); +} +EXPORT_SYMBOL(qpnp_regulator_init); + +static void __exit qpnp_regulator_exit(void) +{ + spmi_driver_unregister(&qpnp_regulator_driver); +} + +MODULE_DESCRIPTION("QPNP PMIC regulator driver"); +MODULE_LICENSE("GPL v2"); + +arch_initcall(qpnp_regulator_init); +module_exit(qpnp_regulator_exit); diff --git a/include/linux/regulator/qpnp-regulator.h b/include/linux/regulator/qpnp-regulator.h new file mode 100644 index 00000000000..ca8ccd7dab1 --- /dev/null +++ b/include/linux/regulator/qpnp-regulator.h @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __REGULATOR_QPNP_REGULATOR_H__ +#define __REGULATOR_QPNP_REGULATOR_H__ + +#include + +#define QPNP_REGULATOR_DRIVER_NAME "qcom,qpnp-regulator" + +/* Pin control enable input pins. */ +#define QPNP_REGULATOR_PIN_CTRL_ENABLE_NONE 0x00 +#define QPNP_REGULATOR_PIN_CTRL_ENABLE_EN0 0x01 +#define QPNP_REGULATOR_PIN_CTRL_ENABLE_EN1 0x02 +#define QPNP_REGULATOR_PIN_CTRL_ENABLE_EN2 0x04 +#define QPNP_REGULATOR_PIN_CTRL_ENABLE_EN3 0x08 +#define QPNP_REGULATOR_PIN_CTRL_ENABLE_HW_DEFAULT 0x10 + +/* Pin control high power mode input pins. */ +#define QPNP_REGULATOR_PIN_CTRL_HPM_NONE 0x00 +#define QPNP_REGULATOR_PIN_CTRL_HPM_EN0 0x01 +#define QPNP_REGULATOR_PIN_CTRL_HPM_EN1 0x02 +#define QPNP_REGULATOR_PIN_CTRL_HPM_EN2 0x04 +#define QPNP_REGULATOR_PIN_CTRL_HPM_EN3 0x08 +#define QPNP_REGULATOR_PIN_CTRL_HPM_SLEEP_B 0x10 +#define QPNP_REGULATOR_PIN_CTRL_HPM_HW_DEFAULT 0x20 + +/* + * Used with enable parameters to specify that hardware default register values + * should be left unaltered. + */ +#define QPNP_REGULATOR_DISABLE 0 +#define QPNP_REGULATOR_ENABLE 1 +#define QPNP_REGULATOR_USE_HW_DEFAULT 2 + +/* Soft start strength of a voltage switch type regulator */ +enum qpnp_vs_soft_start_str { + QPNP_VS_SOFT_START_STR_0P05_UA, + QPNP_VS_SOFT_START_STR_0P25_UA, + QPNP_VS_SOFT_START_STR_0P55_UA, + QPNP_VS_SOFT_START_STR_0P75_UA, + QPNP_VS_SOFT_START_STR_HW_DEFAULT, +}; + +/* Current limit of a boost type regulator */ +enum qpnp_boost_current_limit { + QPNP_BOOST_CURRENT_LIMIT_300_MA, + QPNP_BOOST_CURRENT_LIMIT_600_MA, + QPNP_BOOST_CURRENT_LIMIT_900_MA, + QPNP_BOOST_CURRENT_LIMIT_1200_MA, + QPNP_BOOST_CURRENT_LIMIT_1500_MA, + QPNP_BOOST_CURRENT_LIMIT_1800_MA, + QPNP_BOOST_CURRENT_LIMIT_2100_MA, + QPNP_BOOST_CURRENT_LIMIT_2400_MA, + QPNP_BOOST_CURRENT_LIMIT_HW_DEFAULT, +}; + +/** + * struct qpnp_regulator_platform_data - qpnp-regulator initialization data + * @init_data: regulator constraints + * @pull_down_enable: 1 = Enable output pull down resistor when the + * regulator is disabled + * 0 = Disable pull down resistor + * QPNP_REGULATOR_USE_HW_DEFAULT = do not modify + * pull down state + * @pin_ctrl_enable: Bit mask specifying which hardware pins should be + * used to enable the regulator, if any + * Value should be an ORing of + * QPNP_REGULATOR_PIN_CTRL_ENABLE_* constants. If + * the bit specified by + * QPNP_REGULATOR_PIN_CTRL_ENABLE_HW_DEFAULT is + * set, then pin control enable hardware registers + * will not be modified. + * @pin_ctrl_hpm: Bit mask specifying which hardware pins should be + * used to force the regulator into high power + * mode, if any + * Value should be an ORing of + * QPNP_REGULATOR_PIN_CTRL_HPM_* constants. If + * the bit specified by + * QPNP_REGULATOR_PIN_CTRL_HPM_HW_DEFAULT is + * set, then pin control mode hardware registers + * will not be modified. + * @system_load: Load in uA present on regulator that is not captured + * by any consumer request + * @enable_time: Time in us to delay after enabling the regulator + * @ocp_enable: 1 = Enable over current protection (OCP) for voltage + * switch type regulators so that they latch off + * automatically when over current is detected + * 0 = Disable OCP + * QPNP_REGULATOR_USE_HW_DEFAULT = do not modify + * OCP state + * @boost_current_limit: This parameter sets the current limit of boost type + * regulators. Its value should be one of + * QPNP_BOOST_CURRENT_LIMIT_*. If its value is + * QPNP_BOOST_CURRENT_LIMIT_HW_DEFAULT, then the + * boost current limit will be left at its default + * hardware value. + * @soft_start_enable: 1 = Enable soft start for LDO and voltage switch + * type regulators so that output voltage slowly + * ramps up when the regulator is enabled + * 0 = Disable soft start + * QPNP_REGULATOR_USE_HW_DEFAULT = do not modify + * soft start state + * @vs_soft_start_strength: This parameter sets the soft start strength for + * voltage switch type regulators. Its value + * should be one of QPNP_VS_SOFT_START_STR_*. If + * its value is QPNP_VS_SOFT_START_STR_HW_DEFAULT, + * then the soft start strength will be left at its + * default hardware value. + * @ocp_enable_time: Time to delay in us between enabling a switch and + * subsequently enabling over current protection + * (OCP) for the switch + * @auto_mode_enable: 1 = Enable automatic hardware selection of regulator + * mode (HPM vs LPM). Auto mode is not available + * on boost type regulators + * 0 = Disable auto mode selection + * QPNP_REGULATOR_USE_HW_DEFAULT = do not modify + * auto mode state + * @bypass_mode_enable: 1 = Enable bypass mode for an LDO type regulator so + * that it acts like a switch and simply outputs + * its input voltage + * 0 = Do not enable bypass mode + * QPNP_REGULATOR_USE_HW_DEFAULT = do not modify + * bypass mode state + * @base_addr: SMPI base address for the regulator peripheral + */ +struct qpnp_regulator_platform_data { + struct regulator_init_data init_data; + int pull_down_enable; + unsigned pin_ctrl_enable; + unsigned pin_ctrl_hpm; + int system_load; + int enable_time; + int ocp_enable; + enum qpnp_boost_current_limit boost_current_limit; + int soft_start_enable; + enum qpnp_vs_soft_start_str vs_soft_start_strength; + int ocp_enable_time; + int auto_mode_enable; + int bypass_mode_enable; + u16 base_addr; +}; + +#ifdef CONFIG_REGULATOR_QPNP + +/** + * qpnp_regulator_init() - register spmi driver for qpnp-regulator + * + * This initialization function should be called in systems in which driver + * registration ordering must be controlled precisely. + */ +int __init qpnp_regulator_init(void); + +#else + +static inline int __init qpnp_regulator_init(void) +{ + return -ENODEV; +} + +#endif /* CONFIG_REGULATOR_QPNP */ + +#endif