[PATCH 2/2] Regulator: add driver for Freescale MC34708
Martin Fuzzey
mfuzzey at parkeon.com
Tue Apr 28 07:17:40 PDT 2015
Signed-off-by: Martin Fuzzey <mfuzzey at parkeon.com>
---
drivers/regulator/Kconfig | 7
drivers/regulator/Makefile | 1
drivers/regulator/mc34708-regulator.c | 1266 +++++++++++++++++++++++++++++++++
3 files changed, 1274 insertions(+)
create mode 100644 drivers/regulator/mc34708-regulator.c
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 31ac1bf..0d03c39 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -440,6 +440,13 @@ config REGULATOR_MC13892
Say y here to support the regulators found on the Freescale MC13892
PMIC.
+config REGULATOR_MC34708
+ tristate "Freescale MC34708 regulator driver"
+ depends on MFD_MC13XXX
+ help
+ Say y here to support the regulators found on the Freescale MC34708
+ and MC34709 PMICs.
+
config REGULATOR_PALMAS
tristate "TI Palmas PMIC Regulators"
depends on MFD_PALMAS
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index c1b4fba..6382f6c 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -59,6 +59,7 @@ obj-$(CONFIG_REGULATOR_MAX77802) += max77802.o
obj-$(CONFIG_REGULATOR_MC13783) += mc13783-regulator.o
obj-$(CONFIG_REGULATOR_MC13892) += mc13892-regulator.o
obj-$(CONFIG_REGULATOR_MC13XXX_CORE) += mc13xxx-regulator-core.o
+obj-$(CONFIG_REGULATOR_MC34708) += mc34708-regulator.o
obj-$(CONFIG_REGULATOR_QCOM_RPM) += qcom_rpm-regulator.o
obj-$(CONFIG_REGULATOR_PALMAS) += palmas-regulator.o
obj-$(CONFIG_REGULATOR_PFUZE100) += pfuze100-regulator.o
diff --git a/drivers/regulator/mc34708-regulator.c b/drivers/regulator/mc34708-regulator.c
new file mode 100644
index 0000000..b5ff727
--- /dev/null
+++ b/drivers/regulator/mc34708-regulator.c
@@ -0,0 +1,1266 @@
+/*
+ * Driver for regulators in the Fresscale MC34708/9 PMICs
+ *
+ * The hardware uses different schemes for the three types of regulators:
+ *
+ * The SWx regulators:
+ * A single register field multiplexes enable/disable and mode
+ * selection, for both normal and standby state.
+ * Independent voltage control in normal and standby states.
+ *
+ * The SWBST regulator:
+ * One register field for combined enable/disable and mode selection
+ * in normal state and a second field for standby state.
+ * Single voltage control for both normal and standby states.
+ *
+ * The LDO regulators:
+ * Enable bit (not multiplexed with mode)
+ * Single voltage control for both normal and standby states.
+ * Standby and Mode bits shared by standby and normal states
+ *
+ * The mc13xxx-regulator-core is not a good fit for the above so it is
+ * not used.
+ *
+ * Copyright 2015 Parkeon
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ */
+
+#include <linux/err.h>
+#include <linux/mfd/mc13xxx.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/of_regulator.h>
+
+struct mc34708_hw_mode {
+ s8 normal;
+ s8 alt_normal;
+ s8 standby;
+};
+
+struct mc34708_regulator;
+
+/* Common to regulators of the same type */
+struct mc34708_regulator_kind {
+ int (*setup)(struct mc34708_regulator *);
+ unsigned int (*of_map_mode)(unsigned int);
+ const struct mc34708_hw_mode *hw_modes;
+ int num_hw_modes;
+ const struct regulator_ops ops;
+};
+
+/* Specific for each regulator */
+struct mc34708_regulator_def {
+ const char *name;
+ const struct mc34708_regulator_kind *kind;
+ const char *supply_name;
+ unsigned int enable_reg;
+ unsigned int enable_mask;
+ unsigned int n_voltages;
+ unsigned int fixed_uV;
+ unsigned int min_uV;
+ unsigned int uV_step;
+ const unsigned int *volt_table;
+ unsigned int vsel_reg;
+ unsigned int vsel_mask;
+ unsigned int vsel_stdby_mask;
+ unsigned int mode_reg;
+ unsigned int mode_mask;
+ unsigned int mode_stdby_mask;
+};
+
+struct mc34708_regulator {
+ struct device *dev;
+ struct mc13xxx *mc13xxx;
+ const struct mc34708_regulator_def *def;
+ struct regulator_desc desc;
+ struct regulator_config config;
+ unsigned int req_mode_normal;
+ unsigned int req_mode_standby;
+ bool suspend_off;
+};
+
+struct mc34708_drv_data {
+ u32 saved_regmode0;
+ struct mc34708_regulator regulators[0];
+};
+
+/* ********************************************************************** */
+/* Common helpers */
+/* ********************************************************************** */
+
+static int mc34708_read_bits(struct mc34708_regulator *mc34708_reg,
+ unsigned int reg, u32 mask)
+{
+ int ret;
+ u32 val;
+
+ ret = mc13xxx_reg_read(mc34708_reg->mc13xxx, reg, &val);
+ if (ret < 0)
+ return ret;
+
+ val &= mask;
+ val >>= ffs(mask) - 1;
+
+ return val;
+}
+
+static int mc34708_update_bits(struct mc34708_regulator *mc34708_reg,
+ unsigned int reg, u32 mask, u32 val)
+{
+ val <<= ffs(mask) - 1;
+
+ return mc13xxx_reg_rmw(mc34708_reg->mc13xxx, reg, mask, val);
+}
+
+static int mc34708_get_voltage_sel(struct regulator_dev *rdev)
+{
+ struct mc34708_regulator *mc34708_reg = rdev->reg_data;
+
+ return mc34708_read_bits(mc34708_reg,
+ rdev->desc->vsel_reg, rdev->desc->vsel_mask);
+}
+
+static int mc34708_set_voltage_sel(struct regulator_dev *rdev, unsigned sel)
+{
+ struct mc34708_regulator *mc34708_reg = rdev->reg_data;
+
+ return mc34708_update_bits(mc34708_reg,
+ rdev->desc->vsel_reg,
+ rdev->desc->vsel_mask,
+ sel);
+}
+
+static int mc34708_set_suspend_voltage(struct regulator_dev *rdev, int uV)
+{
+ struct mc34708_regulator *mc34708_reg = rdev->reg_data;
+ int sel;
+
+ sel = regulator_map_voltage_linear(rdev, uV, uV);
+ if (sel < 0)
+ return sel;
+
+ return mc34708_update_bits(mc34708_reg,
+ rdev->desc->vsel_reg,
+ mc34708_reg->def->vsel_stdby_mask,
+ sel);
+}
+
+static const struct mc34708_hw_mode *mc34708_get_hw_mode(
+ struct mc34708_regulator *mc34708_reg)
+{
+ int val;
+ const struct mc34708_hw_mode *hwmode;
+
+ val = mc34708_read_bits(mc34708_reg,
+ mc34708_reg->def->mode_reg,
+ mc34708_reg->def->mode_mask);
+ if (val < 0)
+ return ERR_PTR(val);
+
+ BUG_ON(val >= mc34708_reg->def->kind->num_hw_modes);
+ hwmode = &mc34708_reg->def->kind->hw_modes[val];
+
+ dev_dbg(mc34708_reg->dev, "%s: HwMode=0x%x => normal=%d standby=%d\n",
+ mc34708_reg->def->name, val,
+ hwmode->normal, hwmode->standby);
+
+ return hwmode;
+}
+
+static int mc34708_sw_find_hw_mode_sel(
+ const struct mc34708_regulator_kind *kind,
+ unsigned int normal,
+ unsigned int standby)
+{
+ const struct mc34708_hw_mode *mode;
+ int i;
+
+ for (i = 0, mode = kind->hw_modes;
+ i < kind->num_hw_modes; i++, mode++) {
+ if (mode->normal == -1 || mode->standby == -1)
+ continue;
+
+ if (mode->standby != standby)
+ continue;
+
+ if ((mode->normal == normal) ||
+ (normal && (mode->alt_normal == normal)))
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+static int mc34708_update_hw_mode(
+ struct mc34708_regulator *mc34708_reg,
+ unsigned int normal,
+ unsigned int standby)
+{
+ int sel;
+
+ sel = mc34708_sw_find_hw_mode_sel(mc34708_reg->def->kind,
+ normal, standby);
+ if (sel < 0) {
+ dev_err(mc34708_reg->dev,
+ "%s: no hardware mode for normal=%d standby=%d\n",
+ mc34708_reg->def->name,
+ normal,
+ standby);
+
+ return sel;
+ }
+
+ dev_dbg(mc34708_reg->dev, "%s: normal=%d standby=%d => HwMODE=0x%x\n",
+ mc34708_reg->def->name,
+ normal,
+ standby,
+ sel);
+
+ return mc34708_update_bits(mc34708_reg,
+ mc34708_reg->def->mode_reg,
+ mc34708_reg->def->mode_mask,
+ sel);
+}
+
+/* ********************************************************************** */
+/* SWx regulator support */
+/* ********************************************************************** */
+
+/*
+ * Mapping of SWxMODE bits to mode in normal and standby state.
+ * The hardware has the follwoing modes (in order of increasing power):
+ * OFF
+ * PFM (Pulse Frequency Modulation) for low loads
+ * APS (Automatic Pulse Skip)
+ * PWM (Pulse Width Modulation) for high loads
+ *
+ * Not all combinations are possible. The mode in normal state cannot
+ * be lower power than that in standby state.
+ *
+ * We map these to Linux regulator modes as follows:
+ * PFM : REGULATOR_MODE_STANDBY
+ * PWM : REGULATOR_MODE_FAST
+ * APS : REGULATOR_MODE_NORMAL
+ * OFF : 0
+ * Reserved : -1
+ */
+static const struct mc34708_hw_mode mc34708_sw_modes[] = {
+ { .normal = 0, .standby = 0 },
+ { .normal = REGULATOR_MODE_FAST, .standby = 0 },
+ { .normal = -1, .standby = -1 },
+ { .normal = REGULATOR_MODE_STANDBY, .standby = 0 },
+ { .normal = REGULATOR_MODE_NORMAL, .standby = 0 },
+ { .normal = REGULATOR_MODE_FAST, .standby = REGULATOR_MODE_FAST },
+ { .normal = REGULATOR_MODE_FAST, .standby = REGULATOR_MODE_NORMAL },
+ { .normal = 0, .standby = 0 },
+ { .normal = REGULATOR_MODE_NORMAL, .standby = REGULATOR_MODE_NORMAL },
+ { .normal = -1, .standby = -1 },
+ { .normal = -1, .standby = -1 },
+ { .normal = -1, .standby = -1 },
+ { .normal = REGULATOR_MODE_NORMAL, .standby = REGULATOR_MODE_STANDBY },
+ { .normal = REGULATOR_MODE_FAST, .standby = REGULATOR_MODE_STANDBY },
+ { .normal = -1, .standby = -1 },
+ { .normal = REGULATOR_MODE_STANDBY, .standby = REGULATOR_MODE_STANDBY },
+};
+
+#define MC34708_SW_OPMODE_PFM 1
+#define MC34708_SW_OPMODE_APS 2
+#define MC34708_SW_OPMODE_PWM 3
+
+static unsigned int mc34708_sw_of_map_mode(unsigned int mode)
+{
+ switch (mode) {
+ case MC34708_SW_OPMODE_PFM:
+ return REGULATOR_MODE_STANDBY;
+ case MC34708_SW_OPMODE_APS:
+ return REGULATOR_MODE_NORMAL;
+ case MC34708_SW_OPMODE_PWM:
+ return REGULATOR_MODE_FAST;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mc34708_sw_setup(struct mc34708_regulator *mc34708_reg)
+{
+ const struct mc34708_hw_mode *hw_mode;
+
+ hw_mode = mc34708_get_hw_mode(mc34708_reg);
+ if (IS_ERR(hw_mode))
+ return PTR_ERR(hw_mode);
+
+ /*
+ * Be safe and bail out without touching hardware if
+ * the initial state is "reserved"
+ */
+ if (hw_mode->normal < 0) {
+ dev_err(mc34708_reg->dev,
+ "%s in reserved mode for normal\n",
+ mc34708_reg->def->name);
+ return -EINVAL;
+ }
+ if (hw_mode->standby < 0) {
+ dev_err(mc34708_reg->dev,
+ "%s in reserved mode for standby\n",
+ mc34708_reg->def->name);
+ return -EINVAL;
+ }
+
+ if (hw_mode->normal > 0)
+ mc34708_reg->req_mode_normal = hw_mode->normal;
+ else
+ /*
+ * If regulator is intially off we don't know the mode
+ * but we need a mode to be able to enable it later.
+ */
+ mc34708_reg->req_mode_normal = REGULATOR_MODE_NORMAL;
+
+ mc34708_reg->req_mode_standby = hw_mode->standby;
+ if (!hw_mode->standby)
+ mc34708_reg->suspend_off = true;
+
+ return 0;
+}
+
+static int mc34708_sw_enable(struct regulator_dev *rdev)
+{
+ struct mc34708_regulator *mc34708_reg = rdev->reg_data;
+
+ return mc34708_update_hw_mode(mc34708_reg,
+ mc34708_reg->req_mode_normal,
+ mc34708_reg->req_mode_standby);
+}
+
+static int mc34708_sw_disable(struct regulator_dev *rdev)
+{
+ struct mc34708_regulator *mc34708_reg = rdev->reg_data;
+
+ return mc34708_update_hw_mode(mc34708_reg,
+ 0,
+ mc34708_reg->req_mode_standby);
+}
+
+static int mc34708_sw_is_enabled(struct regulator_dev *rdev)
+{
+ struct mc34708_regulator *mc34708_reg = rdev->reg_data;
+ const struct mc34708_hw_mode *hw_mode;
+
+ hw_mode = mc34708_get_hw_mode(mc34708_reg);
+ if (IS_ERR(hw_mode))
+ return PTR_ERR(hw_mode);
+
+ return hw_mode->normal > 0;
+}
+
+static unsigned int mc34708_sw_get_mode(struct regulator_dev *rdev)
+{
+ struct mc34708_regulator *mc34708_reg = rdev->reg_data;
+
+ return mc34708_reg->req_mode_normal;
+}
+
+static int mc34708_sw_set_mode(struct regulator_dev *rdev, unsigned int mode)
+{
+ struct mc34708_regulator *mc34708_reg = rdev->reg_data;
+ int enabled, ret;
+
+ enabled = mc34708_sw_is_enabled(rdev);
+ if (enabled < 0)
+ return enabled;
+
+ if (enabled) {
+ ret = mc34708_update_hw_mode(mc34708_reg,
+ mode,
+ mc34708_reg->req_mode_standby);
+ if (ret)
+ return ret;
+ }
+
+ mc34708_reg->req_mode_normal = mode;
+
+ return 0;
+}
+
+static int mc34708_sw_set_suspend_enable(struct regulator_dev *rdev)
+{
+ struct mc34708_regulator *mc34708_reg = rdev->reg_data;
+ int ret;
+
+ ret = mc34708_update_hw_mode(mc34708_reg,
+ mc34708_reg->req_mode_normal,
+ mc34708_reg->req_mode_standby);
+ if (!ret)
+ mc34708_reg->suspend_off = false;
+
+ return ret;
+}
+
+static int mc34708_sw_set_suspend_disable(struct regulator_dev *rdev)
+{
+ struct mc34708_regulator *mc34708_reg = rdev->reg_data;
+ int ret;
+
+ ret = mc34708_update_hw_mode(mc34708_reg,
+ mc34708_reg->req_mode_normal,
+ 0);
+ if (!ret)
+ mc34708_reg->suspend_off = true;
+
+ return ret;
+}
+
+static int mc34708_sw_set_suspend_mode(struct regulator_dev *rdev,
+ unsigned int mode)
+{
+ struct mc34708_regulator *mc34708_reg = rdev->reg_data;
+ int enabled, ret;
+
+ enabled = mc34708_sw_is_enabled(rdev);
+ if (enabled < 0)
+ return enabled;
+
+ ret = mc34708_update_hw_mode(mc34708_reg,
+ enabled ? mc34708_reg->req_mode_normal : 0,
+ mc34708_reg->suspend_off ? 0 : mode);
+ if (!ret)
+ mc34708_reg->req_mode_standby = mode;
+
+ return ret;
+}
+
+/* ********************************************************************** */
+/* SWBST regulator support */
+/* ********************************************************************** */
+
+static int mc34708_swbst_mode_to_hwmode(unsigned int mode)
+{
+ switch (mode) {
+ case REGULATOR_MODE_IDLE:
+ case REGULATOR_MODE_STANDBY:
+ return MC34708_SW_OPMODE_PFM;
+
+ case REGULATOR_MODE_NORMAL:
+ return MC34708_SW_OPMODE_APS;
+
+ case REGULATOR_MODE_FAST:
+ return MC34708_SW_OPMODE_PWM;
+
+ default:
+ return -EINVAL;
+ };
+}
+
+static int mc34708_swbst_hwmode_to_mode(unsigned int hwmode)
+{
+ return mc34708_sw_of_map_mode(hwmode);
+}
+
+static int mc34708_swbst_setup(struct mc34708_regulator *mc34708_reg)
+{
+ int val, mode;
+
+ val = mc34708_read_bits(mc34708_reg,
+ mc34708_reg->def->mode_reg,
+ mc34708_reg->def->mode_mask);
+ if (val < 0)
+ return val;
+
+ if (val > 0) {
+ mode = mc34708_swbst_hwmode_to_mode(val);
+ if (mode < 0)
+ return mode;
+
+ mc34708_reg->req_mode_normal = mode;
+ } else {
+ /*
+ * If regulator is intially off we don't know the mode
+ * but we need a mode to be able to enable it later.
+ */
+ mc34708_reg->req_mode_normal = REGULATOR_MODE_NORMAL;
+ }
+
+ dev_dbg(mc34708_reg->dev,
+ "%s: Initial normal mode hw=%d linux=%d\n",
+ mc34708_reg->def->name, val, mc34708_reg->req_mode_normal);
+
+ val = mc34708_read_bits(mc34708_reg,
+ mc34708_reg->def->mode_reg,
+ mc34708_reg->def->mode_stdby_mask);
+ if (val < 0)
+ return val;
+
+ if (val > 0) {
+ mode = mc34708_swbst_hwmode_to_mode(val);
+ if (mode < 0)
+ return mode;
+
+ mc34708_reg->req_mode_standby = mode;
+ } else {
+ /*
+ * If regulator is intially off we don't know the mode
+ * but we need a mode to be able to enable it later.
+ */
+ mc34708_reg->req_mode_standby = REGULATOR_MODE_STANDBY;
+ }
+
+ dev_dbg(mc34708_reg->dev, "Initial standby mode hw=%d linux=%d\n",
+ val,
+ mc34708_reg->req_mode_standby);
+
+ return 0;
+}
+
+static int mc34708_swbst_enable(struct regulator_dev *rdev)
+{
+ struct mc34708_regulator *mc34708_reg = rdev->reg_data;
+
+ return mc34708_update_bits(mc34708_reg,
+ mc34708_reg->def->mode_reg,
+ mc34708_reg->def->mode_mask,
+ mc34708_reg->req_mode_normal);
+}
+
+static int mc34708_swbst_disable(struct regulator_dev *rdev)
+{
+ struct mc34708_regulator *mc34708_reg = rdev->reg_data;
+
+ return mc34708_update_bits(mc34708_reg,
+ mc34708_reg->def->mode_reg,
+ mc34708_reg->def->mode_mask,
+ 0);
+}
+
+static int mc34708_swbst_is_enabled(struct regulator_dev *rdev)
+{
+ struct mc34708_regulator *mc34708_reg = rdev->reg_data;
+ int val;
+
+ val = mc34708_read_bits(mc34708_reg,
+ mc34708_reg->def->mode_reg,
+ mc34708_reg->def->mode_mask);
+
+ if (val < 0)
+ return val;
+
+ return val == 0 ? 0 : 1;
+}
+
+static unsigned int mc34708_swbst_get_mode(struct regulator_dev *rdev)
+{
+ struct mc34708_regulator *mc34708_reg = rdev->reg_data;
+
+ return mc34708_reg->req_mode_normal;
+}
+
+static int mc34708_swbst_set_mode(struct regulator_dev *rdev, unsigned int mode)
+{
+ struct mc34708_regulator *mc34708_reg = rdev->reg_data;
+ int enabled, hwmode, ret;
+
+ hwmode = mc34708_swbst_mode_to_hwmode(mode);
+ if (hwmode < 0)
+ return hwmode;
+
+ enabled = mc34708_swbst_is_enabled(rdev);
+ if (enabled < 0)
+ return enabled;
+
+ if (enabled) {
+ ret = mc34708_update_bits(mc34708_reg,
+ mc34708_reg->def->mode_reg,
+ mc34708_reg->def->mode_mask,
+ hwmode);
+ if (ret)
+ return ret;
+ }
+
+ mc34708_reg->req_mode_normal = mode;
+
+ return 0;
+}
+
+static int mc34708_swbst_set_suspend_enable(struct regulator_dev *rdev)
+{
+ struct mc34708_regulator *mc34708_reg = rdev->reg_data;
+ int hwmode;
+
+ hwmode = mc34708_swbst_mode_to_hwmode(mc34708_reg->req_mode_standby);
+ if (hwmode < 0)
+ return hwmode;
+
+ return mc34708_update_bits(mc34708_reg,
+ mc34708_reg->def->mode_reg,
+ mc34708_reg->def->mode_stdby_mask,
+ hwmode);
+}
+
+static int mc34708_swbst_set_suspend_disable(struct regulator_dev *rdev)
+{
+ struct mc34708_regulator *mc34708_reg = rdev->reg_data;
+
+ return mc34708_update_bits(mc34708_reg,
+ mc34708_reg->def->mode_reg,
+ mc34708_reg->def->mode_stdby_mask,
+ 0);
+}
+
+static int mc34708_swbst_set_suspend_mode(struct regulator_dev *rdev,
+ unsigned int mode)
+{
+ struct mc34708_regulator *mc34708_reg = rdev->reg_data;
+ int ret, hwmode;
+
+ hwmode = mc34708_swbst_mode_to_hwmode(mode);
+ if (hwmode < 0)
+ return hwmode;
+
+ ret = mc34708_update_bits(mc34708_reg,
+ mc34708_reg->def->mode_reg,
+ mc34708_reg->def->mode_stdby_mask,
+ hwmode);
+ if (!ret)
+ mc34708_reg->req_mode_standby = mode;
+
+ return ret;
+}
+
+/* ********************************************************************** */
+/* LDO regulator support */
+/* ********************************************************************** */
+
+/*
+ * 3 types mode / standby configuration for LDOs:
+ * No mode configuration
+ * Single bit mode cofiguration (standby bit)
+ * 2 bit mode configuration mode, standby
+ *
+ * LDO states (excluding enable bit)
+ * VxMODE VxSTBY Normal Standby
+ * 0 0 ON ON
+ * 0 1 ON OFF
+ * 1 0 LP LP
+ * 1 1 ON LP
+ *
+ * Note that it is not possible to have Normal=LP, Standby=OFF
+ * If this state is requested we will use Normal=ON, Standby=OFF until exit
+ * from suspend.
+ * Hence the .alt_normal below which is used for extra matching
+ */
+static const struct mc34708_hw_mode mc34708_ldo_modes[] = {
+ {
+ .normal = REGULATOR_MODE_NORMAL,
+ .standby = REGULATOR_MODE_NORMAL
+ },
+ {
+ .normal = REGULATOR_MODE_NORMAL,
+ .alt_normal = REGULATOR_MODE_STANDBY,
+ .standby = 0
+ },
+ {
+ .normal = REGULATOR_MODE_STANDBY,
+ .standby = REGULATOR_MODE_STANDBY
+ },
+ {
+ .normal = REGULATOR_MODE_NORMAL,
+ .standby = REGULATOR_MODE_STANDBY
+ },
+};
+
+#define MC34708_LDO_OPMODE_LOWPOWER 1
+#define MC34708_LDO_OPMODE_NORMAL 2
+
+static unsigned int mc34708_ldo_of_map_mode(unsigned int mode)
+{
+ switch (mode) {
+ case MC34708_LDO_OPMODE_NORMAL:
+ return REGULATOR_MODE_NORMAL;
+ case MC34708_LDO_OPMODE_LOWPOWER:
+ return REGULATOR_MODE_STANDBY;
+ default:
+ return -EINVAL;
+ }
+}
+
+static bool mc34708_ldo_has_mode_bit(struct mc34708_regulator *mc34708_reg)
+{
+ unsigned int mask = mc34708_reg->def->mode_mask;
+
+ if (!mask)
+ return false;
+
+ mask >>= ffs(mask) - 1;
+
+ return mask == 3;
+}
+
+static int mc34708_ldo_setup(struct mc34708_regulator *mc34708_reg)
+{
+ const struct mc34708_hw_mode *hw_mode;
+
+ hw_mode = mc34708_get_hw_mode(mc34708_reg);
+ if (IS_ERR(hw_mode))
+ return PTR_ERR(hw_mode);
+
+ mc34708_reg->req_mode_normal = hw_mode->normal;
+ if (hw_mode->standby) {
+ mc34708_reg->suspend_off = false;
+ mc34708_reg->req_mode_standby = hw_mode->standby;
+ } else {
+ /* If configured for off in standby we still need a mode to
+ * use when .set_suspend_enable() is called.
+ * That mode depends if the regulator has a mode bit.
+ */
+ mc34708_reg->suspend_off = true;
+ if (mc34708_ldo_has_mode_bit(mc34708_reg))
+ mc34708_reg->req_mode_standby = REGULATOR_MODE_STANDBY;
+ else
+ mc34708_reg->req_mode_standby = REGULATOR_MODE_NORMAL;
+ }
+
+ return 0;
+}
+
+static int mc34708_ldo_enable(struct regulator_dev *rdev)
+{
+ struct mc34708_regulator *mc34708_reg = rdev->reg_data;
+
+ return mc34708_update_bits(mc34708_reg,
+ rdev->desc->enable_reg,
+ rdev->desc->enable_mask,
+ 1);
+}
+
+static int mc34708_ldo_disable(struct regulator_dev *rdev)
+{
+ struct mc34708_regulator *mc34708_reg = rdev->reg_data;
+
+ return mc34708_update_bits(mc34708_reg,
+ rdev->desc->enable_reg,
+ rdev->desc->enable_mask,
+ 0);
+}
+
+static int mc34708_ldo_is_enabled(struct regulator_dev *rdev)
+{
+ struct mc34708_regulator *mc34708_reg = rdev->reg_data;
+
+ return mc34708_read_bits(mc34708_reg,
+ rdev->desc->enable_reg,
+ rdev->desc->enable_mask);
+}
+
+static int mc34708_ldo_vhalf_get_voltage(struct regulator_dev *rdev)
+{
+ int ret;
+
+ if (!rdev->supply)
+ return -EINVAL;
+
+ ret = regulator_get_voltage(rdev->supply);
+ if (ret > 0)
+ ret /= 2;
+
+ return ret;
+}
+
+static unsigned int mc34708_ldo_get_mode(struct regulator_dev *rdev)
+{
+ struct mc34708_regulator *mc34708_reg = rdev->reg_data;
+
+ return mc34708_reg->req_mode_normal;
+}
+
+static int mc34708_ldo_set_mode(struct regulator_dev *rdev, unsigned int mode)
+{
+ struct mc34708_regulator *mc34708_reg = rdev->reg_data;
+ int ret;
+
+ if (!mc34708_ldo_has_mode_bit(mc34708_reg))
+ return 0;
+
+ ret = mc34708_update_hw_mode(mc34708_reg,
+ mode,
+ mc34708_reg->req_mode_standby);
+ if (!ret)
+ mc34708_reg->req_mode_normal = mode;
+
+ return ret;
+}
+
+static int mc34708_ldo_set_suspend_enable(struct regulator_dev *rdev)
+{
+ struct mc34708_regulator *mc34708_reg = rdev->reg_data;
+ int ret;
+
+ ret = mc34708_update_hw_mode(mc34708_reg,
+ mc34708_reg->req_mode_normal,
+ mc34708_reg->req_mode_standby);
+ if (!ret)
+ mc34708_reg->suspend_off = false;
+
+ return ret;
+}
+
+static int mc34708_ldo_set_suspend_disable(struct regulator_dev *rdev)
+{
+ struct mc34708_regulator *mc34708_reg = rdev->reg_data;
+ int ret;
+
+ ret = mc34708_update_hw_mode(mc34708_reg,
+ mc34708_reg->req_mode_normal,
+ 0);
+ if (!ret)
+ mc34708_reg->suspend_off = true;
+
+ return ret;
+}
+
+static int mc34708_ldo_set_suspend_mode(struct regulator_dev *rdev,
+ unsigned int mode)
+{
+ struct mc34708_regulator *mc34708_reg = rdev->reg_data;
+ int ret;
+
+ if (!mc34708_ldo_has_mode_bit(mc34708_reg))
+ return 0;
+
+ ret = mc34708_update_hw_mode(mc34708_reg,
+ mc34708_reg->req_mode_normal,
+ mc34708_reg->suspend_off ? 0 : mode);
+ if (!ret)
+ mc34708_reg->req_mode_standby = mode;
+
+ return ret;
+}
+
+static const struct mc34708_regulator_kind mc34708_sw_kind = {
+ .setup = mc34708_sw_setup,
+ .hw_modes = mc34708_sw_modes,
+ .num_hw_modes = ARRAY_SIZE(mc34708_sw_modes),
+ .of_map_mode = mc34708_sw_of_map_mode,
+ .ops = {
+ .enable = mc34708_sw_enable,
+ .disable = mc34708_sw_disable,
+ .is_enabled = mc34708_sw_is_enabled,
+
+ .list_voltage = regulator_list_voltage_linear,
+ .get_voltage_sel = mc34708_get_voltage_sel,
+ .set_voltage_sel = mc34708_set_voltage_sel,
+
+ .get_mode = mc34708_sw_get_mode,
+ .set_mode = mc34708_sw_set_mode,
+
+ .set_suspend_voltage = mc34708_set_suspend_voltage,
+ .set_suspend_enable = mc34708_sw_set_suspend_enable,
+ .set_suspend_disable = mc34708_sw_set_suspend_disable,
+ .set_suspend_mode = mc34708_sw_set_suspend_mode,
+ },
+};
+
+static const struct mc34708_regulator_kind mc34708_swbst_kind = {
+ .setup = mc34708_swbst_setup,
+ .of_map_mode = mc34708_sw_of_map_mode,
+ .ops = {
+ .enable = mc34708_swbst_enable,
+ .disable = mc34708_swbst_disable,
+ .is_enabled = mc34708_swbst_is_enabled,
+
+ .list_voltage = regulator_list_voltage_linear,
+ .get_voltage_sel = mc34708_get_voltage_sel,
+ .set_voltage_sel = mc34708_set_voltage_sel,
+
+ .get_mode = mc34708_swbst_get_mode,
+ .set_mode = mc34708_swbst_set_mode,
+
+ .set_suspend_enable = mc34708_swbst_set_suspend_enable,
+ .set_suspend_disable = mc34708_swbst_set_suspend_disable,
+ .set_suspend_mode = mc34708_swbst_set_suspend_mode,
+ },
+};
+
+static const struct mc34708_regulator_kind mc34708_ldo_kind = {
+ .setup = mc34708_ldo_setup,
+ .hw_modes = mc34708_ldo_modes,
+ .num_hw_modes = ARRAY_SIZE(mc34708_ldo_modes),
+ .of_map_mode = mc34708_ldo_of_map_mode,
+ .ops = {
+ .enable = mc34708_ldo_enable,
+ .disable = mc34708_ldo_disable,
+ .is_enabled = mc34708_ldo_is_enabled,
+
+ .list_voltage = regulator_list_voltage_table,
+ .get_voltage_sel = mc34708_get_voltage_sel,
+ .set_voltage_sel = mc34708_set_voltage_sel,
+
+ .get_mode = mc34708_ldo_get_mode,
+ .set_mode = mc34708_ldo_set_mode,
+
+ .set_suspend_enable = mc34708_ldo_set_suspend_enable,
+ .set_suspend_disable = mc34708_ldo_set_suspend_disable,
+ .set_suspend_mode = mc34708_ldo_set_suspend_mode,
+ },
+};
+
+static const struct mc34708_regulator_kind mc34708_ldo_fixed_kind = {
+ .ops = {
+ .enable = mc34708_ldo_enable,
+ .disable = mc34708_ldo_disable,
+ .is_enabled = mc34708_ldo_is_enabled,
+ },
+};
+
+static const struct mc34708_regulator_kind mc34708_ldo_vhalf_kind = {
+ .ops = {
+ .enable = mc34708_ldo_enable,
+ .disable = mc34708_ldo_disable,
+ .is_enabled = mc34708_ldo_is_enabled,
+ .get_voltage = mc34708_ldo_vhalf_get_voltage,
+ },
+};
+
+static const unsigned int mc34708_pll_volt_table[] = {
+ 1200000, 1250000, 1500000, 1800000
+};
+
+static const unsigned int mc34708_vusb2_volt_table[] = {
+ 2500000, 2600000, 2750000, 3000000
+};
+
+static const unsigned int mc34708_vdac_volt_table[] = {
+ 2500000, 2600000, 270000, 2775000
+};
+
+static const unsigned int mc34708_vgen1_volt_table[] = {
+ 1200000, 1250000, 1300000, 1350000, 1400000, 1450000, 1500000, 1550000
+};
+
+static const unsigned int mc34708_vgen2_volt_table[] = {
+ 2500000, 2700000, 2800000, 2900000, 3000000, 3100000, 3150000, 3300000
+};
+
+static struct mc34708_regulator_def mc34708_regulator_defs[] = {
+ /* Buck regulators */
+ {
+ .name = "sw1",
+ .kind = &mc34708_sw_kind,
+ .supply_name = "vinsw1",
+ .min_uV = 650000,
+ .uV_step = 12500,
+ .n_voltages = 64,
+ .vsel_reg = 0x18,
+ .vsel_mask = (0x3f << 0),
+ .vsel_stdby_mask = (0x3f << 6),
+ .mode_reg = 0x1c,
+ .mode_mask = (0xf << 0),
+ },
+ {
+ .name = "sw2",
+ .kind = &mc34708_sw_kind,
+ .supply_name = "vinsw2",
+ .min_uV = 650000,
+ .uV_step = 12500,
+ .n_voltages = 64,
+ .vsel_reg = 0x19,
+ .vsel_mask = (0x3f << 0),
+ .vsel_stdby_mask = (0x3f << 6),
+ .mode_reg = 0x1c,
+ .mode_mask = (0xf << 14),
+ },
+ {
+ .name = "sw3",
+ .kind = &mc34708_sw_kind,
+ .supply_name = "vinsw3",
+ .min_uV = 650000,
+ .uV_step = 25000,
+ .n_voltages = 32,
+ .vsel_reg = 0x19,
+ .vsel_mask = (0x1f << 12),
+ .vsel_stdby_mask = (0x1f << 18),
+ .mode_reg = 0x1d,
+ .mode_mask = (0xf << 0),
+ },
+ {
+ .name = "sw4a",
+ .kind = &mc34708_sw_kind,
+ .supply_name = "vinsw4a",
+ .min_uV = 1200000,
+ .uV_step = 25000,
+ .n_voltages = 27,
+ .vsel_reg = 0x1a,
+ .vsel_mask = (0x1f << 0),
+ .vsel_stdby_mask = (0x1f << 5),
+ .mode_reg = 0x1d,
+ .mode_mask = (0xf << 6),
+ },
+ {
+ .name = "sw4b",
+ .kind = &mc34708_sw_kind,
+ .supply_name = "vinsw4b",
+ .min_uV = 1200000,
+ .uV_step = 25000,
+ .n_voltages = 27,
+ .vsel_reg = 0x1a,
+ .vsel_mask = (0x1f << 12),
+ .vsel_stdby_mask = (0x1f << 17),
+ .mode_reg = 0x1d,
+ .mode_mask = (0xf << 12),
+ },
+ {
+ .name = "sw5",
+ .kind = &mc34708_sw_kind,
+ .supply_name = "vinsw5",
+ .min_uV = 1200000,
+ .uV_step = 25000,
+ .n_voltages = 27,
+ .vsel_reg = 0x1b,
+ .vsel_mask = (0x1f << 0),
+ .vsel_stdby_mask = (0x1f << 10),
+ .mode_reg = 0x1d,
+ .mode_mask = (0xf << 18),
+ },
+
+ /* SWBST regulator */
+ {
+ .name = "swbst",
+ .kind = &mc34708_swbst_kind,
+ .supply_name = "vinswbst",
+ .min_uV = 5000000,
+ .uV_step = 50000,
+ .n_voltages = 4,
+ .vsel_reg = 0x1f,
+ .vsel_mask = (0x3 << 0),
+ .mode_reg = 0x1f,
+ .mode_mask = (0x3 << 2),
+ .mode_stdby_mask = (0x3 << 5),
+ },
+
+ /* LDO regulators */
+ {
+ .name = "vpll",
+ .kind = &mc34708_ldo_kind,
+ .enable_reg = 0x20,
+ .enable_mask = (1 << 15),
+ .volt_table = mc34708_pll_volt_table,
+ .n_voltages = ARRAY_SIZE(mc34708_pll_volt_table),
+ .mode_reg = 0x20,
+ .mode_mask = (1 << 16),
+ },
+ {
+ .name = "vrefddr",
+ .kind = &mc34708_ldo_vhalf_kind,
+ .supply_name = "vinrefddr",
+ .enable_reg = 0x20,
+ .enable_mask = (1 << 10),
+ },
+ {
+ .name = "vusb",
+ .kind = &mc34708_ldo_fixed_kind,
+ .enable_reg = 0x20,
+ .enable_mask = (1 << 3),
+ .fixed_uV = 3300000,
+ .n_voltages = 1,
+ },
+ {
+ .name = "vusb2",
+ .kind = &mc34708_ldo_kind,
+ .enable_reg = 0x20,
+ .enable_mask = (1 << 18),
+ .volt_table = mc34708_vusb2_volt_table,
+ .n_voltages = ARRAY_SIZE(mc34708_vusb2_volt_table),
+ .mode_reg = 0x20,
+ .mode_mask = (3 << 19),
+ },
+ {
+ .name = "vdac",
+ .kind = &mc34708_ldo_kind,
+ .enable_reg = 0x20,
+ .enable_mask = (1 << 4),
+ .volt_table = mc34708_vdac_volt_table,
+ .n_voltages = ARRAY_SIZE(mc34708_vdac_volt_table),
+ .mode_reg = 0x20,
+ .mode_mask = (3 << 5),
+ },
+ {
+ .name = "vgen1",
+ .kind = &mc34708_ldo_kind,
+ .enable_reg = 0x20,
+ .enable_mask = (1 << 0),
+ .volt_table = mc34708_vgen1_volt_table,
+ .n_voltages = ARRAY_SIZE(mc34708_vgen1_volt_table),
+ .mode_reg = 0x20,
+ .mode_mask = (1 << 1),
+ },
+ {
+ .name = "vgen2",
+ .kind = &mc34708_ldo_kind,
+ .enable_reg = 0x20,
+ .enable_mask = (1 << 12),
+ .volt_table = mc34708_vgen2_volt_table,
+ .n_voltages = ARRAY_SIZE(mc34708_vgen2_volt_table),
+ .mode_reg = 0x20,
+ .mode_mask = (3 << 13),
+ },
+};
+
+/*
+ * Setting some LDO standby states also requires changing the normal state.
+ * Therefore save the LDO configuration register on suspend and restore it
+ * on resume.
+ *
+ * This works because .set_suspend_X are called by the platform suspend handler
+ * AFTER device suspend
+ */
+#define MC34708_REG_REGMODE0 0x20
+
+static int mc34708_suspend(struct device *dev)
+{
+ struct mc34708_drv_data *mc34708_data = dev_get_drvdata(dev);
+
+ return mc13xxx_reg_read(mc34708_data->regulators[0].mc13xxx,
+ MC34708_REG_REGMODE0,
+ &mc34708_data->saved_regmode0);
+}
+
+static int mc34708_resume(struct device *dev)
+{
+ struct mc34708_drv_data *mc34708_data = dev_get_drvdata(dev);
+
+ return mc13xxx_reg_write(mc34708_data->regulators[0].mc13xxx,
+ MC34708_REG_REGMODE0,
+ mc34708_data->saved_regmode0);
+}
+
+static int mc34708_regulator_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ int num_regs;
+ struct mc34708_drv_data *mc34708_data;
+ struct mc34708_regulator *mc34708_reg;
+ struct device_node *regs_np, *reg_np;
+ struct regulator_dev *rdev;
+ int ret;
+ int i;
+
+ regs_np = of_get_child_by_name(dev->parent->of_node, "regulators");
+ if (!regs_np)
+ return -ENODEV;
+
+ dev->of_node = regs_np;
+
+ num_regs = of_get_child_count(regs_np);
+ mc34708_data = devm_kzalloc(dev, sizeof(*mc34708_data) +
+ num_regs * sizeof(*mc34708_reg),
+ GFP_KERNEL);
+ if (!mc34708_data) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ dev_set_drvdata(dev, mc34708_data);
+
+ mc34708_reg = mc34708_data->regulators;
+ for_each_child_of_node(regs_np, reg_np) {
+ const struct mc34708_regulator_def *rd;
+ bool found;
+
+ found = false;
+ for (i = 0; i < ARRAY_SIZE(mc34708_regulator_defs); i++) {
+ rd = &mc34708_regulator_defs[i];
+
+ if (!of_node_cmp(reg_np->name, rd->name)) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ dev_warn(dev, "Unknown regulator '%s'\n", reg_np->name);
+ continue;
+ }
+
+ mc34708_reg->mc13xxx = dev_get_drvdata(dev->parent);
+ mc34708_reg->dev = dev;
+ mc34708_reg->def = rd;
+ mc34708_reg->desc.name = rd->name;
+ mc34708_reg->desc.supply_name = rd->supply_name;
+ mc34708_reg->desc.enable_reg = rd->enable_reg;
+ mc34708_reg->desc.enable_mask = rd->enable_mask;
+ mc34708_reg->desc.n_voltages = rd->n_voltages;
+ mc34708_reg->desc.fixed_uV = rd->fixed_uV;
+ mc34708_reg->desc.min_uV = rd->min_uV;
+ mc34708_reg->desc.uV_step = rd->uV_step;
+ mc34708_reg->desc.volt_table = rd->volt_table;
+ mc34708_reg->desc.vsel_reg = rd->vsel_reg;
+ mc34708_reg->desc.vsel_mask = rd->vsel_mask;
+ mc34708_reg->desc.of_map_mode = rd->kind->of_map_mode;
+ mc34708_reg->desc.ops = &rd->kind->ops;
+
+ mc34708_reg->config.init_data = of_get_regulator_init_data(dev,
+ reg_np,
+ &mc34708_reg->desc);
+ mc34708_reg->config.dev = dev;
+ mc34708_reg->config.of_node = reg_np;
+ mc34708_reg->config.driver_data = mc34708_reg;
+
+ if (rd->kind->setup) {
+ ret = rd->kind->setup(mc34708_reg);
+ if (ret)
+ goto out;
+ }
+
+ rdev = devm_regulator_register(dev,
+ &mc34708_reg->desc,
+ &mc34708_reg->config);
+ if (IS_ERR(rdev)) {
+ ret = PTR_ERR(rdev);
+ dev_err(dev,
+ "Failed to register regulator %s (%d)\n",
+ rd->name, ret);
+ goto out;
+ }
+
+ mc34708_reg++;
+ }
+
+ ret = 0;
+
+out:
+ of_node_put(regs_np);
+
+ return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(mc34708_pm, mc34708_suspend, mc34708_resume);
+
+static struct platform_driver mc34708_regulator_driver = {
+ .driver = {
+ .name = "mc34708-regulator",
+ .pm = &mc34708_pm,
+ },
+ .probe = mc34708_regulator_probe,
+};
+
+static int __init mc34708_regulator_init(void)
+{
+ return platform_driver_register(&mc34708_regulator_driver);
+}
+subsys_initcall(mc34708_regulator_init);
+
+static void __exit mc34708_regulator_exit(void)
+{
+ platform_driver_unregister(&mc34708_regulator_driver);
+}
+module_exit(mc34708_regulator_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Martin Fuzzey <mfuzzey at parkeon.com>");
+MODULE_DESCRIPTION("Regulator Driver for Freescale MC34708 PMIC");
+MODULE_ALIAS("platform:mc34708-regulator");
More information about the linux-arm-kernel
mailing list