pmos/device/testing/linux-samsung-coreprimevelte-mainline/0018-DONOTMERGE-add-downstream-regulator-driver.patch
Duje Mihanović 7a80409e7e
linux-samsung-coreprimevelte-mainline: upgrade to 6.6_rc3 (MR 4440)
Also enables touchscreen with downstream PMIC driver.
2023-10-01 22:18:18 -07:00

2177 lines
65 KiB
Diff

From 511728c3b8f0957fb0e6a3f31b447428c852dbc1 Mon Sep 17 00:00:00 2001
From: Karel Balej <balejk@matfyz.cz>
Date: Sun, 24 Sep 2023 12:40:26 +0200
Subject: [PATCH] DONOTMERGE: add downstream regulator driver
---
drivers/regulator/88pm88x-buck.c | 714 ++++++++++++++++++++++++++++++
drivers/regulator/88pm88x-ldo.c | 726 +++++++++++++++++++++++++++++++
drivers/regulator/88pm88x-vr.c | 666 ++++++++++++++++++++++++++++
drivers/regulator/Kconfig | 10 +
drivers/regulator/Makefile | 3 +
5 files changed, 2119 insertions(+)
create mode 100644 drivers/regulator/88pm88x-buck.c
create mode 100644 drivers/regulator/88pm88x-ldo.c
create mode 100644 drivers/regulator/88pm88x-vr.c
diff --git a/drivers/regulator/88pm88x-buck.c b/drivers/regulator/88pm88x-buck.c
new file mode 100644
index 000000000000..7546e0fb9d87
--- /dev/null
+++ b/drivers/regulator/88pm88x-buck.c
@@ -0,0 +1,714 @@
+/*
+ * Buck driver for Marvell 88PM88X
+ *
+ * Copyright (C) 2014 Marvell International Ltd.
+ * Yi Zhang <yizhang@marvell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/regmap.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/mfd/88pm88x.h>
+#include <linux/mfd/88pm886.h>
+#include <linux/mfd/88pm880.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/regulator/of_regulator.h>
+
+/* max current in sleep */
+#define MAX_SLEEP_CURRENT 5000
+
+/* BUCK enable2 register offset relative to enable1 register */
+#define PM88X_BUCK_EN2_OFF (0x06)
+/* ------------- 88pm886 buck registers --------------- */
+
+/* buck voltage */
+#define PM886_BUCK2_VOUT (0xb3)
+#define PM886_BUCK3_VOUT (0xc1)
+#define PM886_BUCK4_VOUT (0xcf)
+#define PM886_BUCK5_VOUT (0xdd)
+
+/* set buck sleep voltage */
+#define PM886_BUCK1_SET_SLP (0xa3)
+#define PM886_BUCK2_SET_SLP (0xb1)
+#define PM886_BUCK3_SET_SLP (0xbf)
+#define PM886_BUCK4_SET_SLP (0xcd)
+#define PM886_BUCK5_SET_SLP (0xdb)
+
+/* control section */
+#define PM886_BUCK_EN (0x08)
+
+/* ------------- 88pm880 buck registers --------------- */
+/* buck voltage */
+#define PM880_BUCK2_VOUT (0x58)
+#define PM880_BUCK3_VOUT (0x70)
+#define PM880_BUCK4_VOUT (0x88)
+#define PM880_BUCK5_VOUT (0x98)
+#define PM880_BUCK6_VOUT (0xa8)
+
+/* set buck sleep voltage */
+#define PM880_BUCK1_SET_SLP (0x27)
+#define PM880_BUCK1A_SET_SLP (0x27)
+#define PM880_BUCK1B_SET_SLP (0x3c)
+
+#define PM880_BUCK2_SET_SLP (0x56)
+#define PM880_BUCK3_SET_SLP (0x6e)
+#define PM880_BUCK4_SET_SLP (0x86)
+#define PM880_BUCK5_SET_SLP (0x96)
+#define PM880_BUCK6_SET_SLP (0xa6)
+#define PM880_BUCK7_SET_SLP (0xb6)
+
+/* control section */
+#define PM880_BUCK_EN (0x08)
+
+/*
+ * vreg - the buck regs string.
+ * ebit - the bit number in the enable register.
+ * amax - the current
+ * Buck has 2 kinds of voltage steps. It is easy to find voltage by ranges,
+ * not the constant voltage table.
+ */
+#define PM88X_BUCK(_pmic, vreg, ebit, amax, volt_ranges, n_volt, slp_en_msk, slp_en_off) \
+{ \
+ .desc = { \
+ .name = #vreg, \
+ .ops = &pm88x_volt_buck_ops, \
+ .type = REGULATOR_VOLTAGE, \
+ .id = _pmic##_ID_##vreg, \
+ .owner = THIS_MODULE, \
+ .n_voltages = n_volt, \
+ .linear_ranges = volt_ranges, \
+ .n_linear_ranges = ARRAY_SIZE(volt_ranges), \
+ .vsel_reg = _pmic##_##vreg##_VOUT, \
+ .vsel_mask = 0x7f, \
+ .enable_reg = _pmic##_BUCK_EN, \
+ .enable_mask = 1 << (ebit), \
+ }, \
+ .max_ua = (amax), \
+ .sleep_vsel_reg = _pmic##_##vreg##_SET_SLP, \
+ .sleep_vsel_mask = 0x7f, \
+ .sleep_enable_reg = _pmic##_##vreg##_SLP_CTRL, \
+ .sleep_enable_mask = (slp_en_msk << slp_en_off), \
+ .sleep_enable_off = (slp_en_off), \
+}
+
+#define PM886_BUCK(vreg, ebit, amax, volt_ranges, n_volt) \
+ PM88X_BUCK(PM886, vreg, ebit, amax, volt_ranges, n_volt, 0x3, 4)
+
+#define PM880_BUCK(vreg, ebit, amax, volt_ranges, n_volt) \
+ PM88X_BUCK(PM880, vreg, ebit, amax, volt_ranges, n_volt, 0x3, 4)
+
+#define PM886_BUCK_AUDIO(vreg, ebit, amax, volt_ranges, n_volt) \
+ PM88X_BUCK(PM886, vreg, ebit, amax, volt_ranges, n_volt, 0x1, 7)
+
+#define PM880_BUCK_AUDIO(vreg, ebit, amax, volt_ranges, n_volt) \
+ PM88X_BUCK(PM880, vreg, ebit, amax, volt_ranges, n_volt, 0x1, 7)
+
+/*
+ * 88pm886 buck1 and 88pm880 buck1. both have dvc function
+ * from 0x00 to 0x4F: step is 12.5mV, range is from 0.6V to 1.6V
+ * from 0x50 to 0x3F step is 50mV, range is from 1.6V to 1.8V
+ */
+static const struct regulator_linear_range buck_volt_range1[] = {
+ REGULATOR_LINEAR_RANGE(600000, 0, 0x4f, 12500),
+ REGULATOR_LINEAR_RANGE(1600000, 0x50, 0x54, 50000),
+};
+
+/*
+ * 88pm886 buck 2-5, and 88pm880 buck 2-7
+ * from 0x00 to 0x4F VOUT step is 12.5mV, range is from 0.6V to 1.6V
+ * from 0x50 to 0x72 step is 50mV, range is from 1.6V to 3.3V
+ */
+static const struct regulator_linear_range buck_volt_range2[] = {
+ REGULATOR_LINEAR_RANGE(600000, 0, 0x4f, 12500),
+ REGULATOR_LINEAR_RANGE(1600000, 0x50, 0x72, 50000),
+};
+
+struct pm88x_buck_info {
+ struct regulator_desc desc;
+ int max_ua;
+ u8 sleep_enable_mask;
+ u8 sleep_enable_reg;
+ u8 sleep_enable_off;
+ u8 sleep_vsel_reg;
+ u8 sleep_vsel_mask;
+};
+
+struct pm88x_regulators {
+ struct regulator_dev *rdev;
+ struct pm88x_chip *chip;
+ struct regmap *map;
+};
+
+struct pm88x_buck_print {
+ char name[15];
+ char enable[15];
+ char slp_mode[15];
+ char set_slp[15];
+ char volt[10];
+ char audio_en[15];
+ char audio[10];
+};
+
+struct pm88x_buck_extra {
+ const char *name;
+ bool dvc;
+ u8 audio_enable_reg;
+ u8 audio_enable_mask;
+ u8 audio_enable_off;
+ u8 audio_vsel_reg;
+ u8 audio_vsel_mask;
+};
+
+#define PM88X_BUCK_EXTRA(_name, _dvc, _areg, _amsk, _aoff, _vreg, _vmsk) \
+{ \
+ .name = _name, \
+ .dvc = _dvc, \
+ .audio_enable_reg = _areg, \
+ .audio_enable_mask = _amsk, \
+ .audio_enable_off = _aoff, \
+ .audio_vsel_reg = _vreg, \
+ .audio_vsel_mask = _vmsk \
+}
+
+static struct pm88x_buck_extra pm886_buck_extra_info[] = {
+ PM88X_BUCK_EXTRA("BUCK1", 1, 0xa4, 0x80, 0x07, 0xa4, 0x7f),
+ PM88X_BUCK_EXTRA("BUCK2", 0, 0xb2, 0x80, 0x07, 0xb2, 0x7f),
+ PM88X_BUCK_EXTRA("BUCK3", 0, 0xc0, 0x80, 0x07, 0xc0, 0x7f),
+ PM88X_BUCK_EXTRA("BUCK4", 0, 0, 0, 0, 0, 0),
+ PM88X_BUCK_EXTRA("BUCK5", 0, 0, 0, 0, 0, 0),
+};
+
+static struct pm88x_buck_extra pm880_buck_extra_info[] = {
+ PM88X_BUCK_EXTRA("BUCK1A", 1, 0x27, 0x80, 0x07, 0x27, 0x7f),
+ PM88X_BUCK_EXTRA("BUCK2", 0, 0x57, 0x80, 0x07, 0x57, 0x7f),
+ PM88X_BUCK_EXTRA("BUCK3", 0, 0x6f, 0x80, 0x07, 0x6f, 0x7f),
+ PM88X_BUCK_EXTRA("BUCK4", 0, 0, 0, 0, 0, 0),
+ PM88X_BUCK_EXTRA("BUCK5", 0, 0, 0, 0, 0, 0),
+ PM88X_BUCK_EXTRA("BUCK6", 0, 0, 0, 0, 0, 0),
+ PM88X_BUCK_EXTRA("BUCK7", 1, 0, 0, 0, 0, 0),
+};
+
+#define BUCK_OFF (0x0)
+#define BUCK_ACTIVE_VOLT_SLP (0x1)
+#define BUCK_SLP_VOLT_SLP (0x2)
+#define BUCK_ACTIVE_VOLT_ACTIVE (0x3)
+#define BUCK_AUDIO_MODE_EN (0x1)
+
+int pm88x_buck_set_suspend_mode(struct regulator_dev *rdev, unsigned int mode)
+{
+ struct pm88x_buck_info *info = rdev_get_drvdata(rdev);
+ u8 val;
+ int ret, rid;
+
+ if (!info)
+ return -EINVAL;
+
+ rid = rdev_get_id(rdev);
+ /* we enabled buck1 audio mode enable/disable for 88pm886 and 88pm880 */
+ if (rid == PM880_ID_BUCK1A) {
+ switch (mode) {
+ case REGULATOR_MODE_NORMAL:
+ val = 0;
+ break;
+ case REGULATOR_MODE_IDLE:
+ val = BUCK_AUDIO_MODE_EN;
+ break;
+ default:
+ return -EINVAL;
+ }
+ } else {
+ switch (mode) {
+ case REGULATOR_MODE_NORMAL:
+ /* regulator will be active with normal voltage */
+ val = BUCK_ACTIVE_VOLT_ACTIVE;
+ break;
+ case REGULATOR_MODE_IDLE:
+ /* regulator will be in sleep with sleep voltage */
+ val = BUCK_SLP_VOLT_SLP;
+ break;
+ case REGULATOR_MODE_STANDBY:
+ /* regulator will be off */
+ val = BUCK_OFF;
+ break;
+ default:
+ /* regulator will be active with sleep voltage */
+ val = BUCK_ACTIVE_VOLT_SLP;
+ break;
+ }
+ }
+
+ ret = regmap_update_bits(rdev->regmap, info->sleep_enable_reg,
+ info->sleep_enable_mask, (val << info->sleep_enable_off));
+ return ret;
+}
+
+static unsigned int pm88x_buck_get_optimum_mode(struct regulator_dev *rdev,
+ int input_uV, int output_uV,
+ int output_uA)
+{
+ struct pm88x_buck_info *info = rdev_get_drvdata(rdev);
+ if (!info)
+ return REGULATOR_MODE_IDLE;
+
+ if (output_uA < 0) {
+ dev_err(rdev_get_dev(rdev), "current needs to be > 0.\n");
+ return REGULATOR_MODE_IDLE;
+ }
+
+ return (output_uA < MAX_SLEEP_CURRENT) ?
+ REGULATOR_MODE_IDLE : REGULATOR_MODE_NORMAL;
+}
+
+static int pm88x_buck_get_current_limit(struct regulator_dev *rdev)
+{
+ struct pm88x_buck_info *info = rdev_get_drvdata(rdev);
+ if (!info)
+ return 0;
+ return info->max_ua;
+}
+
+static int pm88x_buck_set_suspend_voltage(struct regulator_dev *rdev, int uv)
+{
+ int ret, sel;
+ struct pm88x_buck_info *info = rdev_get_drvdata(rdev);
+ if (!info || !info->desc.ops)
+ return -EINVAL;
+ if (!info->desc.ops->set_suspend_mode)
+ return 0;
+ /*
+ * two steps:
+ * 1) set the suspend voltage to *_set_slp registers
+ * 2) set regulator mode via set_suspend_mode() interface to enable output
+ */
+ /* the suspend voltage mapping is the same as active */
+ sel = regulator_map_voltage_linear_range(rdev, uv, uv);
+ if (sel < 0)
+ return -EINVAL;
+
+ sel <<= ffs(info->sleep_vsel_mask) - 1;
+
+ ret = regmap_update_bits(rdev->regmap, info->sleep_vsel_reg,
+ info->sleep_vsel_mask, sel);
+ if (ret < 0)
+ return -EINVAL;
+
+ /* TODO: do we need this? */
+ ret = pm88x_buck_set_suspend_mode(rdev, REGULATOR_MODE_IDLE);
+ return ret;
+}
+
+/*
+ * about the get_optimum_mode()/set_suspend_mode()/set_suspend_voltage() interface:
+ * - 88pm88x has two sets of registers to set and enable/disable regulators
+ * in active and suspend(sleep) status:
+ * the following focues on the sleep part:
+ * - there are two control bits: 00-disable,
+ * 01/10-use sleep voltage,
+ * 11-use active voltage,
+ *- in most of the scenario, these registers are configured when the whole PMIC
+ * initialized, when the system enters into suspend(sleep) mode, the regulator
+ * works according to the setting or disabled;
+ *- there is also case that the device driver needs to:
+ * - set the sleep voltage;
+ * - choose to use sleep voltage or active voltage depends on the load;
+ * so:
+ * set_suspend_voltage() is used to manipulate the registers to set sleep volt;
+ * set_suspend_mode() is used to switch between sleep voltage and active voltage
+ * get_optimum_mode() is used to get right mode
+ */
+static struct regulator_ops pm88x_volt_buck_ops = {
+ .list_voltage = regulator_list_voltage_linear_range,
+ .map_voltage = regulator_map_voltage_linear_range,
+ .set_voltage_sel = regulator_set_voltage_sel_regmap,
+ .get_voltage_sel = regulator_get_voltage_sel_regmap,
+ .enable = regulator_enable_regmap,
+ .disable = regulator_disable_regmap,
+ .is_enabled = regulator_is_enabled_regmap,
+ .get_current_limit = pm88x_buck_get_current_limit,
+ .get_optimum_mode = pm88x_buck_get_optimum_mode,
+ .set_suspend_mode = pm88x_buck_set_suspend_mode,
+ .set_suspend_voltage = pm88x_buck_set_suspend_voltage,
+};
+
+/* The array is indexed by id(PM886_ID_BUCK*) */
+static struct pm88x_buck_info pm886_buck_configs[] = {
+ PM886_BUCK(BUCK1, 0, 3000000, buck_volt_range1, 0x55),
+ PM886_BUCK(BUCK2, 1, 1200000, buck_volt_range2, 0x73),
+ PM886_BUCK(BUCK3, 2, 1200000, buck_volt_range2, 0x73),
+ PM886_BUCK(BUCK4, 3, 1200000, buck_volt_range2, 0x73),
+ PM886_BUCK(BUCK5, 4, 1200000, buck_volt_range2, 0x73),
+};
+
+/* The array is indexed by id(PM880_ID_BUCK*) */
+static struct pm88x_buck_info pm880_buck_configs[] = {
+ PM880_BUCK_AUDIO(BUCK1A, 0, 3000000, buck_volt_range1, 0x55),
+ PM880_BUCK(BUCK2, 2, 1200000, buck_volt_range2, 0x73),
+ PM880_BUCK(BUCK3, 3, 1200000, buck_volt_range2, 0x73),
+ PM880_BUCK(BUCK4, 4, 1200000, buck_volt_range2, 0x73),
+ PM880_BUCK(BUCK5, 5, 1200000, buck_volt_range2, 0x73),
+ PM880_BUCK(BUCK6, 6, 1200000, buck_volt_range2, 0x73),
+ PM880_BUCK(BUCK7, 7, 1200000, buck_volt_range2, 0x73),
+};
+
+#define PM88X_BUCK_OF_MATCH(_pmic, id, comp, label) \
+ { \
+ .compatible = comp, \
+ .data = &_pmic##_buck_configs[id##_##label], \
+ }
+#define PM886_BUCK_OF_MATCH(comp, label) \
+ PM88X_BUCK_OF_MATCH(pm886, PM886_ID, comp, label)
+
+#define PM880_BUCK_OF_MATCH(comp, label) \
+ PM88X_BUCK_OF_MATCH(pm880, PM880_ID, comp, label)
+
+static const struct of_device_id pm88x_bucks_of_match[] = {
+ PM886_BUCK_OF_MATCH("marvell,88pm886-buck1", BUCK1),
+ PM886_BUCK_OF_MATCH("marvell,88pm886-buck2", BUCK2),
+ PM886_BUCK_OF_MATCH("marvell,88pm886-buck3", BUCK3),
+ PM886_BUCK_OF_MATCH("marvell,88pm886-buck4", BUCK4),
+ PM886_BUCK_OF_MATCH("marvell,88pm886-buck5", BUCK5),
+
+ PM880_BUCK_OF_MATCH("marvell,88pm880-buck1a", BUCK1A),
+ PM880_BUCK_OF_MATCH("marvell,88pm880-buck2", BUCK2),
+ PM880_BUCK_OF_MATCH("marvell,88pm880-buck3", BUCK3),
+ PM880_BUCK_OF_MATCH("marvell,88pm880-buck4", BUCK4),
+ PM880_BUCK_OF_MATCH("marvell,88pm880-buck5", BUCK5),
+ PM880_BUCK_OF_MATCH("marvell,88pm880-buck6", BUCK6),
+ PM880_BUCK_OF_MATCH("marvell,88pm880-buck7", BUCK7),
+};
+
+/*
+ * The function convert the buck voltage register value
+ * to a real voltage value (in uV) according to the voltage table.
+ */
+static int pm88x_get_vbuck_vol(unsigned int val, struct pm88x_buck_info *info)
+{
+ const struct regulator_linear_range *range;
+ int i, volt = -EINVAL;
+
+ /* get the voltage via the register value */
+ for (i = 0; i < info->desc.n_linear_ranges; i++) {
+ range = &info->desc.linear_ranges[i];
+ if (!range)
+ return -EINVAL;
+
+ if (val >= range->min_sel && val <= range->max_sel) {
+ volt = (val - range->min_sel) * range->uV_step + range->min_uV;
+ break;
+ }
+ }
+ return volt;
+}
+
+/* The function check if the regulator register is configured to enable/disable */
+static int pm88x_check_en(struct pm88x_chip *chip, unsigned int reg, unsigned int mask,
+ unsigned int reg2)
+{
+ struct regmap *map = chip->buck_regmap;
+ int ret, value;
+ unsigned int enable1, enable2;
+
+ ret = regmap_read(map, reg, &enable1);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(map, reg2, &enable2);
+ if (ret < 0)
+ return ret;
+
+ value = (enable1 | enable2) & mask;
+
+ return value;
+}
+
+/* The function check the regulator sleep mode as configured in his register */
+static int pm88x_check_slp_mode(struct regmap *map, unsigned int reg, int off)
+{
+ int ret;
+ unsigned int slp_mode;
+
+ ret = regmap_read(map, reg, &slp_mode);
+ if (ret < 0)
+ return ret;
+
+ slp_mode = (slp_mode >> off) & 0x3;
+
+ return slp_mode;
+}
+
+/* The function return the value in the regulator voltage register */
+static unsigned int pm88x_check_vol(struct regmap *map, unsigned int reg, unsigned int mask)
+{
+ int ret;
+ unsigned int vol_val;
+
+ ret = regmap_bulk_read(map, reg, &vol_val, 1);
+ if (ret < 0)
+ return ret;
+
+ /* mask and shift the relevant value from the register */
+ vol_val = (vol_val & mask) >> (ffs(mask) - 1);
+
+ return vol_val;
+}
+
+static int pm88x_update_print(struct pm88x_chip *chip, struct pm88x_buck_info *info,
+ struct pm88x_buck_extra *extra, struct pm88x_buck_print *print_temp,
+ int index, int buck_num)
+{
+ int ret, volt;
+ struct regmap *map = chip->buck_regmap;
+ char *slp_mode_str[] = {"off", "active_slp", "sleep", "active"};
+ int slp_mode_num = ARRAY_SIZE(slp_mode_str);
+
+ sprintf(print_temp->name, "%s", info[index].desc.name);
+
+ /* check enable/disable */
+ ret = pm88x_check_en(chip, info[index].desc.enable_reg, info[index].desc.enable_mask,
+ info[index].desc.enable_reg + PM88X_BUCK_EN2_OFF);
+ if (ret < 0)
+ return ret;
+ else if (ret)
+ strcpy(print_temp->enable, "enable");
+ else
+ strcpy(print_temp->enable, "disable");
+
+ if (!strcmp(print_temp->name, "BUCK1A")) {
+ sprintf(print_temp->slp_mode, " VR");
+ sprintf(print_temp->set_slp, " VR");
+ } else {
+ /* check sleep mode */
+ ret = pm88x_check_slp_mode(map, info[index].sleep_enable_reg,
+ info[index].sleep_enable_off);
+ if (ret < 0)
+ return ret;
+ if (ret < slp_mode_num)
+ strcpy(print_temp->slp_mode, slp_mode_str[ret]);
+ else
+ strcpy(print_temp->slp_mode, "unknown");
+
+ /* print sleep voltage */
+ ret = pm88x_check_vol(map, info[index].sleep_vsel_reg, info[index].sleep_vsel_mask);
+ if (ret < 0)
+ return ret;
+
+ volt = pm88x_get_vbuck_vol(ret, &info[index]);
+ if (volt < 0)
+ return volt;
+ else
+ sprintf(print_temp->set_slp, "%4d", volt/1000);
+ }
+
+ /* print active voltage(s) */
+ if (extra[index].dvc) {
+ sprintf(print_temp->volt, " DVC ");
+ } else {
+ ret = pm88x_check_vol(map, info[index].desc.vsel_reg,
+ info[index].desc.vsel_mask);
+ if (ret < 0)
+ return ret;
+
+ volt = pm88x_get_vbuck_vol(ret, &info[index]);
+ if (volt < 0)
+ return volt;
+ else
+ sprintf(print_temp->volt, "%4d", volt/1000);
+ }
+
+ /* print audio voltage */
+ if (extra[index].audio_enable_reg) {
+ ret = pm88x_check_en(chip, extra[index].audio_enable_reg,
+ extra[index].audio_enable_mask,
+ extra[index].audio_enable_reg);
+ if (ret < 0)
+ return ret;
+ else if (ret)
+ strcpy(print_temp->audio_en, "enable");
+ else
+ strcpy(print_temp->audio_en, "disable");
+
+ ret = pm88x_check_vol(map, extra[index].audio_vsel_reg,
+ extra[index].audio_vsel_mask);
+ if (ret < 0)
+ return ret;
+
+ volt = pm88x_get_vbuck_vol(ret, &info[index]);
+ if (volt < 0)
+ return volt;
+ else
+ sprintf(print_temp->audio, "%4d", volt/1000);
+ } else {
+ strcpy(print_temp->audio_en, " - ");
+ sprintf(print_temp->audio, " -");
+ }
+
+ return 0;
+}
+
+int pm88x_display_buck(struct pm88x_chip *chip, char *buf)
+{
+ struct pm88x_buck_print *print_temp;
+ struct pm88x_buck_info *info;
+ struct pm88x_buck_extra *extra;
+ int buck_num, i, len = 0;
+ ssize_t ret;
+
+ switch (chip->type) {
+ case PM886:
+ info = pm886_buck_configs;
+ extra = pm886_buck_extra_info;
+ buck_num = ARRAY_SIZE(pm886_buck_configs);
+ break;
+ case PM880:
+ info = pm880_buck_configs;
+ extra = pm880_buck_extra_info;
+ buck_num = ARRAY_SIZE(pm880_buck_configs);
+ break;
+ default:
+ pr_err("%s: Cannot find chip type.\n", __func__);
+ return -ENODEV;
+ }
+
+ print_temp = kmalloc(sizeof(struct pm88x_buck_print), GFP_KERNEL);
+ if (!print_temp) {
+ pr_err("%s: Cannot allocate print template.\n", __func__);
+ return -ENOMEM;
+ }
+
+ len += sprintf(buf + len, "\nBUCK");
+ len += sprintf(buf + len, "\n-----------------------------------");
+ len += sprintf(buf + len, "-------------------------------------\n");
+ len += sprintf(buf + len, "| name | status | slp_mode |slp_volt");
+ len += sprintf(buf + len, "| volt | audio_en| audio |\n");
+ len += sprintf(buf + len, "------------------------------------");
+ len += sprintf(buf + len, "------------------------------------\n");
+
+ for (i = 0; i < buck_num; i++) {
+ ret = pm88x_update_print(chip, info, extra, print_temp, i, buck_num);
+ if (ret < 0) {
+ pr_err("Print of regulator %s failed\n", print_temp->name);
+ goto out_print;
+ }
+ len += sprintf(buf + len, "| %-8s |", print_temp->name);
+ len += sprintf(buf + len, " %-7s |", print_temp->enable);
+ len += sprintf(buf + len, " %-10s|", print_temp->slp_mode);
+ len += sprintf(buf + len, " %-5s |", print_temp->set_slp);
+ len += sprintf(buf + len, " %-5s |", print_temp->volt);
+ len += sprintf(buf + len, " %-7s |", print_temp->audio_en);
+ len += sprintf(buf + len, " %-5s |\n", print_temp->audio);
+ }
+
+ len += sprintf(buf + len, "------------------------------------");
+ len += sprintf(buf + len, "------------------------------------\n");
+
+ ret = len;
+out_print:
+ kfree(print_temp);
+ return ret;
+}
+
+static int pm88x_buck_probe(struct platform_device *pdev)
+{
+ struct pm88x_chip *chip = dev_get_drvdata(pdev->dev.parent);
+ struct pm88x_regulators *data;
+ struct regulator_config config = { };
+ struct regulator_init_data *init_data;
+ struct regulation_constraints *c;
+ const struct of_device_id *match;
+ const struct pm88x_buck_info *const_info;
+ struct pm88x_buck_info *info;
+ int ret;
+
+ match = of_match_device(pm88x_bucks_of_match, &pdev->dev);
+ if (match) {
+ const_info = match->data;
+ init_data = of_get_regulator_init_data(&pdev->dev,
+ pdev->dev.of_node);
+ } else {
+ dev_err(&pdev->dev, "parse dts fails!\n");
+ return -EINVAL;
+ }
+
+ info = kmemdup(const_info, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(struct pm88x_regulators),
+ GFP_KERNEL);
+ if (!data) {
+ dev_err(&pdev->dev, "failed to allocate pm88x_regualtors");
+ return -ENOMEM;
+ }
+ data->map = chip->buck_regmap;
+ data->chip = chip;
+
+ /* add regulator config */
+ config.dev = &pdev->dev;
+ config.init_data = init_data;
+ config.driver_data = info;
+ config.regmap = data->map;
+ config.of_node = pdev->dev.of_node;
+
+ data->rdev = devm_regulator_register(&pdev->dev, &info->desc, &config);
+ if (IS_ERR(data->rdev)) {
+ dev_err(&pdev->dev, "cannot register %s\n", info->desc.name);
+ ret = PTR_ERR(data->rdev);
+ return ret;
+ }
+
+ c = data->rdev->constraints;
+ c->valid_ops_mask |= REGULATOR_CHANGE_DRMS | REGULATOR_CHANGE_MODE
+ | REGULATOR_CHANGE_VOLTAGE;
+ c->valid_modes_mask |= REGULATOR_MODE_NORMAL
+ | REGULATOR_MODE_IDLE;
+ c->input_uV = 1000;
+
+ platform_set_drvdata(pdev, data);
+
+ return 0;
+}
+
+static int pm88x_buck_remove(struct platform_device *pdev)
+{
+ struct pm88x_regulators *data = platform_get_drvdata(pdev);
+ devm_kfree(&pdev->dev, data);
+ return 0;
+}
+
+static struct platform_driver pm88x_buck_driver = {
+ .driver = {
+ .name = "88pm88x-buck",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(pm88x_bucks_of_match),
+ },
+ .probe = pm88x_buck_probe,
+ .remove = pm88x_buck_remove,
+};
+
+static int pm88x_buck_init(void)
+{
+ return platform_driver_register(&pm88x_buck_driver);
+}
+subsys_initcall(pm88x_buck_init);
+
+static void pm88x_buck_exit(void)
+{
+ platform_driver_unregister(&pm88x_buck_driver);
+}
+module_exit(pm88x_buck_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Yi Zhang<yizhang@marvell.com>");
+MODULE_DESCRIPTION("Buck for Marvell 88PM88X PMIC");
+MODULE_ALIAS("platform:88pm88x-buck");
diff --git a/drivers/regulator/88pm88x-ldo.c b/drivers/regulator/88pm88x-ldo.c
new file mode 100644
index 000000000000..d2bf731e2dcc
--- /dev/null
+++ b/drivers/regulator/88pm88x-ldo.c
@@ -0,0 +1,726 @@
+/*
+ * LDO driver for Marvell 88PM88X
+ *
+ * Copyright (C) 2014 Marvell International Ltd.
+ * Yi Zhang <yizhang@marvell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/regmap.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/mfd/88pm88x.h>
+#include <linux/mfd/88pm886.h>
+#include <linux/mfd/88pm880.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/regulator/of_regulator.h>
+
+/* max current in sleep */
+#define MAX_SLEEP_CURRENT 5000
+
+/* ------------- 88pm886 ldo registers --------------- */
+/* control section */
+#define PM886_LDO_EN1 (0x09)
+#define PM886_LDO_EN2 (0x0a)
+
+/* LDO enable2 register offset relative to enable1 register */
+#define PM88X_LDO_EN2_OFF (0x06)
+
+/*
+ * 88pm886 ldo voltage:
+ * ldox_set_slp[7: 4] ldox_set [3: 0]
+ */
+#define PM886_LDO1_VOUT (0x20)
+#define PM886_LDO2_VOUT (0x26)
+#define PM886_LDO3_VOUT (0x2c)
+#define PM886_LDO4_VOUT (0x32)
+#define PM886_LDO5_VOUT (0x38)
+#define PM886_LDO6_VOUT (0x3e)
+#define PM886_LDO7_VOUT (0x44)
+#define PM886_LDO8_VOUT (0x4a)
+#define PM886_LDO9_VOUT (0x50)
+#define PM886_LDO10_VOUT (0x56)
+#define PM886_LDO11_VOUT (0x5c)
+#define PM886_LDO12_VOUT (0x62)
+#define PM886_LDO13_VOUT (0x68)
+#define PM886_LDO14_VOUT (0x6e)
+#define PM886_LDO15_VOUT (0x74)
+#define PM886_LDO16_VOUT (0x7a)
+
+/* ------------- 88pm880 ldo registers --------------- */
+/* control section */
+#define PM880_LDO_EN1 (0x09)
+#define PM880_LDO_EN2 (0x0a)
+#define PM880_LDO_EN3 (0x0b)
+
+/*
+ * 88pm880 ldo voltage:
+ * ldox_set_slp[7: 4] ldox_set [3: 0]
+ */
+#define PM880_LDO1_VOUT (0x20)
+#define PM880_LDO2_VOUT (0x26)
+#define PM880_LDO3_VOUT (0x2c)
+#define PM880_LDO4_VOUT (0x32)
+#define PM880_LDO5_VOUT (0x38)
+#define PM880_LDO6_VOUT (0x3e)
+#define PM880_LDO7_VOUT (0x44)
+#define PM880_LDO8_VOUT (0x4a)
+#define PM880_LDO9_VOUT (0x50)
+#define PM880_LDO10_VOUT (0x56)
+#define PM880_LDO11_VOUT (0x5c)
+#define PM880_LDO12_VOUT (0x62)
+#define PM880_LDO13_VOUT (0x68)
+#define PM880_LDO14_VOUT (0x6e)
+#define PM880_LDO15_VOUT (0x74)
+#define PM880_LDO16_VOUT (0x7a)
+#define PM880_LDO17_VOUT (0x80)
+#define PM880_LDO18_VOUT (0x86)
+
+/*
+ * set ldo sleep voltage register is the same as the active registers;
+ * only the mask is not the same:
+ * bit [7 : 4] --> to set sleep voltage
+ * bit [3 : 0] --> to set active voltage
+ * no need to give definition here
+ */
+
+/*
+ * vreg - the LDO regs string
+ * ebit - the bit number in the enable register.
+ * ereg - the enable register
+ * amax - the current
+ * volt_table - the LDO voltage table
+ * For all the LDOes, there are too many ranges. Using volt_table will be
+ * simpler and faster.
+ */
+#define PM88X_LDO(_pmic, vreg, ereg, ebit, amax, ldo_volt_table) \
+{ \
+ .desc = { \
+ .name = #vreg, \
+ .ops = &pm88x_volt_ldo_ops, \
+ .type = REGULATOR_VOLTAGE, \
+ .id = _pmic##_ID_##vreg, \
+ .owner = THIS_MODULE, \
+ .n_voltages = ARRAY_SIZE(ldo_volt_table), \
+ .vsel_reg = _pmic##_##vreg##_VOUT, \
+ .vsel_mask = 0xf, \
+ .enable_reg = _pmic##_LDO_##ereg, \
+ .enable_mask = 1 << (ebit), \
+ .volt_table = ldo_volt_table, \
+ }, \
+ .max_ua = (amax), \
+ .sleep_vsel_reg = _pmic##_##vreg##_VOUT, \
+ .sleep_vsel_mask = (0xf << 4), \
+ .sleep_enable_reg = _pmic##_##vreg##_SLP_CTRL, \
+ .sleep_enable_mask = (0x3 << 4), \
+}
+
+#define PM886_LDO(vreg, ereg, ebit, amax, ldo_volt_table) \
+ PM88X_LDO(PM886, vreg, ereg, ebit, amax, ldo_volt_table)
+
+#define PM880_LDO(vreg, ereg, ebit, amax, ldo_volt_table) \
+ PM88X_LDO(PM880, vreg, ereg, ebit, amax, ldo_volt_table)
+
+/* 88pm886 ldo1~3, 88pm880 ldo1-3 */
+static const unsigned int ldo_volt_table1[] = {
+ 1700000, 1800000, 1900000, 2500000, 2800000, 2900000, 3100000, 3300000,
+};
+/* 88pm886 ldo4~15, 88pm880 ldo4-17 */
+static const unsigned int ldo_volt_table2[] = {
+ 1200000, 1250000, 1700000, 1800000, 1850000, 1900000, 2500000, 2600000,
+ 2700000, 2750000, 2800000, 2850000, 2900000, 3000000, 3100000, 3300000,
+};
+/* 88pm886 ldo16, 88pm880 ldo18 */
+static const unsigned int ldo_volt_table3[] = {
+ 1700000, 1800000, 1900000, 2000000, 2100000, 2500000, 2700000, 2800000,
+};
+
+struct pm88x_ldo_info {
+ struct regulator_desc desc;
+ int max_ua;
+ u8 sleep_enable_mask;
+ u8 sleep_enable_reg;
+ u8 sleep_vsel_reg;
+ u8 sleep_vsel_mask;
+};
+
+struct pm88x_ldos {
+ struct regulator_dev *rdev;
+ struct pm88x_chip *chip;
+ struct regmap *map;
+};
+
+struct pm88x_ldo_print {
+ char name[15];
+ char enable[15];
+ char slp_mode[15];
+ char set_slp[15];
+ char volt[10];
+ char audio_en[15];
+ char audio[10];
+};
+
+struct pm88x_ldo_extra {
+ const char *name;
+ u8 audio_enable_reg;
+ u8 audio_enable_mask;
+ u8 audio_enable_off;
+ u8 audio_vsel_reg;
+ u8 audio_vsel_mask;
+};
+
+#define PM88X_LDO_EXTRA(_name, _areg, _amsk, _aoff, _vreg, _vmsk) \
+{ \
+ .name = _name, \
+ .audio_enable_reg = _areg, \
+ .audio_enable_mask = _amsk, \
+ .audio_enable_off = _aoff, \
+ .audio_vsel_reg = _vreg, \
+ .audio_vsel_mask = _vmsk \
+}
+
+static struct pm88x_ldo_extra pm88x_ldo_extra_info[] = {
+ PM88X_LDO_EXTRA("LDO2", 0x28, 0x10, 0x04, 0x28, 0x0f),
+};
+
+#define LDO_OFF (0x0 << 4)
+#define LDO_ACTIVE_VOLT_SLP (0x1 << 4)
+#define LDO_SLP_VOLT_SLP (0x2 << 4)
+#define LDO_ACTIVE_VOLT_ACTIVE (0x3 << 4)
+
+int pm88x_ldo_set_suspend_mode(struct regulator_dev *rdev, unsigned int mode)
+{
+ struct pm88x_ldo_info *info = rdev_get_drvdata(rdev);
+ u8 val;
+ int ret;
+
+ if (!info)
+ return -EINVAL;
+
+ switch (mode) {
+ case REGULATOR_MODE_NORMAL:
+ /* regulator will be active with normal voltage */
+ val = LDO_ACTIVE_VOLT_ACTIVE;
+ break;
+ case REGULATOR_MODE_IDLE:
+ /* regulator will be in sleep with sleep voltage */
+ val = LDO_SLP_VOLT_SLP;
+ break;
+ case REGULATOR_MODE_STANDBY:
+ /* regulator will be off */
+ val = LDO_OFF;
+ break;
+ default:
+ /* regulator will be active with sleep voltage */
+ val = LDO_ACTIVE_VOLT_SLP;
+ break;
+ }
+
+ ret = regmap_update_bits(rdev->regmap, info->sleep_enable_reg,
+ info->sleep_enable_mask, val);
+ return ret;
+}
+
+static unsigned int pm88x_ldo_get_optimum_mode(struct regulator_dev *rdev,
+ int input_uV, int output_uV,
+ int output_uA)
+{
+ struct pm88x_ldo_info *info = rdev_get_drvdata(rdev);
+ if (!info)
+ return REGULATOR_MODE_IDLE;
+
+ if (output_uA < 0) {
+ dev_err(rdev_get_dev(rdev), "current needs to be > 0.\n");
+ return REGULATOR_MODE_IDLE;
+ }
+
+ return (output_uA < MAX_SLEEP_CURRENT) ?
+ REGULATOR_MODE_IDLE : REGULATOR_MODE_NORMAL;
+}
+
+static int pm88x_ldo_get_current_limit(struct regulator_dev *rdev)
+{
+ struct pm88x_ldo_info *info = rdev_get_drvdata(rdev);
+ if (!info)
+ return 0;
+ return info->max_ua;
+}
+
+static int pm88x_ldo_set_suspend_voltage(struct regulator_dev *rdev, int uv)
+{
+ int ret, sel;
+ struct pm88x_ldo_info *info = rdev_get_drvdata(rdev);
+ if (!info || !info->desc.ops)
+ return -EINVAL;
+ if (!info->desc.ops->set_suspend_mode)
+ return 0;
+ /*
+ * two steps:
+ * 1) set the suspend voltage to *_set_slp registers
+ * 2) set regulator mode via set_suspend_mode() interface to enable output
+ */
+ /* the suspend voltage mapping is the same as active */
+ sel = regulator_map_voltage_iterate(rdev, uv, uv);
+ if (sel < 0)
+ return -EINVAL;
+
+ sel <<= ffs(info->sleep_vsel_mask) - 1;
+
+ ret = regmap_update_bits(rdev->regmap, info->sleep_vsel_reg,
+ info->sleep_vsel_mask, sel);
+ if (ret < 0)
+ return -EINVAL;
+
+ /* TODO: do we need this? */
+ ret = pm88x_ldo_set_suspend_mode(rdev, REGULATOR_MODE_IDLE);
+ return ret;
+}
+
+/*
+ * about the get_optimum_mode()/set_suspend_mode()/set_suspend_voltage() interface:
+ * - 88pm88x has two sets of registers to set and enable/disable regulators
+ * in active and suspend(sleep) status:
+ * the following focues on the sleep part:
+ * - there are two control bits: 00-disable,
+ * 01/10-use sleep voltage,
+ * 11-use active voltage,
+ *- in most of the scenario, these registers are configured when the whole PMIC
+ * initialized, when the system enters into suspend(sleep) mode, the regulator
+ * works according to the setting or disabled;
+ *- there is also case that the device driver needs to:
+ * - set the sleep voltage;
+ * - choose to use sleep voltage or active voltage depends on the load;
+ * so:
+ * set_suspend_voltage() is used to manipulate the registers to set sleep volt;
+ * set_suspend_mode() is used to switch between sleep voltage and active voltage
+ * get_optimum_mode() is used to get right mode
+ */
+static struct regulator_ops pm88x_volt_ldo_ops = {
+ .list_voltage = regulator_list_voltage_table,
+ .map_voltage = regulator_map_voltage_iterate,
+ .set_voltage_sel = regulator_set_voltage_sel_regmap,
+ .get_voltage_sel = regulator_get_voltage_sel_regmap,
+ .enable = regulator_enable_regmap,
+ .disable = regulator_disable_regmap,
+ .is_enabled = regulator_is_enabled_regmap,
+ .get_current_limit = pm88x_ldo_get_current_limit,
+ .get_optimum_mode = pm88x_ldo_get_optimum_mode,
+ .set_suspend_mode = pm88x_ldo_set_suspend_mode,
+ .set_suspend_voltage = pm88x_ldo_set_suspend_voltage,
+};
+
+/* The array is indexed by id(PM886_ID_LDO*) */
+static struct pm88x_ldo_info pm886_ldo_configs[] = {
+ /* 88pm886 ldo */
+ PM886_LDO(LDO1, EN1, 0, 100000, ldo_volt_table1),
+ PM886_LDO(LDO2, EN1, 1, 100000, ldo_volt_table1),
+ PM886_LDO(LDO3, EN1, 2, 100000, ldo_volt_table1),
+ PM886_LDO(LDO4, EN1, 3, 400000, ldo_volt_table2),
+ PM886_LDO(LDO5, EN1, 4, 400000, ldo_volt_table2),
+ PM886_LDO(LDO6, EN1, 5, 400000, ldo_volt_table2),
+ PM886_LDO(LDO7, EN1, 6, 400000, ldo_volt_table2),
+ PM886_LDO(LDO8, EN1, 7, 400000, ldo_volt_table2),
+ PM886_LDO(LDO9, EN2, 0, 400000, ldo_volt_table2),
+ PM886_LDO(LDO10, EN2, 1, 200000, ldo_volt_table2),
+ PM886_LDO(LDO11, EN2, 2, 200000, ldo_volt_table2),
+ PM886_LDO(LDO12, EN2, 3, 200000, ldo_volt_table2),
+ PM886_LDO(LDO13, EN2, 4, 200000, ldo_volt_table2),
+ PM886_LDO(LDO14, EN2, 5, 200000, ldo_volt_table2),
+ PM886_LDO(LDO15, EN2, 6, 200000, ldo_volt_table2),
+ PM886_LDO(LDO16, EN2, 7, 200000, ldo_volt_table3),
+};
+
+/* The array is indexed by id(PM880_ID_LDO*) */
+static struct pm88x_ldo_info pm880_ldo_configs[] = {
+ /* 88pm880 ldo */
+ PM880_LDO(LDO1, EN1, 0, 100000, ldo_volt_table1),
+ PM880_LDO(LDO2, EN1, 1, 100000, ldo_volt_table1),
+ PM880_LDO(LDO3, EN1, 2, 100000, ldo_volt_table1),
+ PM880_LDO(LDO4, EN1, 3, 400000, ldo_volt_table2),
+ PM880_LDO(LDO5, EN1, 4, 400000, ldo_volt_table2),
+ PM880_LDO(LDO6, EN1, 5, 400000, ldo_volt_table2),
+ PM880_LDO(LDO7, EN1, 6, 400000, ldo_volt_table2),
+ PM880_LDO(LDO8, EN1, 7, 400000, ldo_volt_table2),
+ PM880_LDO(LDO9, EN2, 0, 400000, ldo_volt_table2),
+ PM880_LDO(LDO10, EN2, 1, 200000, ldo_volt_table2),
+ PM880_LDO(LDO11, EN2, 2, 200000, ldo_volt_table2),
+ PM880_LDO(LDO12, EN2, 3, 200000, ldo_volt_table2),
+ PM880_LDO(LDO13, EN2, 4, 200000, ldo_volt_table2),
+ PM880_LDO(LDO14, EN2, 5, 200000, ldo_volt_table2),
+ PM880_LDO(LDO15, EN2, 6, 200000, ldo_volt_table2),
+ PM880_LDO(LDO16, EN2, 7, 200000, ldo_volt_table2),
+ PM880_LDO(LDO17, EN3, 0, 200000, ldo_volt_table2),
+ PM880_LDO(LDO18, EN3, 1, 200000, ldo_volt_table3),
+};
+
+#define PM88X_LDO_OF_MATCH(_pmic, id, comp, label) \
+ { \
+ .compatible = comp, \
+ .data = &_pmic##_ldo_configs[id##_##label], \
+ }
+
+#define PM886_LDO_OF_MATCH(comp, label) \
+ PM88X_LDO_OF_MATCH(pm886, PM886_ID, comp, label)
+
+#define PM880_LDO_OF_MATCH(comp, label) \
+ PM88X_LDO_OF_MATCH(pm880, PM880_ID, comp, label)
+
+static const struct of_device_id pm88x_ldos_of_match[] = {
+ /* 88pm886 */
+ PM886_LDO_OF_MATCH("marvell,88pm886-ldo1", LDO1),
+ PM886_LDO_OF_MATCH("marvell,88pm886-ldo2", LDO2),
+ PM886_LDO_OF_MATCH("marvell,88pm886-ldo3", LDO3),
+ PM886_LDO_OF_MATCH("marvell,88pm886-ldo4", LDO4),
+ PM886_LDO_OF_MATCH("marvell,88pm886-ldo5", LDO5),
+ PM886_LDO_OF_MATCH("marvell,88pm886-ldo6", LDO6),
+ PM886_LDO_OF_MATCH("marvell,88pm886-ldo7", LDO7),
+ PM886_LDO_OF_MATCH("marvell,88pm886-ldo8", LDO8),
+ PM886_LDO_OF_MATCH("marvell,88pm886-ldo9", LDO9),
+ PM886_LDO_OF_MATCH("marvell,88pm886-ldo10", LDO10),
+ PM886_LDO_OF_MATCH("marvell,88pm886-ldo11", LDO11),
+ PM886_LDO_OF_MATCH("marvell,88pm886-ldo12", LDO12),
+ PM886_LDO_OF_MATCH("marvell,88pm886-ldo13", LDO13),
+ PM886_LDO_OF_MATCH("marvell,88pm886-ldo14", LDO14),
+ PM886_LDO_OF_MATCH("marvell,88pm886-ldo15", LDO15),
+ PM886_LDO_OF_MATCH("marvell,88pm886-ldo16", LDO16),
+ /* 88pm880 */
+ PM880_LDO_OF_MATCH("marvell,88pm880-ldo1", LDO1),
+ PM880_LDO_OF_MATCH("marvell,88pm880-ldo2", LDO2),
+ PM880_LDO_OF_MATCH("marvell,88pm880-ldo3", LDO3),
+ PM880_LDO_OF_MATCH("marvell,88pm880-ldo4", LDO4),
+ PM880_LDO_OF_MATCH("marvell,88pm880-ldo5", LDO5),
+ PM880_LDO_OF_MATCH("marvell,88pm880-ldo6", LDO6),
+ PM880_LDO_OF_MATCH("marvell,88pm880-ldo7", LDO7),
+ PM880_LDO_OF_MATCH("marvell,88pm880-ldo8", LDO8),
+ PM880_LDO_OF_MATCH("marvell,88pm880-ldo9", LDO9),
+ PM880_LDO_OF_MATCH("marvell,88pm880-ldo10", LDO10),
+ PM880_LDO_OF_MATCH("marvell,88pm880-ldo11", LDO11),
+ PM880_LDO_OF_MATCH("marvell,88pm880-ldo12", LDO12),
+ PM880_LDO_OF_MATCH("marvell,88pm880-ldo13", LDO13),
+ PM880_LDO_OF_MATCH("marvell,88pm880-ldo14", LDO14),
+ PM880_LDO_OF_MATCH("marvell,88pm880-ldo15", LDO15),
+ PM880_LDO_OF_MATCH("marvell,88pm880-ldo16", LDO16),
+ PM880_LDO_OF_MATCH("marvell,88pm880-ldo17", LDO17),
+ PM880_LDO_OF_MATCH("marvell,88pm880-ldo18", LDO18),
+};
+
+/*
+ * The function convert the ldo voltage register value
+ * to a real voltage value (in uV) according to the voltage table.
+ */
+static int pm88x_get_vldo_vol(unsigned int val, struct pm88x_ldo_info *info)
+{
+ int volt = -EINVAL;
+
+ /* get the voltage via the register value */
+ volt = info->desc.volt_table[val];
+ return volt;
+}
+
+/* The function check if the regulator register is configured to enable/disable */
+static int pm88x_check_en(struct pm88x_chip *chip, unsigned int reg, unsigned int mask,
+ unsigned int reg2)
+{
+ struct regmap *map = chip->ldo_regmap;
+ int ret, value;
+ unsigned int enable1, enable2;
+
+ ret = regmap_read(map, reg, &enable1);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(map, reg2, &enable2);
+ if (ret < 0)
+ return ret;
+
+ value = (enable1 | enable2) & mask;
+
+ return value;
+}
+
+/* The function check the regulator sleep mode as configured in his register */
+static int pm88x_check_slp_mode(struct regmap *map, unsigned int reg, int mask)
+{
+ int ret;
+ unsigned int slp_mode;
+
+ ret = regmap_read(map, reg, &slp_mode);
+ if (ret < 0)
+ return ret;
+
+ slp_mode = (slp_mode & mask) >> (ffs(mask) - 1);
+
+ return slp_mode;
+}
+
+/* The function return the value in the regulator voltage register */
+static unsigned int pm88x_check_vol(struct regmap *map, unsigned int reg, unsigned int mask)
+{
+ int ret;
+ unsigned int vol_val;
+
+ ret = regmap_bulk_read(map, reg, &vol_val, 1);
+ if (ret < 0)
+ return ret;
+
+ /* mask and shift the relevant value from the register */
+ vol_val = (vol_val & mask) >> (ffs(mask) - 1);
+
+ return vol_val;
+}
+
+static int pm88x_update_print(struct pm88x_chip *chip, struct pm88x_ldo_info *info,
+ struct pm88x_ldo_extra *extra, struct pm88x_ldo_print *print_temp,
+ int index, int ldo_num, int extra_info_num)
+{
+ int i, ret, volt, flag = 0;
+ struct regmap *map = chip->ldo_regmap;
+ char *slp_mode_str[] = {"off", "active_slp", "sleep", "active"};
+ int slp_mode_num = sizeof(slp_mode_str)/sizeof(slp_mode_str[0]);
+
+ sprintf(print_temp->name, "%s", info[index].desc.name);
+
+ /* check enable/disable */
+ ret = pm88x_check_en(chip, info[index].desc.enable_reg, info[index].desc.enable_mask,
+ info[index].desc.enable_reg + PM88X_LDO_EN2_OFF);
+ if (ret < 0)
+ return ret;
+ else if (ret)
+ strcpy(print_temp->enable, "enable");
+ else
+ strcpy(print_temp->enable, "disable");
+
+ /* check sleep mode */
+ ret = pm88x_check_slp_mode(map, info[index].sleep_enable_reg,
+ info[index].sleep_enable_mask);
+ if (ret < 0)
+ return ret;
+ if (ret < slp_mode_num)
+ strcpy(print_temp->slp_mode, slp_mode_str[ret]);
+ else
+ strcpy(print_temp->slp_mode, "unknown");
+
+ /* print sleep voltage */
+ ret = pm88x_check_vol(map, info[index].sleep_vsel_reg, info[index].sleep_vsel_mask);
+ if (ret < 0)
+ return ret;
+
+ volt = pm88x_get_vldo_vol(ret, &info[index]);
+ if (volt < 0)
+ return volt;
+ else
+ sprintf(print_temp->set_slp, "%4d", volt/1000);
+
+ /* print active voltage(s) */
+ ret = pm88x_check_vol(map, info[index].desc.vsel_reg,
+ info[index].desc.vsel_mask);
+ if (ret < 0)
+ return ret;
+
+ volt = pm88x_get_vldo_vol(ret, &info[index]);
+ if (volt < 0)
+ return volt;
+ else
+ sprintf(print_temp->volt, "%4d", volt/1000);
+
+ for (i = 0; i < extra_info_num; i++) {
+ /* print audio voltage */
+ if (!strcmp(print_temp->name, extra[i].name) && extra[i].audio_enable_reg) {
+ ret = pm88x_check_en(chip, extra[i].audio_enable_reg,
+ extra[i].audio_enable_mask,
+ extra[i].audio_enable_reg);
+ if (ret < 0)
+ return ret;
+ else if (ret)
+ strcpy(print_temp->audio_en, "enable");
+ else
+ strcpy(print_temp->audio_en, "disable");
+ ret = pm88x_check_vol(map, extra[i].audio_vsel_reg,
+ extra[i].audio_vsel_mask);
+ if (ret < 0)
+ return ret;
+
+ volt = pm88x_get_vldo_vol(ret, &info[index]);
+ if (volt < 0)
+ return volt;
+ else
+ sprintf(print_temp->audio, "%4d", volt/1000);
+ flag = 1;
+ }
+ }
+ if (!flag) {
+ strcpy(print_temp->audio_en, " - ");
+ sprintf(print_temp->audio, " -");
+ flag = 0;
+ }
+ return 0;
+}
+
+int pm88x_display_ldo(struct pm88x_chip *chip, char *buf)
+{
+ struct pm88x_ldo_print *print_temp;
+ struct pm88x_ldo_info *info;
+ struct pm88x_ldo_extra *extra;
+ int ldo_num, extra_info_num, i, len = 0;
+ ssize_t ret;
+
+ switch (chip->type) {
+ case PM886:
+ info = pm886_ldo_configs;
+ ldo_num = sizeof(pm886_ldo_configs) / sizeof(pm886_ldo_configs[0]);
+ extra = pm88x_ldo_extra_info;
+ extra_info_num = sizeof(pm88x_ldo_extra_info) / sizeof(pm88x_ldo_extra_info[0]);
+ break;
+ case PM880:
+ info = pm880_ldo_configs;
+ ldo_num = sizeof(pm880_ldo_configs) / sizeof(pm880_ldo_configs[0]);
+ extra = pm88x_ldo_extra_info;
+ extra_info_num = sizeof(pm88x_ldo_extra_info) / sizeof(pm88x_ldo_extra_info[0]);
+ break;
+ default:
+ pr_err("%s: Cannot find chip type.\n", __func__);
+ return -ENODEV;
+ }
+
+ print_temp = kmalloc(sizeof(struct pm88x_ldo_print), GFP_KERNEL);
+ if (!print_temp) {
+ pr_err("%s: Cannot allocate print template.\n", __func__);
+ return -ENOMEM;
+ }
+
+ len += sprintf(buf + len, "\nLDO");
+ len += sprintf(buf + len, "\n-----------------------------------");
+ len += sprintf(buf + len, "-------------------------------------\n");
+ len += sprintf(buf + len, "| name | status | slp_mode |slp_volt");
+ len += sprintf(buf + len, "| volt | audio_en| audio |\n");
+ len += sprintf(buf + len, "------------------------------------");
+ len += sprintf(buf + len, "------------------------------------\n");
+
+ for (i = 0; i < ldo_num; i++) {
+ ret = pm88x_update_print(chip, info, extra, print_temp, i, ldo_num, extra_info_num);
+ if (ret < 0) {
+ pr_err("Print of regulator %s failed\n", print_temp->name);
+ goto out_print;
+ }
+ len += sprintf(buf + len, "| %-8s |", print_temp->name);
+ len += sprintf(buf + len, " %-7s |", print_temp->enable);
+ len += sprintf(buf + len, " %-10s|", print_temp->slp_mode);
+ len += sprintf(buf + len, " %-5s |", print_temp->set_slp);
+ len += sprintf(buf + len, " %-5s |", print_temp->volt);
+ len += sprintf(buf + len, " %-7s |", print_temp->audio_en);
+ len += sprintf(buf + len, " %-5s |\n", print_temp->audio);
+ }
+
+ len += sprintf(buf + len, "------------------------------------");
+ len += sprintf(buf + len, "------------------------------------\n");
+
+ ret = len;
+out_print:
+ kfree(print_temp);
+ return ret;
+}
+
+static int pm88x_ldo_probe(struct platform_device *pdev)
+{
+ struct pm88x_chip *chip = dev_get_drvdata(pdev->dev.parent);
+ struct pm88x_ldos *data;
+ struct regulator_config config = { };
+ struct regulator_init_data *init_data;
+ struct regulation_constraints *c;
+ const struct of_device_id *match;
+ const struct pm88x_ldo_info *const_info;
+ struct pm88x_ldo_info *info;
+ int ret;
+
+ match = of_match_device(pm88x_ldos_of_match, &pdev->dev);
+ if (match) {
+ const_info = match->data;
+ init_data = of_get_regulator_init_data(&pdev->dev,
+ pdev->dev.of_node);
+ } else {
+ dev_err(&pdev->dev, "parse dts fails!\n");
+ return -EINVAL;
+ }
+
+ info = kmemdup(const_info, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(struct pm88x_ldos),
+ GFP_KERNEL);
+ if (!data) {
+ dev_err(&pdev->dev, "failed to allocate pm88x_regualtors");
+ return -ENOMEM;
+ }
+ data->map = chip->ldo_regmap;
+ data->chip = chip;
+
+ /* add regulator config */
+ config.dev = &pdev->dev;
+ config.init_data = init_data;
+ config.driver_data = info;
+ config.regmap = data->map;
+ config.of_node = pdev->dev.of_node;
+
+ data->rdev = devm_regulator_register(&pdev->dev, &info->desc, &config);
+ if (IS_ERR(data->rdev)) {
+ dev_err(&pdev->dev, "cannot register %s\n", info->desc.name);
+ ret = PTR_ERR(data->rdev);
+ return ret;
+ }
+
+ c = data->rdev->constraints;
+ c->valid_ops_mask |= REGULATOR_CHANGE_DRMS | REGULATOR_CHANGE_MODE
+ | REGULATOR_CHANGE_VOLTAGE;
+ c->valid_modes_mask |= REGULATOR_MODE_NORMAL
+ | REGULATOR_MODE_IDLE;
+ c->input_uV = 1000;
+
+ platform_set_drvdata(pdev, data);
+
+ return 0;
+}
+
+static int pm88x_ldo_remove(struct platform_device *pdev)
+{
+ struct pm88x_ldos *data = platform_get_drvdata(pdev);
+ devm_kfree(&pdev->dev, data);
+ return 0;
+}
+
+static struct platform_driver pm88x_ldo_driver = {
+ .driver = {
+ .name = "88pm88x-ldo",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(pm88x_ldos_of_match),
+ },
+ .probe = pm88x_ldo_probe,
+ .remove = pm88x_ldo_remove,
+};
+
+static int pm88x_ldo_init(void)
+{
+ return platform_driver_register(&pm88x_ldo_driver);
+}
+subsys_initcall(pm88x_ldo_init);
+
+static void pm88x_ldo_exit(void)
+{
+ platform_driver_unregister(&pm88x_ldo_driver);
+}
+module_exit(pm88x_ldo_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Yi Zhang<yizhang@marvell.com>");
+MODULE_DESCRIPTION("LDO Driver for Marvell 88PM88X PMIC");
+MODULE_ALIAS("platform:88pm88x-ldo");
diff --git a/drivers/regulator/88pm88x-vr.c b/drivers/regulator/88pm88x-vr.c
new file mode 100644
index 000000000000..2175859b8727
--- /dev/null
+++ b/drivers/regulator/88pm88x-vr.c
@@ -0,0 +1,666 @@
+/*
+ * virtual regulator driver for Marvell 88PM88X
+ *
+ * Copyright (C) 2015 Marvell International Ltd.
+ * Yi Zhang <yizhang@marvell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/regmap.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/mfd/88pm88x.h>
+#include <linux/mfd/88pm886.h>
+#include <linux/mfd/88pm880.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/regulator/of_regulator.h>
+
+#define PM88X_VR_EN (0x28)
+
+#define PM886_BUCK1_SLP_VOUT (0xa3)
+#define PM886_BUCK1_SLP_EN (0xa2)
+
+#define PM880_BUCK1A_SLP_VOUT (0x26)
+#define PM880_BUCK1A_SLP_EN (0x24)
+#define PM880_BUCK1B_SLP_VOUT (0x3e)
+#define PM880_BUCK1B_SLP_EN (0x3c)
+
+#define PM880_BUCK1A_AUDIO_VOUT (0x27)
+#define PM880_BUCK1A_AUDIO_EN (0x27)
+#define PM880_BUCK1B_AUDIO_EN (0x3f)
+#define PM880_BUCK1B_AUDIO_VOUT (0x3f)
+
+#define PM88X_VR(vreg, ebit, nr) \
+{ \
+ .desc = { \
+ .name = #vreg, \
+ .ops = &pm88x_virtual_regulator_ops, \
+ .type = REGULATOR_VOLTAGE, \
+ .id = PM88X##_ID_##vreg, \
+ .owner = THIS_MODULE, \
+ .enable_reg = PM88X##_VR_EN, \
+ .enable_mask = 1 << (ebit), \
+ }, \
+ .page_nr = nr, \
+}
+
+#define PM88X_BUCK_SLP(_pmic, vreg, ebit, nr, volt_ranges, n_volt) \
+{ \
+ .desc = { \
+ .name = #vreg, \
+ .ops = &pm88x_buck_slp_ops, \
+ .type = REGULATOR_VOLTAGE, \
+ .id = _pmic##_ID_##vreg, \
+ .owner = THIS_MODULE, \
+ .n_voltages = n_volt, \
+ .linear_ranges = volt_ranges, \
+ .n_linear_ranges = ARRAY_SIZE(volt_ranges), \
+ .vsel_reg = _pmic##_##vreg##_VOUT, \
+ .vsel_mask = 0x7f, \
+ .enable_reg = _pmic##_##vreg##_EN, \
+ .enable_mask = (0x3) << (ebit), \
+ }, \
+ .page_nr = nr, \
+}
+
+#define PM88X_BUCK_AUDIO(_pmic, vreg, ebit, nr, volt_ranges, n_volt) \
+{ \
+ .desc = { \
+ .name = #vreg, \
+ .ops = &pm88x_buck_audio_ops, \
+ .type = REGULATOR_VOLTAGE, \
+ .id = _pmic##_ID_##vreg, \
+ .owner = THIS_MODULE, \
+ .n_voltages = n_volt, \
+ .linear_ranges = volt_ranges, \
+ .n_linear_ranges = ARRAY_SIZE(volt_ranges), \
+ .vsel_reg = _pmic##_##vreg##_VOUT, \
+ .vsel_mask = 0x7f, \
+ .enable_reg = _pmic##_##vreg##_EN, \
+ .enable_mask = (0x1) << (ebit), \
+ }, \
+ .page_nr = nr, \
+}
+
+#define PM880_BUCK_SLP(vreg, ebit, nr, volt_ranges, n_volt) \
+ PM88X_BUCK_SLP(PM880, vreg, ebit, nr, volt_ranges, n_volt)
+
+#define PM886_BUCK_SLP(vreg, ebit, nr, volt_ranges, n_volt) \
+ PM88X_BUCK_SLP(PM886, vreg, ebit, nr, volt_ranges, n_volt)
+
+#define PM880_BUCK_AUDIO(vreg, ebit, nr, volt_ranges, n_volt) \
+ PM88X_BUCK_AUDIO(PM880, vreg, ebit, nr, volt_ranges, n_volt)
+
+#define PM88X_VR_OF_MATCH(comp, label) \
+ { \
+ .compatible = comp, \
+ .data = &pm88x_vr_configs[PM88X_ID##_##label], \
+ }
+
+#define PM88X_BUCK_SLP_OF_MATCH(_pmic, id, comp, label) \
+ { \
+ .compatible = comp, \
+ .data = &_pmic##_buck_slp_configs[id##_##label], \
+ }
+
+#define PM88X_BUCK_AUDIO_OF_MATCH(_pmic, id, comp, label) \
+ { \
+ .compatible = comp, \
+ .data = &_pmic##_buck_audio_configs[id##_##label], \
+ }
+
+#define PM880_BUCK_SLP_OF_MATCH(comp, label) \
+ PM88X_BUCK_SLP_OF_MATCH(pm880, PM880_ID, comp, label)
+
+#define PM886_BUCK_SLP_OF_MATCH(comp, label) \
+ PM88X_BUCK_SLP_OF_MATCH(pm886, PM886_ID, comp, label)
+
+#define PM880_BUCK_AUDIO_OF_MATCH(comp, label) \
+ PM88X_BUCK_AUDIO_OF_MATCH(pm880, PM880_ID, comp, label)
+
+static const struct regulator_linear_range buck_slp_volt_range1[] = {
+ REGULATOR_LINEAR_RANGE(600000, 0, 0x4f, 12500),
+ REGULATOR_LINEAR_RANGE(1600000, 0x50, 0x54, 50000),
+};
+
+static const struct regulator_linear_range buck_audio_volt_range1[] = {
+ REGULATOR_LINEAR_RANGE(600000, 0, 0x54, 12500),
+};
+
+struct pm88x_vr_info {
+ struct regulator_desc desc;
+ unsigned int page_nr;
+};
+
+struct pm88x_buck_slp_info {
+ struct regulator_desc desc;
+ unsigned int page_nr;
+};
+
+struct pm88x_buck_audio_info {
+ struct regulator_desc desc;
+ unsigned int page_nr;
+};
+
+struct pm88x_regulators {
+ struct regulator_dev *rdev;
+ struct pm88x_chip *chip;
+ struct regmap *map;
+};
+
+struct pm88x_vr_print {
+ char name[15];
+ char enable[15];
+ char slp_mode[15];
+ char set_slp[15];
+ char volt[10];
+ char audio_en[15];
+ char audio[10];
+};
+
+static struct regulator_ops pm88x_virtual_regulator_ops = {
+ .enable = regulator_enable_regmap,
+ .disable = regulator_disable_regmap,
+ .is_enabled = regulator_is_enabled_regmap,
+};
+
+static int pm88x_buck_slp_enable(struct regulator_dev *rdev)
+{
+ return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg,
+ rdev->desc->enable_mask, 0x2);
+}
+
+static int pm88x_buck_slp_disable(struct regulator_dev *rdev)
+{
+ dev_info(&rdev->dev, "%s: this buck is _off_ in suspend.\n", __func__);
+ return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg,
+ rdev->desc->enable_mask, 0x0);
+}
+
+static int pm88x_buck_slp_is_enabled(struct regulator_dev *rdev)
+{
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(rdev->regmap, rdev->desc->enable_reg, &val);
+ if (ret != 0)
+ return ret;
+
+ return !!((val & rdev->desc->enable_mask) == (0x2 << 5));
+}
+
+static int pm88x_buck_slp_map_voltage(struct regulator_dev *rdev,
+ int min_uV, int max_uV)
+{
+ /* use higest voltage */
+ if (max_uV >= 1600000)
+ return 0x50 + (max_uV - 1600000) / 50000;
+ else
+ return (max_uV - 600000) / 12500;
+
+ return -EINVAL;
+}
+
+static struct regulator_ops pm88x_buck_slp_ops = {
+ .enable = pm88x_buck_slp_enable,
+ .disable = pm88x_buck_slp_disable,
+ .is_enabled = pm88x_buck_slp_is_enabled,
+ .set_voltage_sel = regulator_set_voltage_sel_regmap,
+ .get_voltage_sel = regulator_get_voltage_sel_regmap,
+ .list_voltage = regulator_list_voltage_linear_range,
+ .map_voltage = pm88x_buck_slp_map_voltage,
+};
+
+static int pm88x_buck_audio_enable(struct regulator_dev *rdev)
+{
+ dev_info(&rdev->dev, "%s is empty.\n", __func__);
+ return 0;
+}
+
+static int pm88x_buck_audio_disable(struct regulator_dev *rdev)
+{
+ dev_info(&rdev->dev, "%s is empty.\n", __func__);
+ return 0;
+}
+
+static int pm88x_buck_audio_is_enabled(struct regulator_dev *rdev)
+{
+ dev_info(&rdev->dev, "%s is empty.\n", __func__);
+ return 0;
+}
+
+static struct regulator_ops pm88x_buck_audio_ops = {
+ .enable = pm88x_buck_audio_enable,
+ .disable = pm88x_buck_audio_disable,
+ .is_enabled = pm88x_buck_audio_is_enabled,
+ .set_voltage_sel = regulator_set_voltage_sel_regmap,
+ .get_voltage_sel = regulator_get_voltage_sel_regmap,
+ .list_voltage = regulator_list_voltage_linear_range,
+};
+
+static struct pm88x_vr_info pm88x_vr_configs[] = {
+ PM88X_VR(VOTG, 7, 3),
+};
+
+static struct pm88x_buck_slp_info pm880_buck_slp_configs[] = {
+ PM880_BUCK_SLP(BUCK1A_SLP, 4, 4, buck_slp_volt_range1, 0x55),
+ PM880_BUCK_SLP(BUCK1B_SLP, 4, 4, buck_slp_volt_range1, 0x55),
+};
+
+static struct pm88x_buck_slp_info pm886_buck_slp_configs[] = {
+ PM886_BUCK_SLP(BUCK1_SLP, 4, 1, buck_slp_volt_range1, 0x55),
+};
+
+static struct pm88x_buck_audio_info pm880_buck_audio_configs[] = {
+ PM880_BUCK_AUDIO(BUCK1A_AUDIO, 7, 4, buck_audio_volt_range1, 0x50),
+ PM880_BUCK_AUDIO(BUCK1B_AUDIO, 7, 4, buck_audio_volt_range1, 0x50),
+};
+
+static const struct of_device_id pm88x_vrs_of_match[] = {
+ PM88X_VR_OF_MATCH("marvell,88pm88x-votg", VOTG),
+
+ PM886_BUCK_SLP_OF_MATCH("marvell,88pm886-buck1-slp", BUCK1_SLP),
+
+ PM880_BUCK_SLP_OF_MATCH("marvell,88pm880-buck1a-slp", BUCK1A_SLP),
+ PM880_BUCK_SLP_OF_MATCH("marvell,88pm880-buck1b-slp", BUCK1B_SLP),
+
+ PM880_BUCK_AUDIO_OF_MATCH("marvell,88pm880-buck1a-audio", BUCK1A_AUDIO),
+ PM880_BUCK_AUDIO_OF_MATCH("marvell,88pm880-buck1b-audio", BUCK1B_AUDIO),
+};
+
+static struct regmap *nr_to_regmap(struct pm88x_chip *chip, unsigned int nr)
+{
+ switch (nr) {
+ case 0:
+ return chip->base_regmap;
+ case 1:
+ return chip->ldo_regmap;
+ case 2:
+ return chip->gpadc_regmap;
+ case 3:
+ return chip->battery_regmap;
+ case 4:
+ return chip->buck_regmap;
+ case 7:
+ return chip->test_regmap;
+ default:
+ pr_err("unsupported pages.\n");
+ return NULL;
+ }
+}
+
+static int of_get_legacy_init_data(struct device *dev,
+ struct regulator_init_data **init_data)
+{
+ struct device_node *node = dev->of_node;
+ struct regulator_consumer_supply *consumer_supplies;
+ int len, num, ret, i;
+
+ len = of_property_count_strings(node, "marvell,consumer-supplies");
+ if (len <= 0)
+ return 0;
+
+ /* format: marvell,consumer-supplies = "supply-name", "dev-name" */
+ if (len % 2 != 0) {
+ dev_err(dev,
+ "format should be: marvell,consumer-supplies = supply-name, dev-name\n");
+
+ return -EINVAL;
+ }
+
+ num = len / 2;
+ consumer_supplies = devm_kzalloc(dev,
+ sizeof(struct regulator_consumer_supply) * num,
+ GFP_KERNEL);
+ if (!consumer_supplies) {
+ dev_err(dev, "alloc memory fails.\n");
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < num; i++) {
+ ret = of_property_read_string_index(node,
+ "marvell,consumer-supplies",
+ i * 2,
+ &consumer_supplies[i].supply);
+ if (ret) {
+ dev_err(dev, "read property fails.\n");
+ devm_kfree(dev, consumer_supplies);
+ return ret;
+ }
+
+ ret = of_property_read_string_index(node,
+ "marvell,consumer-supplies",
+ i * 2 + 1,
+ &consumer_supplies[i].dev_name);
+ if (ret) {
+ dev_err(dev, "read property fails.\n");
+ devm_kfree(dev, consumer_supplies);
+ return ret;
+ }
+
+ if (!strcmp((consumer_supplies[i].dev_name), "nameless"))
+ consumer_supplies[i].dev_name = NULL;
+
+ }
+
+ (*init_data)->consumer_supplies = consumer_supplies;
+ (*init_data)->num_consumer_supplies = num;
+
+ return 0;
+}
+
+/*
+ * The function convert the vr voltage register value
+ * to a real voltage value (in uV) according to the voltage table.
+ */
+static int pm88x_get_vvr_vol(unsigned int val, unsigned int n_linear_ranges,
+ const struct regulator_linear_range *ranges)
+{
+ const struct regulator_linear_range *range;
+ int i, volt = -EINVAL;
+
+ /* get the voltage via the register value */
+ for (i = 0; i < n_linear_ranges; i++) {
+ range = &ranges[i];
+ if (!range)
+ return -EINVAL;
+
+ if (val >= range->min_sel && val <= range->max_sel) {
+ volt = (val - range->min_sel) * range->uV_step + range->min_uV;
+ break;
+ }
+ }
+ return volt;
+}
+
+/* The function check if the regulator register is configured to enable/disable */
+static int pm88x_check_en(struct regmap *map, unsigned int reg, unsigned int mask)
+{
+ int ret, value;
+ unsigned int enable1;
+
+ ret = regmap_read(map, reg, &enable1);
+ if (ret < 0)
+ return ret;
+
+ value = enable1 & mask;
+
+ return value;
+}
+
+/* The function return the value in the regulator voltage register */
+static unsigned int pm88x_check_vol(struct regmap *map, unsigned int reg, unsigned int mask)
+{
+ int ret;
+ unsigned int vol_val;
+
+ ret = regmap_bulk_read(map, reg, &vol_val, 1);
+ if (ret < 0)
+ return ret;
+
+ /* mask and shift the relevant value from the register */
+ vol_val = (vol_val & mask) >> (ffs(mask) - 1);
+
+ return vol_val;
+}
+
+static int pm88x_update_print(struct pm88x_chip *chip, struct regmap *map,
+ struct regulator_desc *desc, struct pm88x_vr_print *print_temp,
+ int index, int num)
+{
+ int ret, volt;
+
+ sprintf(print_temp->name, "%s", desc->name);
+
+ /* check enable/disable */
+ ret = pm88x_check_en(map, desc->enable_reg, desc->enable_mask);
+ if (ret < 0)
+ return ret;
+ else if (ret)
+ strcpy(print_temp->enable, "enable");
+ else
+ strcpy(print_temp->enable, "disable");
+
+ /* no sleep mode */
+ strcpy(print_temp->slp_mode, " -");
+
+ /* no sleep voltage */
+ sprintf(print_temp->set_slp, " -");
+
+ /* print active voltage(s) */
+ ret = pm88x_check_vol(map, desc->vsel_reg, desc->vsel_mask);
+ if (ret < 0)
+ return ret;
+
+ if (desc->n_linear_ranges) {
+ volt = pm88x_get_vvr_vol(ret, desc->n_linear_ranges, desc->linear_ranges);
+ if (volt < 0)
+ return volt;
+ else
+ sprintf(print_temp->volt, "%4d", volt/1000);
+ } else {
+ sprintf(print_temp->volt, " -");
+ }
+ /* no audio mode*/
+ strcpy(print_temp->audio_en, " - ");
+ sprintf(print_temp->audio, " -");
+
+ return 0;
+}
+
+int pm88x_display_vr(struct pm88x_chip *chip, char *buf)
+{
+ struct pm88x_vr_print *print_temp;
+ struct pm88x_buck_slp_info *slp_info = NULL;
+ struct pm88x_buck_audio_info *audio_info = NULL;
+ struct pm88x_vr_info *vr_info = NULL;
+ struct regmap *map;
+ int slp_num, audio_num, vr_num, i, len = 0;
+ ssize_t ret;
+
+ switch (chip->type) {
+ case PM886:
+ slp_info = pm886_buck_slp_configs;
+ slp_num = ARRAY_SIZE(pm886_buck_slp_configs);
+ vr_info = pm88x_vr_configs;
+ vr_num = ARRAY_SIZE(pm88x_vr_configs);
+ break;
+ case PM880:
+ slp_info = pm880_buck_slp_configs;
+ slp_num = ARRAY_SIZE(pm880_buck_slp_configs);
+ audio_info = pm880_buck_audio_configs;
+ audio_num = ARRAY_SIZE(pm880_buck_audio_configs);
+ vr_info = pm88x_vr_configs;
+ vr_num = ARRAY_SIZE(pm88x_vr_configs);
+ break;
+ default:
+ pr_err("%s: Cannot find chip type.\n", __func__);
+ return -ENODEV;
+ }
+
+ print_temp = kmalloc(sizeof(struct pm88x_vr_print), GFP_KERNEL);
+ if (!print_temp) {
+ pr_err("%s: Cannot allocate print template.\n", __func__);
+ return -ENOMEM;
+ }
+
+ len += sprintf(buf + len, "\nVirtual Regulator");
+ len += sprintf(buf + len, "\n------------------------------------");
+ len += sprintf(buf + len, "--------------------------------------\n");
+ len += sprintf(buf + len, "| name | status | slp_mode |slp_volt");
+ len += sprintf(buf + len, "| volt | audio_en| audio |\n");
+ len += sprintf(buf + len, "-------------------------------------");
+ len += sprintf(buf + len, "-------------------------------------\n");
+
+ if (slp_info) {
+ map = nr_to_regmap(chip, slp_info->page_nr);
+ for (i = 0; i < slp_num; i++) {
+ ret = pm88x_update_print(chip, map, &slp_info[i].desc,
+ print_temp, i, slp_num);
+ if (ret < 0) {
+ pr_err("Print of regulator %s failed\n", print_temp->name);
+ goto out_print;
+ }
+ len += sprintf(buf + len, "|%-12s|", print_temp->name);
+ len += sprintf(buf + len, " %-7s |", print_temp->enable);
+ len += sprintf(buf + len, " %-10s|", print_temp->slp_mode);
+ len += sprintf(buf + len, " %-5s |", print_temp->set_slp);
+ len += sprintf(buf + len, " %-5s |", print_temp->volt);
+ len += sprintf(buf + len, " %-7s |", print_temp->audio_en);
+ len += sprintf(buf + len, " %-5s |\n", print_temp->audio);
+ }
+ }
+
+ if (audio_info) {
+ map = nr_to_regmap(chip, audio_info->page_nr);
+ for (i = 0; i < audio_num; i++) {
+ ret = pm88x_update_print(chip, map, &audio_info[i].desc,
+ print_temp, i, audio_num);
+ if (ret < 0) {
+ pr_err("Print of regulator %s failed\n", print_temp->name);
+ goto out_print;
+ }
+ len += sprintf(buf + len, "|%-12s|", print_temp->name);
+ len += sprintf(buf + len, " %-7s |", print_temp->enable);
+ len += sprintf(buf + len, " %-10s|", print_temp->slp_mode);
+ len += sprintf(buf + len, " %-5s |", print_temp->set_slp);
+ len += sprintf(buf + len, " %-5s |", print_temp->volt);
+ len += sprintf(buf + len, " %-7s |", print_temp->audio_en);
+ len += sprintf(buf + len, " %-5s |\n", print_temp->audio);
+ }
+ }
+
+ if (vr_info) {
+ map = nr_to_regmap(chip, vr_info->page_nr);
+ for (i = 0; i < vr_num; i++) {
+ ret = pm88x_update_print(chip, map, &vr_info[i].desc,
+ print_temp, i, vr_num);
+ if (ret < 0) {
+ pr_err("Print of regulator %s failed\n", print_temp->name);
+ goto out_print;
+ }
+ len += sprintf(buf + len, "|%-12s|", print_temp->name);
+ len += sprintf(buf + len, " %-7s |", print_temp->enable);
+ len += sprintf(buf + len, " %-10s|", print_temp->slp_mode);
+ len += sprintf(buf + len, " %-5s |", print_temp->set_slp);
+ len += sprintf(buf + len, " %-5s |", print_temp->volt);
+ len += sprintf(buf + len, " %-7s |", print_temp->audio_en);
+ len += sprintf(buf + len, " %-5s |\n", print_temp->audio);
+ }
+ }
+
+ len += sprintf(buf + len, "-------------------------------------");
+ len += sprintf(buf + len, "-------------------------------------\n");
+
+ ret = len;
+out_print:
+ kfree(print_temp);
+ return ret;
+}
+
+static int pm88x_virtual_regulator_probe(struct platform_device *pdev)
+{
+ struct pm88x_chip *chip = dev_get_drvdata(pdev->dev.parent);
+ struct pm88x_regulators *data;
+ struct regulator_config config = { };
+ struct regulator_init_data *init_data;
+ struct regulation_constraints *c;
+ const struct of_device_id *match;
+ const struct pm88x_vr_info *const_info;
+ struct pm88x_vr_info *info;
+ int ret;
+
+ match = of_match_device(pm88x_vrs_of_match, &pdev->dev);
+ if (match) {
+ const_info = match->data;
+ init_data = of_get_regulator_init_data(&pdev->dev,
+ pdev->dev.of_node);
+ ret = of_get_legacy_init_data(&pdev->dev, &init_data);
+ if (ret < 0)
+ return ret;
+ } else {
+ dev_err(&pdev->dev, "parse dts fails!\n");
+ return -EINVAL;
+ }
+
+ info = kmemdup(const_info, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(struct pm88x_regulators),
+ GFP_KERNEL);
+ if (!data) {
+ dev_err(&pdev->dev, "failed to allocate pm88x_regualtors");
+ return -ENOMEM;
+ }
+ data->map = nr_to_regmap(chip, info->page_nr);
+ data->chip = chip;
+
+ /* add regulator config */
+ config.dev = &pdev->dev;
+ config.init_data = init_data;
+ config.driver_data = info;
+ config.regmap = data->map;
+ config.of_node = pdev->dev.of_node;
+
+ data->rdev = devm_regulator_register(&pdev->dev, &info->desc, &config);
+ if (IS_ERR(data->rdev)) {
+ dev_err(&pdev->dev, "cannot register %s\n", info->desc.name);
+ ret = PTR_ERR(data->rdev);
+ return ret;
+ }
+
+ c = data->rdev->constraints;
+ if (info->desc.ops->enable)
+ c->valid_ops_mask |= REGULATOR_CHANGE_STATUS;
+ if (info->desc.ops->set_voltage_sel)
+ c->valid_ops_mask |= REGULATOR_CHANGE_VOLTAGE;
+
+ platform_set_drvdata(pdev, data);
+
+ return 0;
+}
+
+static int pm88x_virtual_regulator_remove(struct platform_device *pdev)
+{
+ struct pm88x_regulators *data = platform_get_drvdata(pdev);
+ devm_kfree(&pdev->dev, data);
+ return 0;
+}
+
+static struct platform_driver pm88x_virtual_regulator_driver = {
+ .driver = {
+ .name = "88pm88x-virtual-regulator",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(pm88x_vrs_of_match),
+ },
+ .probe = pm88x_virtual_regulator_probe,
+ .remove = pm88x_virtual_regulator_remove,
+};
+
+static int pm88x_virtual_regulator_init(void)
+{
+ return platform_driver_register(&pm88x_virtual_regulator_driver);
+}
+subsys_initcall(pm88x_virtual_regulator_init);
+
+static void pm88x_virtual_regulator_exit(void)
+{
+ platform_driver_unregister(&pm88x_virtual_regulator_driver);
+}
+module_exit(pm88x_virtual_regulator_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Yi Zhang <yizhang@marvell.com>");
+MODULE_DESCRIPTION("for virtual supply in Marvell 88PM88X PMIC");
+MODULE_ALIAS("platform:88pm88x-vr");
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 965d4f0c18a6..484baabf3c41 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -1663,4 +1663,14 @@ config REGULATOR_QCOM_LABIBB
boost regulator and IBB can be used as a negative boost regulator
for LCD display panel.
+config REGULATOR_88PM88X
+ tristate "Marvell 88PM88X Power regulators"
+ depends on MFD_88PM88X
+ depends on I2C
+ help
+ This driver supports Marvell 88PM88X voltage regulator chips.
+ It delivers digitally programmable output, the voltage is
+ programmed via I2C interface.
+ It has 5 bucks and 16 ldos. Buck1 has DVC feature
+
endif
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 23074714a81a..8a1e6f454760 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -63,6 +63,9 @@ obj-$(CONFIG_REGULATOR_LP873X) += lp873x-regulator.o
obj-$(CONFIG_REGULATOR_LP87565) += lp87565-regulator.o
obj-$(CONFIG_REGULATOR_LP8788) += lp8788-buck.o
obj-$(CONFIG_REGULATOR_LP8788) += lp8788-ldo.o
+obj-$(CONFIG_REGULATOR_88PM88X) += 88pm88x-ldo.o
+obj-$(CONFIG_REGULATOR_88PM88X) += 88pm88x-buck.o
+obj-$(CONFIG_REGULATOR_88PM88X) += 88pm88x-vr.o
obj-$(CONFIG_REGULATOR_LP8755) += lp8755.o
obj-$(CONFIG_REGULATOR_LTC3589) += ltc3589.o
obj-$(CONFIG_REGULATOR_LTC3676) += ltc3676.o
--
2.42.0