alistair23-linux/drivers/regulator/bd9571mwv-regulator.c
Geert Uytterhoeven 6eb0bfae69
regulator: bd9571mwv: Add support for backup mode
The BD9571MWV PMIC supports backup mode, which keeps one or more DDR
rails powered while the main SoC is powered down.

Which DDR rails are to be kept powered is board-specific, and controlled
using the optional "rohm,ddr-backup-power" DT property.  In the absence
of this property, backup mode is not available.

Backup mode can be enabled or disabled by the user using the standard
"wakeup" virtual file in sysfs, e.g. to enable:

    echo enabled > /sys/devices/platform/soc/e60b0000.i2c/i2c-7/7-0030/bd9571mwv-regulator.2.auto/power/wakeup

When the PMIC is configured for backup mode, the role of the accessory
power switch changes from a power switch to a wake-up switch.
Two types of switches (or signals) can be used:
  A. With a momentary power switch (or pulse signal), the PMIC is
     configured for backup mode in the PMIC driver's suspend callback,
     during system suspend.
     Backup mode is enabled by default, as there is no further impact
     during normal system operation.

  B. With a toggle power switch (or level signal), the following steps
     must be followed exactly:
       1. Configure PMIC for backup mode,
       2. Switch accessory power switch off, to prepare for system
          suspend, which is a manual step not controlled by software,
       3. Suspend system.
     This mode is not yet supported by the driver.

As the switch type is board-specific, and cannot be determined
automatically, it is obtained from the presence of one of the
"rohm,rstbmode-*" properties in DT.

Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
Acked-by: Lee Jones <lee.jones@linaro.org>
Signed-off-by: Mark Brown <broonie@kernel.org>
2018-04-23 17:53:50 +01:00

304 lines
7.7 KiB
C

/*
* ROHM BD9571MWV-M regulator driver
*
* Copyright (C) 2017 Marek Vasut <marek.vasut+renesas@gmail.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.
*
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
* kind, whether expressed or implied; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License version 2 for more details.
*
* Based on the TPS65086 driver
*
* NOTE: VD09 is missing
*/
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regulator/driver.h>
#include <linux/mfd/bd9571mwv.h>
struct bd9571mwv_reg {
struct bd9571mwv *bd;
/* DDR Backup Power */
u8 bkup_mode_cnt_keepon; /* from "rohm,ddr-backup-power" */
u8 bkup_mode_cnt_saved;
/* Power switch type */
bool rstbmode_level;
bool rstbmode_pulse;
};
enum bd9571mwv_regulators { VD09, VD18, VD25, VD33, DVFS };
#define BD9571MWV_REG(_name, _of, _id, _ops, _vr, _vm, _nv, _min, _step, _lmin)\
{ \
.name = _name, \
.of_match = of_match_ptr(_of), \
.regulators_node = "regulators", \
.id = _id, \
.ops = &_ops, \
.n_voltages = _nv, \
.type = REGULATOR_VOLTAGE, \
.owner = THIS_MODULE, \
.vsel_reg = _vr, \
.vsel_mask = _vm, \
.min_uV = _min, \
.uV_step = _step, \
.linear_min_sel = _lmin, \
}
static int bd9571mwv_avs_get_moni_state(struct regulator_dev *rdev)
{
unsigned int val;
int ret;
ret = regmap_read(rdev->regmap, BD9571MWV_AVS_SET_MONI, &val);
if (ret != 0)
return ret;
return val & BD9571MWV_AVS_SET_MONI_MASK;
}
static int bd9571mwv_avs_set_voltage_sel_regmap(struct regulator_dev *rdev,
unsigned int sel)
{
int ret;
ret = bd9571mwv_avs_get_moni_state(rdev);
if (ret < 0)
return ret;
return regmap_write_bits(rdev->regmap, BD9571MWV_AVS_VD09_VID(ret),
rdev->desc->vsel_mask, sel);
}
static int bd9571mwv_avs_get_voltage_sel_regmap(struct regulator_dev *rdev)
{
unsigned int val;
int ret;
ret = bd9571mwv_avs_get_moni_state(rdev);
if (ret < 0)
return ret;
ret = regmap_read(rdev->regmap, BD9571MWV_AVS_VD09_VID(ret), &val);
if (ret != 0)
return ret;
val &= rdev->desc->vsel_mask;
val >>= ffs(rdev->desc->vsel_mask) - 1;
return val;
}
static int bd9571mwv_reg_set_voltage_sel_regmap(struct regulator_dev *rdev,
unsigned int sel)
{
return regmap_write_bits(rdev->regmap, BD9571MWV_DVFS_SETVID,
rdev->desc->vsel_mask, sel);
}
/* Operations permitted on AVS voltage regulator */
static struct regulator_ops avs_ops = {
.set_voltage_sel = bd9571mwv_avs_set_voltage_sel_regmap,
.map_voltage = regulator_map_voltage_linear,
.get_voltage_sel = bd9571mwv_avs_get_voltage_sel_regmap,
.list_voltage = regulator_list_voltage_linear,
};
/* Operations permitted on voltage regulators */
static struct regulator_ops reg_ops = {
.set_voltage_sel = bd9571mwv_reg_set_voltage_sel_regmap,
.map_voltage = regulator_map_voltage_linear,
.get_voltage_sel = regulator_get_voltage_sel_regmap,
.list_voltage = regulator_list_voltage_linear,
};
/* Operations permitted on voltage monitors */
static struct regulator_ops vid_ops = {
.map_voltage = regulator_map_voltage_linear,
.get_voltage_sel = regulator_get_voltage_sel_regmap,
.list_voltage = regulator_list_voltage_linear,
};
static struct regulator_desc regulators[] = {
BD9571MWV_REG("VD09", "vd09", VD09, avs_ops, 0, 0x7f,
0x80, 600000, 10000, 0x3c),
BD9571MWV_REG("VD18", "vd18", VD18, vid_ops, BD9571MWV_VD18_VID, 0xf,
16, 1625000, 25000, 0),
BD9571MWV_REG("VD25", "vd25", VD25, vid_ops, BD9571MWV_VD25_VID, 0xf,
16, 2150000, 50000, 0),
BD9571MWV_REG("VD33", "vd33", VD33, vid_ops, BD9571MWV_VD33_VID, 0xf,
11, 2800000, 100000, 0),
BD9571MWV_REG("DVFS", "dvfs", DVFS, reg_ops,
BD9571MWV_DVFS_MONIVDAC, 0x7f,
0x80, 600000, 10000, 0x3c),
};
#ifdef CONFIG_PM_SLEEP
static int bd9571mwv_bkup_mode_read(struct bd9571mwv *bd, unsigned int *mode)
{
int ret;
ret = regmap_read(bd->regmap, BD9571MWV_BKUP_MODE_CNT, mode);
if (ret) {
dev_err(bd->dev, "failed to read backup mode (%d)\n", ret);
return ret;
}
return 0;
}
static int bd9571mwv_bkup_mode_write(struct bd9571mwv *bd, unsigned int mode)
{
int ret;
ret = regmap_write(bd->regmap, BD9571MWV_BKUP_MODE_CNT, mode);
if (ret) {
dev_err(bd->dev, "failed to configure backup mode 0x%x (%d)\n",
mode, ret);
return ret;
}
return 0;
}
static int bd9571mwv_suspend(struct device *dev)
{
struct bd9571mwv_reg *bdreg = dev_get_drvdata(dev);
unsigned int mode;
int ret;
if (!device_may_wakeup(dev))
return 0;
/* Save DDR Backup Mode */
ret = bd9571mwv_bkup_mode_read(bdreg->bd, &mode);
if (ret)
return ret;
bdreg->bkup_mode_cnt_saved = mode;
if (!bdreg->rstbmode_pulse)
return 0;
/* Enable DDR Backup Mode */
mode &= ~BD9571MWV_BKUP_MODE_CNT_KEEPON_MASK;
mode |= bdreg->bkup_mode_cnt_keepon;
if (mode != bdreg->bkup_mode_cnt_saved)
return bd9571mwv_bkup_mode_write(bdreg->bd, mode);
return 0;
}
static int bd9571mwv_resume(struct device *dev)
{
struct bd9571mwv_reg *bdreg = dev_get_drvdata(dev);
if (!device_may_wakeup(dev))
return 0;
/* Restore DDR Backup Mode */
return bd9571mwv_bkup_mode_write(bdreg->bd, bdreg->bkup_mode_cnt_saved);
}
static const struct dev_pm_ops bd9571mwv_pm = {
SET_SYSTEM_SLEEP_PM_OPS(bd9571mwv_suspend, bd9571mwv_resume)
};
#define DEV_PM_OPS &bd9571mwv_pm
#else
#define DEV_PM_OPS NULL
#endif /* CONFIG_PM_SLEEP */
static int bd9571mwv_regulator_probe(struct platform_device *pdev)
{
struct bd9571mwv *bd = dev_get_drvdata(pdev->dev.parent);
struct regulator_config config = { };
struct bd9571mwv_reg *bdreg;
struct regulator_dev *rdev;
unsigned int val;
int i;
bdreg = devm_kzalloc(&pdev->dev, sizeof(*bdreg), GFP_KERNEL);
if (!bdreg)
return -ENOMEM;
bdreg->bd = bd;
platform_set_drvdata(pdev, bdreg);
config.dev = &pdev->dev;
config.dev->of_node = bd->dev->of_node;
config.driver_data = bd;
config.regmap = bd->regmap;
for (i = 0; i < ARRAY_SIZE(regulators); i++) {
rdev = devm_regulator_register(&pdev->dev, &regulators[i],
&config);
if (IS_ERR(rdev)) {
dev_err(bd->dev, "failed to register %s regulator\n",
pdev->name);
return PTR_ERR(rdev);
}
}
val = 0;
of_property_read_u32(bd->dev->of_node, "rohm,ddr-backup-power", &val);
if (val & ~BD9571MWV_BKUP_MODE_CNT_KEEPON_MASK) {
dev_err(bd->dev, "invalid %s mode %u\n",
"rohm,ddr-backup-power", val);
return -EINVAL;
}
bdreg->bkup_mode_cnt_keepon = val;
bdreg->rstbmode_level = of_property_read_bool(bd->dev->of_node,
"rohm,rstbmode-level");
bdreg->rstbmode_pulse = of_property_read_bool(bd->dev->of_node,
"rohm,rstbmode-pulse");
if (bdreg->rstbmode_level && bdreg->rstbmode_pulse) {
dev_err(bd->dev, "only one rohm,rstbmode-* may be specified");
return -EINVAL;
}
if (bdreg->bkup_mode_cnt_keepon) {
device_set_wakeup_capable(&pdev->dev, true);
/*
* Wakeup is enabled by default in pulse mode, but needs
* explicit user setup in level mode.
*/
device_set_wakeup_enable(&pdev->dev, bdreg->rstbmode_pulse);
}
return 0;
}
static const struct platform_device_id bd9571mwv_regulator_id_table[] = {
{ "bd9571mwv-regulator", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(platform, bd9571mwv_regulator_id_table);
static struct platform_driver bd9571mwv_regulator_driver = {
.driver = {
.name = "bd9571mwv-regulator",
.pm = DEV_PM_OPS,
},
.probe = bd9571mwv_regulator_probe,
.id_table = bd9571mwv_regulator_id_table,
};
module_platform_driver(bd9571mwv_regulator_driver);
MODULE_AUTHOR("Marek Vasut <marek.vasut+renesas@gmail.com>");
MODULE_DESCRIPTION("BD9571MWV Regulator driver");
MODULE_LICENSE("GPL v2");