2177 lines
65 KiB
Diff
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
|
|
|