linux/drivers/regulator/palmas-regulator.c
Laxman Dewangan 32b6d3f602 regulator: palmas: add support for external control of rails
Palmas rails like LDOs, SMPSs, REGENs, SYSENs can be enable and disable
by register programming through I2C communication as well as it can be
enable/disable with the external control input ENABLE1, ENABLE2 and NSLEEP.

Add support for configuring these rails to be controlled by external control
inputs. This is require to configure the rail's control register as well as
configuration of resource register.

Provide the external input names through parameter "roof-floor". Updated the
DT binding document to details different value of the roof-floor.

Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
Acked-by: Stephen Warren <swarren@nvidia.com>
Signed-off-by: Mark Brown <broonie@linaro.org>
2013-09-17 00:31:03 +01:00

1258 lines
32 KiB
C

/*
* Driver for Regulator part of Palmas PMIC Chips
*
* Copyright 2011-2013 Texas Instruments Inc.
*
* Author: Graeme Gregory <gg@slimlogic.co.uk>
* Author: Ian Lartey <ian@slimlogic.co.uk>
*
* 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/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/slab.h>
#include <linux/regmap.h>
#include <linux/mfd/palmas.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/regulator/of_regulator.h>
struct regs_info {
char *name;
char *sname;
u8 vsel_addr;
u8 ctrl_addr;
u8 tstep_addr;
int sleep_id;
};
static const struct regs_info palmas_regs_info[] = {
{
.name = "SMPS12",
.sname = "smps1-in",
.vsel_addr = PALMAS_SMPS12_VOLTAGE,
.ctrl_addr = PALMAS_SMPS12_CTRL,
.tstep_addr = PALMAS_SMPS12_TSTEP,
.sleep_id = PALMAS_EXTERNAL_REQSTR_ID_SMPS12,
},
{
.name = "SMPS123",
.sname = "smps1-in",
.vsel_addr = PALMAS_SMPS12_VOLTAGE,
.ctrl_addr = PALMAS_SMPS12_CTRL,
.tstep_addr = PALMAS_SMPS12_TSTEP,
.sleep_id = PALMAS_EXTERNAL_REQSTR_ID_SMPS12,
},
{
.name = "SMPS3",
.sname = "smps3-in",
.vsel_addr = PALMAS_SMPS3_VOLTAGE,
.ctrl_addr = PALMAS_SMPS3_CTRL,
.sleep_id = PALMAS_EXTERNAL_REQSTR_ID_SMPS3,
},
{
.name = "SMPS45",
.sname = "smps4-in",
.vsel_addr = PALMAS_SMPS45_VOLTAGE,
.ctrl_addr = PALMAS_SMPS45_CTRL,
.tstep_addr = PALMAS_SMPS45_TSTEP,
.sleep_id = PALMAS_EXTERNAL_REQSTR_ID_SMPS45,
},
{
.name = "SMPS457",
.sname = "smps4-in",
.vsel_addr = PALMAS_SMPS45_VOLTAGE,
.ctrl_addr = PALMAS_SMPS45_CTRL,
.tstep_addr = PALMAS_SMPS45_TSTEP,
.sleep_id = PALMAS_EXTERNAL_REQSTR_ID_SMPS45,
},
{
.name = "SMPS6",
.sname = "smps6-in",
.vsel_addr = PALMAS_SMPS6_VOLTAGE,
.ctrl_addr = PALMAS_SMPS6_CTRL,
.tstep_addr = PALMAS_SMPS6_TSTEP,
.sleep_id = PALMAS_EXTERNAL_REQSTR_ID_SMPS6,
},
{
.name = "SMPS7",
.sname = "smps7-in",
.vsel_addr = PALMAS_SMPS7_VOLTAGE,
.ctrl_addr = PALMAS_SMPS7_CTRL,
.sleep_id = PALMAS_EXTERNAL_REQSTR_ID_SMPS7,
},
{
.name = "SMPS8",
.sname = "smps8-in",
.vsel_addr = PALMAS_SMPS8_VOLTAGE,
.ctrl_addr = PALMAS_SMPS8_CTRL,
.tstep_addr = PALMAS_SMPS8_TSTEP,
.sleep_id = PALMAS_EXTERNAL_REQSTR_ID_SMPS8,
},
{
.name = "SMPS9",
.sname = "smps9-in",
.vsel_addr = PALMAS_SMPS9_VOLTAGE,
.ctrl_addr = PALMAS_SMPS9_CTRL,
.sleep_id = PALMAS_EXTERNAL_REQSTR_ID_SMPS9,
},
{
.name = "SMPS10_OUT2",
.sname = "smps10-in",
.ctrl_addr = PALMAS_SMPS10_CTRL,
.sleep_id = PALMAS_EXTERNAL_REQSTR_ID_SMPS10,
},
{
.name = "SMPS10_OUT1",
.sname = "smps10-out2",
.ctrl_addr = PALMAS_SMPS10_CTRL,
.sleep_id = PALMAS_EXTERNAL_REQSTR_ID_SMPS10,
},
{
.name = "LDO1",
.sname = "ldo1-in",
.vsel_addr = PALMAS_LDO1_VOLTAGE,
.ctrl_addr = PALMAS_LDO1_CTRL,
.sleep_id = PALMAS_EXTERNAL_REQSTR_ID_LDO1,
},
{
.name = "LDO2",
.sname = "ldo2-in",
.vsel_addr = PALMAS_LDO2_VOLTAGE,
.ctrl_addr = PALMAS_LDO2_CTRL,
.sleep_id = PALMAS_EXTERNAL_REQSTR_ID_LDO2,
},
{
.name = "LDO3",
.sname = "ldo3-in",
.vsel_addr = PALMAS_LDO3_VOLTAGE,
.ctrl_addr = PALMAS_LDO3_CTRL,
.sleep_id = PALMAS_EXTERNAL_REQSTR_ID_LDO3,
},
{
.name = "LDO4",
.sname = "ldo4-in",
.vsel_addr = PALMAS_LDO4_VOLTAGE,
.ctrl_addr = PALMAS_LDO4_CTRL,
.sleep_id = PALMAS_EXTERNAL_REQSTR_ID_LDO4,
},
{
.name = "LDO5",
.sname = "ldo5-in",
.vsel_addr = PALMAS_LDO5_VOLTAGE,
.ctrl_addr = PALMAS_LDO5_CTRL,
.sleep_id = PALMAS_EXTERNAL_REQSTR_ID_LDO5,
},
{
.name = "LDO6",
.sname = "ldo6-in",
.vsel_addr = PALMAS_LDO6_VOLTAGE,
.ctrl_addr = PALMAS_LDO6_CTRL,
.sleep_id = PALMAS_EXTERNAL_REQSTR_ID_LDO6,
},
{
.name = "LDO7",
.sname = "ldo7-in",
.vsel_addr = PALMAS_LDO7_VOLTAGE,
.ctrl_addr = PALMAS_LDO7_CTRL,
.sleep_id = PALMAS_EXTERNAL_REQSTR_ID_LDO7,
},
{
.name = "LDO8",
.sname = "ldo8-in",
.vsel_addr = PALMAS_LDO8_VOLTAGE,
.ctrl_addr = PALMAS_LDO8_CTRL,
.sleep_id = PALMAS_EXTERNAL_REQSTR_ID_LDO8,
},
{
.name = "LDO9",
.sname = "ldo9-in",
.vsel_addr = PALMAS_LDO9_VOLTAGE,
.ctrl_addr = PALMAS_LDO9_CTRL,
.sleep_id = PALMAS_EXTERNAL_REQSTR_ID_LDO9,
},
{
.name = "LDOLN",
.sname = "ldoln-in",
.vsel_addr = PALMAS_LDOLN_VOLTAGE,
.ctrl_addr = PALMAS_LDOLN_CTRL,
.sleep_id = PALMAS_EXTERNAL_REQSTR_ID_LDOLN,
},
{
.name = "LDOUSB",
.sname = "ldousb-in",
.vsel_addr = PALMAS_LDOUSB_VOLTAGE,
.ctrl_addr = PALMAS_LDOUSB_CTRL,
.sleep_id = PALMAS_EXTERNAL_REQSTR_ID_LDOUSB,
},
{
.name = "REGEN1",
.ctrl_addr = PALMAS_REGEN1_CTRL,
.sleep_id = PALMAS_EXTERNAL_REQSTR_ID_REGEN1,
},
{
.name = "REGEN2",
.ctrl_addr = PALMAS_REGEN2_CTRL,
.sleep_id = PALMAS_EXTERNAL_REQSTR_ID_REGEN2,
},
{
.name = "REGEN3",
.ctrl_addr = PALMAS_REGEN3_CTRL,
.sleep_id = PALMAS_EXTERNAL_REQSTR_ID_REGEN3,
},
{
.name = "SYSEN1",
.ctrl_addr = PALMAS_SYSEN1_CTRL,
.sleep_id = PALMAS_EXTERNAL_REQSTR_ID_SYSEN1,
},
{
.name = "SYSEN2",
.ctrl_addr = PALMAS_SYSEN2_CTRL,
.sleep_id = PALMAS_EXTERNAL_REQSTR_ID_SYSEN2,
},
};
static unsigned int palmas_smps_ramp_delay[4] = {0, 10000, 5000, 2500};
#define SMPS_CTRL_MODE_OFF 0x00
#define SMPS_CTRL_MODE_ON 0x01
#define SMPS_CTRL_MODE_ECO 0x02
#define SMPS_CTRL_MODE_PWM 0x03
/* These values are derived from the data sheet. And are the number of steps
* where there is a voltage change, the ranges at beginning and end of register
* max/min values where there are no change are ommitted.
*
* So they are basically (maxV-minV)/stepV
*/
#define PALMAS_SMPS_NUM_VOLTAGES 117
#define PALMAS_SMPS10_NUM_VOLTAGES 2
#define PALMAS_LDO_NUM_VOLTAGES 50
#define SMPS10_VSEL (1<<3)
#define SMPS10_BOOST_EN (1<<2)
#define SMPS10_BYPASS_EN (1<<1)
#define SMPS10_SWITCH_EN (1<<0)
#define REGULATOR_SLAVE 0
static int palmas_smps_read(struct palmas *palmas, unsigned int reg,
unsigned int *dest)
{
unsigned int addr;
addr = PALMAS_BASE_TO_REG(PALMAS_SMPS_BASE, reg);
return regmap_read(palmas->regmap[REGULATOR_SLAVE], addr, dest);
}
static int palmas_smps_write(struct palmas *palmas, unsigned int reg,
unsigned int value)
{
unsigned int addr;
addr = PALMAS_BASE_TO_REG(PALMAS_SMPS_BASE, reg);
return regmap_write(palmas->regmap[REGULATOR_SLAVE], addr, value);
}
static int palmas_ldo_read(struct palmas *palmas, unsigned int reg,
unsigned int *dest)
{
unsigned int addr;
addr = PALMAS_BASE_TO_REG(PALMAS_LDO_BASE, reg);
return regmap_read(palmas->regmap[REGULATOR_SLAVE], addr, dest);
}
static int palmas_ldo_write(struct palmas *palmas, unsigned int reg,
unsigned int value)
{
unsigned int addr;
addr = PALMAS_BASE_TO_REG(PALMAS_LDO_BASE, reg);
return regmap_write(palmas->regmap[REGULATOR_SLAVE], addr, value);
}
static int palmas_is_enabled_smps(struct regulator_dev *dev)
{
struct palmas_pmic *pmic = rdev_get_drvdata(dev);
int id = rdev_get_id(dev);
unsigned int reg;
palmas_smps_read(pmic->palmas, palmas_regs_info[id].ctrl_addr, &reg);
reg &= PALMAS_SMPS12_CTRL_STATUS_MASK;
reg >>= PALMAS_SMPS12_CTRL_STATUS_SHIFT;
return !!(reg);
}
static int palmas_enable_smps(struct regulator_dev *dev)
{
struct palmas_pmic *pmic = rdev_get_drvdata(dev);
int id = rdev_get_id(dev);
unsigned int reg;
palmas_smps_read(pmic->palmas, palmas_regs_info[id].ctrl_addr, &reg);
reg &= ~PALMAS_SMPS12_CTRL_MODE_ACTIVE_MASK;
if (pmic->current_reg_mode[id])
reg |= pmic->current_reg_mode[id];
else
reg |= SMPS_CTRL_MODE_ON;
palmas_smps_write(pmic->palmas, palmas_regs_info[id].ctrl_addr, reg);
return 0;
}
static int palmas_disable_smps(struct regulator_dev *dev)
{
struct palmas_pmic *pmic = rdev_get_drvdata(dev);
int id = rdev_get_id(dev);
unsigned int reg;
palmas_smps_read(pmic->palmas, palmas_regs_info[id].ctrl_addr, &reg);
reg &= ~PALMAS_SMPS12_CTRL_MODE_ACTIVE_MASK;
palmas_smps_write(pmic->palmas, palmas_regs_info[id].ctrl_addr, reg);
return 0;
}
static int palmas_set_mode_smps(struct regulator_dev *dev, unsigned int mode)
{
struct palmas_pmic *pmic = rdev_get_drvdata(dev);
int id = rdev_get_id(dev);
unsigned int reg;
bool rail_enable = true;
palmas_smps_read(pmic->palmas, palmas_regs_info[id].ctrl_addr, &reg);
reg &= ~PALMAS_SMPS12_CTRL_MODE_ACTIVE_MASK;
if (reg == SMPS_CTRL_MODE_OFF)
rail_enable = false;
switch (mode) {
case REGULATOR_MODE_NORMAL:
reg |= SMPS_CTRL_MODE_ON;
break;
case REGULATOR_MODE_IDLE:
reg |= SMPS_CTRL_MODE_ECO;
break;
case REGULATOR_MODE_FAST:
reg |= SMPS_CTRL_MODE_PWM;
break;
default:
return -EINVAL;
}
pmic->current_reg_mode[id] = reg & PALMAS_SMPS12_CTRL_MODE_ACTIVE_MASK;
if (rail_enable)
palmas_smps_write(pmic->palmas,
palmas_regs_info[id].ctrl_addr, reg);
return 0;
}
static unsigned int palmas_get_mode_smps(struct regulator_dev *dev)
{
struct palmas_pmic *pmic = rdev_get_drvdata(dev);
int id = rdev_get_id(dev);
unsigned int reg;
reg = pmic->current_reg_mode[id] & PALMAS_SMPS12_CTRL_MODE_ACTIVE_MASK;
switch (reg) {
case SMPS_CTRL_MODE_ON:
return REGULATOR_MODE_NORMAL;
case SMPS_CTRL_MODE_ECO:
return REGULATOR_MODE_IDLE;
case SMPS_CTRL_MODE_PWM:
return REGULATOR_MODE_FAST;
}
return 0;
}
static int palmas_list_voltage_smps(struct regulator_dev *dev,
unsigned selector)
{
struct palmas_pmic *pmic = rdev_get_drvdata(dev);
int id = rdev_get_id(dev);
int mult = 1;
/* Read the multiplier set in VSEL register to return
* the correct voltage.
*/
if (pmic->range[id])
mult = 2;
if (selector == 0)
return 0;
else if (selector < 6)
return 500000 * mult;
else
/* Voltage is linear mapping starting from selector 6,
* volt = (0.49V + ((selector - 5) * 0.01V)) * RANGE
* RANGE is either x1 or x2
*/
return (490000 + ((selector - 5) * 10000)) * mult;
}
static int palmas_map_voltage_smps(struct regulator_dev *rdev,
int min_uV, int max_uV)
{
struct palmas_pmic *pmic = rdev_get_drvdata(rdev);
int id = rdev_get_id(rdev);
int ret, voltage;
if (min_uV == 0)
return 0;
if (pmic->range[id]) { /* RANGE is x2 */
if (min_uV < 1000000)
min_uV = 1000000;
ret = DIV_ROUND_UP(min_uV - 1000000, 20000) + 6;
} else { /* RANGE is x1 */
if (min_uV < 500000)
min_uV = 500000;
ret = DIV_ROUND_UP(min_uV - 500000, 10000) + 6;
}
/* Map back into a voltage to verify we're still in bounds */
voltage = palmas_list_voltage_smps(rdev, ret);
if (voltage < min_uV || voltage > max_uV)
return -EINVAL;
return ret;
}
static int palma_smps_set_voltage_smps_time_sel(struct regulator_dev *rdev,
unsigned int old_selector, unsigned int new_selector)
{
struct palmas_pmic *pmic = rdev_get_drvdata(rdev);
int id = rdev_get_id(rdev);
int old_uv, new_uv;
unsigned int ramp_delay = pmic->ramp_delay[id];
if (!ramp_delay)
return 0;
old_uv = palmas_list_voltage_smps(rdev, old_selector);
if (old_uv < 0)
return old_uv;
new_uv = palmas_list_voltage_smps(rdev, new_selector);
if (new_uv < 0)
return new_uv;
return DIV_ROUND_UP(abs(old_uv - new_uv), ramp_delay);
}
static int palmas_smps_set_ramp_delay(struct regulator_dev *rdev,
int ramp_delay)
{
struct palmas_pmic *pmic = rdev_get_drvdata(rdev);
int id = rdev_get_id(rdev);
unsigned int reg = 0;
unsigned int addr = palmas_regs_info[id].tstep_addr;
int ret;
/* SMPS3 and SMPS7 do not have tstep_addr setting */
switch (id) {
case PALMAS_REG_SMPS3:
case PALMAS_REG_SMPS7:
return 0;
}
if (ramp_delay <= 0)
reg = 0;
else if (ramp_delay <= 2500)
reg = 3;
else if (ramp_delay <= 5000)
reg = 2;
else
reg = 1;
ret = palmas_smps_write(pmic->palmas, addr, reg);
if (ret < 0) {
dev_err(pmic->palmas->dev, "TSTEP write failed: %d\n", ret);
return ret;
}
pmic->ramp_delay[id] = palmas_smps_ramp_delay[reg];
return ret;
}
static struct regulator_ops palmas_ops_smps = {
.is_enabled = palmas_is_enabled_smps,
.enable = palmas_enable_smps,
.disable = palmas_disable_smps,
.set_mode = palmas_set_mode_smps,
.get_mode = palmas_get_mode_smps,
.get_voltage_sel = regulator_get_voltage_sel_regmap,
.set_voltage_sel = regulator_set_voltage_sel_regmap,
.list_voltage = palmas_list_voltage_smps,
.map_voltage = palmas_map_voltage_smps,
.set_voltage_time_sel = palma_smps_set_voltage_smps_time_sel,
.set_ramp_delay = palmas_smps_set_ramp_delay,
};
static struct regulator_ops palmas_ops_ext_control_smps = {
.set_mode = palmas_set_mode_smps,
.get_mode = palmas_get_mode_smps,
.get_voltage_sel = regulator_get_voltage_sel_regmap,
.set_voltage_sel = regulator_set_voltage_sel_regmap,
.list_voltage = palmas_list_voltage_smps,
.map_voltage = palmas_map_voltage_smps,
.set_voltage_time_sel = palma_smps_set_voltage_smps_time_sel,
.set_ramp_delay = palmas_smps_set_ramp_delay,
};
static struct regulator_ops palmas_ops_smps10 = {
.is_enabled = regulator_is_enabled_regmap,
.enable = regulator_enable_regmap,
.disable = regulator_disable_regmap,
.get_voltage_sel = regulator_get_voltage_sel_regmap,
.set_voltage_sel = regulator_set_voltage_sel_regmap,
.list_voltage = regulator_list_voltage_linear,
.map_voltage = regulator_map_voltage_linear,
.set_bypass = regulator_set_bypass_regmap,
.get_bypass = regulator_get_bypass_regmap,
};
static int palmas_is_enabled_ldo(struct regulator_dev *dev)
{
struct palmas_pmic *pmic = rdev_get_drvdata(dev);
int id = rdev_get_id(dev);
unsigned int reg;
palmas_ldo_read(pmic->palmas, palmas_regs_info[id].ctrl_addr, &reg);
reg &= PALMAS_LDO1_CTRL_STATUS;
return !!(reg);
}
static struct regulator_ops palmas_ops_ldo = {
.is_enabled = palmas_is_enabled_ldo,
.enable = regulator_enable_regmap,
.disable = regulator_disable_regmap,
.get_voltage_sel = regulator_get_voltage_sel_regmap,
.set_voltage_sel = regulator_set_voltage_sel_regmap,
.list_voltage = regulator_list_voltage_linear,
.map_voltage = regulator_map_voltage_linear,
};
static struct regulator_ops palmas_ops_ext_control_ldo = {
.get_voltage_sel = regulator_get_voltage_sel_regmap,
.set_voltage_sel = regulator_set_voltage_sel_regmap,
.list_voltage = regulator_list_voltage_linear,
.map_voltage = regulator_map_voltage_linear,
};
static struct regulator_ops palmas_ops_extreg = {
.is_enabled = regulator_is_enabled_regmap,
.enable = regulator_enable_regmap,
.disable = regulator_disable_regmap,
};
static struct regulator_ops palmas_ops_ext_control_extreg = {
};
static int palmas_regulator_config_external(struct palmas *palmas, int id,
struct palmas_reg_init *reg_init)
{
int sleep_id = palmas_regs_info[id].sleep_id;
int ret;
ret = palmas_ext_control_req_config(palmas, sleep_id,
reg_init->roof_floor, true);
if (ret < 0)
dev_err(palmas->dev,
"Ext control config for regulator %d failed %d\n",
id, ret);
return ret;
}
/*
* setup the hardware based sleep configuration of the SMPS/LDO regulators
* from the platform data. This is different to the software based control
* supported by the regulator framework as it is controlled by toggling
* pins on the PMIC such as PREQ, SYSEN, ...
*/
static int palmas_smps_init(struct palmas *palmas, int id,
struct palmas_reg_init *reg_init)
{
unsigned int reg;
unsigned int addr;
int ret;
addr = palmas_regs_info[id].ctrl_addr;
ret = palmas_smps_read(palmas, addr, &reg);
if (ret)
return ret;
switch (id) {
case PALMAS_REG_SMPS10_OUT1:
case PALMAS_REG_SMPS10_OUT2:
reg &= ~PALMAS_SMPS10_CTRL_MODE_SLEEP_MASK;
if (reg_init->mode_sleep)
reg |= reg_init->mode_sleep <<
PALMAS_SMPS10_CTRL_MODE_SLEEP_SHIFT;
break;
default:
if (reg_init->warm_reset)
reg |= PALMAS_SMPS12_CTRL_WR_S;
else
reg &= ~PALMAS_SMPS12_CTRL_WR_S;
if (reg_init->roof_floor)
reg |= PALMAS_SMPS12_CTRL_ROOF_FLOOR_EN;
else
reg &= ~PALMAS_SMPS12_CTRL_ROOF_FLOOR_EN;
reg &= ~PALMAS_SMPS12_CTRL_MODE_SLEEP_MASK;
if (reg_init->mode_sleep)
reg |= reg_init->mode_sleep <<
PALMAS_SMPS12_CTRL_MODE_SLEEP_SHIFT;
}
ret = palmas_smps_write(palmas, addr, reg);
if (ret)
return ret;
if (palmas_regs_info[id].vsel_addr && reg_init->vsel) {
addr = palmas_regs_info[id].vsel_addr;
reg = reg_init->vsel;
ret = palmas_smps_write(palmas, addr, reg);
if (ret)
return ret;
}
if (reg_init->roof_floor && (id != PALMAS_REG_SMPS10_OUT1) &&
(id != PALMAS_REG_SMPS10_OUT2)) {
/* Enable externally controlled regulator */
addr = palmas_regs_info[id].ctrl_addr;
ret = palmas_smps_read(palmas, addr, &reg);
if (ret < 0)
return ret;
if (!(reg & PALMAS_SMPS12_CTRL_MODE_ACTIVE_MASK)) {
reg |= SMPS_CTRL_MODE_ON;
ret = palmas_smps_write(palmas, addr, reg);
if (ret < 0)
return ret;
}
return palmas_regulator_config_external(palmas, id, reg_init);
}
return 0;
}
static int palmas_ldo_init(struct palmas *palmas, int id,
struct palmas_reg_init *reg_init)
{
unsigned int reg;
unsigned int addr;
int ret;
addr = palmas_regs_info[id].ctrl_addr;
ret = palmas_ldo_read(palmas, addr, &reg);
if (ret)
return ret;
if (reg_init->warm_reset)
reg |= PALMAS_LDO1_CTRL_WR_S;
else
reg &= ~PALMAS_LDO1_CTRL_WR_S;
if (reg_init->mode_sleep)
reg |= PALMAS_LDO1_CTRL_MODE_SLEEP;
else
reg &= ~PALMAS_LDO1_CTRL_MODE_SLEEP;
ret = palmas_ldo_write(palmas, addr, reg);
if (ret)
return ret;
if (reg_init->roof_floor) {
/* Enable externally controlled regulator */
addr = palmas_regs_info[id].ctrl_addr;
ret = palmas_update_bits(palmas, PALMAS_LDO_BASE,
addr, PALMAS_LDO1_CTRL_MODE_ACTIVE,
PALMAS_LDO1_CTRL_MODE_ACTIVE);
if (ret < 0) {
dev_err(palmas->dev,
"LDO Register 0x%02x update failed %d\n",
addr, ret);
return ret;
}
return palmas_regulator_config_external(palmas, id, reg_init);
}
return 0;
}
static int palmas_extreg_init(struct palmas *palmas, int id,
struct palmas_reg_init *reg_init)
{
unsigned int addr;
int ret;
unsigned int val = 0;
addr = palmas_regs_info[id].ctrl_addr;
if (reg_init->mode_sleep)
val = PALMAS_REGEN1_CTRL_MODE_SLEEP;
ret = palmas_update_bits(palmas, PALMAS_RESOURCE_BASE,
addr, PALMAS_REGEN1_CTRL_MODE_SLEEP, val);
if (ret < 0) {
dev_err(palmas->dev, "Resource reg 0x%02x update failed %d\n",
addr, ret);
return ret;
}
if (reg_init->roof_floor) {
/* Enable externally controlled regulator */
addr = palmas_regs_info[id].ctrl_addr;
ret = palmas_update_bits(palmas, PALMAS_RESOURCE_BASE,
addr, PALMAS_REGEN1_CTRL_MODE_ACTIVE,
PALMAS_REGEN1_CTRL_MODE_ACTIVE);
if (ret < 0) {
dev_err(palmas->dev,
"Resource Register 0x%02x update failed %d\n",
addr, ret);
return ret;
}
return palmas_regulator_config_external(palmas, id, reg_init);
}
return 0;
}
static void palmas_enable_ldo8_track(struct palmas *palmas)
{
unsigned int reg;
unsigned int addr;
int ret;
addr = palmas_regs_info[PALMAS_REG_LDO8].ctrl_addr;
ret = palmas_ldo_read(palmas, addr, &reg);
if (ret) {
dev_err(palmas->dev, "Error in reading ldo8 control reg\n");
return;
}
reg |= PALMAS_LDO8_CTRL_LDO_TRACKING_EN;
ret = palmas_ldo_write(palmas, addr, reg);
if (ret < 0) {
dev_err(palmas->dev, "Error in enabling tracking mode\n");
return;
}
/*
* When SMPS45 is set to off and LDO8 tracking is enabled, the LDO8
* output is defined by the LDO8_VOLTAGE.VSEL register divided by two,
* and can be set from 0.45 to 1.65 V.
*/
addr = palmas_regs_info[PALMAS_REG_LDO8].vsel_addr;
ret = palmas_ldo_read(palmas, addr, &reg);
if (ret) {
dev_err(palmas->dev, "Error in reading ldo8 voltage reg\n");
return;
}
reg = (reg << 1) & PALMAS_LDO8_VOLTAGE_VSEL_MASK;
ret = palmas_ldo_write(palmas, addr, reg);
if (ret < 0)
dev_err(palmas->dev, "Error in setting ldo8 voltage reg\n");
return;
}
static struct of_regulator_match palmas_matches[] = {
{ .name = "smps12", },
{ .name = "smps123", },
{ .name = "smps3", },
{ .name = "smps45", },
{ .name = "smps457", },
{ .name = "smps6", },
{ .name = "smps7", },
{ .name = "smps8", },
{ .name = "smps9", },
{ .name = "smps10_out2", },
{ .name = "smps10_out1", },
{ .name = "ldo1", },
{ .name = "ldo2", },
{ .name = "ldo3", },
{ .name = "ldo4", },
{ .name = "ldo5", },
{ .name = "ldo6", },
{ .name = "ldo7", },
{ .name = "ldo8", },
{ .name = "ldo9", },
{ .name = "ldoln", },
{ .name = "ldousb", },
{ .name = "regen1", },
{ .name = "regen2", },
{ .name = "regen3", },
{ .name = "sysen1", },
{ .name = "sysen2", },
};
static void palmas_dt_to_pdata(struct device *dev,
struct device_node *node,
struct palmas_pmic_platform_data *pdata)
{
struct device_node *regulators;
u32 prop;
int idx, ret;
node = of_node_get(node);
regulators = of_find_node_by_name(node, "regulators");
if (!regulators) {
dev_info(dev, "regulator node not found\n");
return;
}
ret = of_regulator_match(dev, regulators, palmas_matches,
PALMAS_NUM_REGS);
of_node_put(regulators);
if (ret < 0) {
dev_err(dev, "Error parsing regulator init data: %d\n", ret);
return;
}
for (idx = 0; idx < PALMAS_NUM_REGS; idx++) {
if (!palmas_matches[idx].init_data ||
!palmas_matches[idx].of_node)
continue;
pdata->reg_data[idx] = palmas_matches[idx].init_data;
pdata->reg_init[idx] = devm_kzalloc(dev,
sizeof(struct palmas_reg_init), GFP_KERNEL);
pdata->reg_init[idx]->warm_reset =
of_property_read_bool(palmas_matches[idx].of_node,
"ti,warm-reset");
ret = of_property_read_u32(palmas_matches[idx].of_node,
"ti,roof-floor", &prop);
/* EINVAL: Property not found */
if (ret != -EINVAL) {
int econtrol;
/* use default value, when no value is specified */
econtrol = PALMAS_EXT_CONTROL_NSLEEP;
if (!ret) {
switch (prop) {
case 1:
econtrol = PALMAS_EXT_CONTROL_ENABLE1;
break;
case 2:
econtrol = PALMAS_EXT_CONTROL_ENABLE2;
break;
case 3:
econtrol = PALMAS_EXT_CONTROL_NSLEEP;
break;
default:
WARN_ON(1);
dev_warn(dev,
"%s: Invalid roof-floor option: %u\n",
palmas_matches[idx].name, prop);
break;
}
}
pdata->reg_init[idx]->roof_floor = econtrol;
}
ret = of_property_read_u32(palmas_matches[idx].of_node,
"ti,mode-sleep", &prop);
if (!ret)
pdata->reg_init[idx]->mode_sleep = prop;
ret = of_property_read_bool(palmas_matches[idx].of_node,
"ti,smps-range");
if (ret)
pdata->reg_init[idx]->vsel =
PALMAS_SMPS12_VOLTAGE_RANGE;
if (idx == PALMAS_REG_LDO8)
pdata->enable_ldo8_tracking = of_property_read_bool(
palmas_matches[idx].of_node,
"ti,enable-ldo8-tracking");
}
pdata->ldo6_vibrator = of_property_read_bool(node, "ti,ldo6-vibrator");
}
static int palmas_regulators_probe(struct platform_device *pdev)
{
struct palmas *palmas = dev_get_drvdata(pdev->dev.parent);
struct palmas_pmic_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct device_node *node = pdev->dev.of_node;
struct regulator_dev *rdev;
struct regulator_config config = { };
struct palmas_pmic *pmic;
struct palmas_reg_init *reg_init;
int id = 0, ret;
unsigned int addr, reg;
if (node && !pdata) {
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return -ENOMEM;
palmas_dt_to_pdata(&pdev->dev, node, pdata);
}
pmic = devm_kzalloc(&pdev->dev, sizeof(*pmic), GFP_KERNEL);
if (!pmic)
return -ENOMEM;
pmic->dev = &pdev->dev;
pmic->palmas = palmas;
palmas->pmic = pmic;
platform_set_drvdata(pdev, pmic);
ret = palmas_smps_read(palmas, PALMAS_SMPS_CTRL, &reg);
if (ret)
return ret;
if (reg & PALMAS_SMPS_CTRL_SMPS12_SMPS123_EN)
pmic->smps123 = 1;
if (reg & PALMAS_SMPS_CTRL_SMPS45_SMPS457_EN)
pmic->smps457 = 1;
config.regmap = palmas->regmap[REGULATOR_SLAVE];
config.dev = &pdev->dev;
config.driver_data = pmic;
for (id = 0; id < PALMAS_REG_LDO1; id++) {
bool ramp_delay_support = false;
/*
* Miss out regulators which are not available due
* to slaving configurations.
*/
switch (id) {
case PALMAS_REG_SMPS12:
case PALMAS_REG_SMPS3:
if (pmic->smps123)
continue;
if (id == PALMAS_REG_SMPS12)
ramp_delay_support = true;
break;
case PALMAS_REG_SMPS123:
if (!pmic->smps123)
continue;
ramp_delay_support = true;
break;
case PALMAS_REG_SMPS45:
case PALMAS_REG_SMPS7:
if (pmic->smps457)
continue;
if (id == PALMAS_REG_SMPS45)
ramp_delay_support = true;
break;
case PALMAS_REG_SMPS457:
if (!pmic->smps457)
continue;
ramp_delay_support = true;
break;
case PALMAS_REG_SMPS10_OUT1:
case PALMAS_REG_SMPS10_OUT2:
if (!PALMAS_PMIC_HAS(palmas, SMPS10_BOOST))
continue;
}
if ((id == PALMAS_REG_SMPS6) || (id == PALMAS_REG_SMPS8))
ramp_delay_support = true;
if (ramp_delay_support) {
addr = palmas_regs_info[id].tstep_addr;
ret = palmas_smps_read(pmic->palmas, addr, &reg);
if (ret < 0) {
dev_err(&pdev->dev,
"reading TSTEP reg failed: %d\n", ret);
goto err_unregister_regulator;
}
pmic->desc[id].ramp_delay =
palmas_smps_ramp_delay[reg & 0x3];
pmic->ramp_delay[id] = pmic->desc[id].ramp_delay;
}
/* Initialise sleep/init values from platform data */
if (pdata && pdata->reg_init[id]) {
reg_init = pdata->reg_init[id];
ret = palmas_smps_init(palmas, id, reg_init);
if (ret)
goto err_unregister_regulator;
} else {
reg_init = NULL;
}
/* Register the regulators */
pmic->desc[id].name = palmas_regs_info[id].name;
pmic->desc[id].id = id;
switch (id) {
case PALMAS_REG_SMPS10_OUT1:
case PALMAS_REG_SMPS10_OUT2:
pmic->desc[id].n_voltages = PALMAS_SMPS10_NUM_VOLTAGES;
pmic->desc[id].ops = &palmas_ops_smps10;
pmic->desc[id].vsel_reg =
PALMAS_BASE_TO_REG(PALMAS_SMPS_BASE,
PALMAS_SMPS10_CTRL);
pmic->desc[id].vsel_mask = SMPS10_VSEL;
pmic->desc[id].enable_reg =
PALMAS_BASE_TO_REG(PALMAS_SMPS_BASE,
PALMAS_SMPS10_CTRL);
if (id == PALMAS_REG_SMPS10_OUT1)
pmic->desc[id].enable_mask = SMPS10_SWITCH_EN;
else
pmic->desc[id].enable_mask = SMPS10_BOOST_EN;
pmic->desc[id].bypass_reg =
PALMAS_BASE_TO_REG(PALMAS_SMPS_BASE,
PALMAS_SMPS10_CTRL);
pmic->desc[id].bypass_mask = SMPS10_BYPASS_EN;
pmic->desc[id].min_uV = 3750000;
pmic->desc[id].uV_step = 1250000;
break;
default:
/*
* Read and store the RANGE bit for later use
* This must be done before regulator is probed,
* otherwise we error in probe with unsupportable
* ranges. Read the current smps mode for later use.
*/
addr = palmas_regs_info[id].vsel_addr;
ret = palmas_smps_read(pmic->palmas, addr, &reg);
if (ret)
goto err_unregister_regulator;
if (reg & PALMAS_SMPS12_VOLTAGE_RANGE)
pmic->range[id] = 1;
if (reg_init && reg_init->roof_floor)
pmic->desc[id].ops =
&palmas_ops_ext_control_smps;
else
pmic->desc[id].ops = &palmas_ops_smps;
pmic->desc[id].n_voltages = PALMAS_SMPS_NUM_VOLTAGES;
pmic->desc[id].vsel_reg =
PALMAS_BASE_TO_REG(PALMAS_SMPS_BASE,
palmas_regs_info[id].vsel_addr);
pmic->desc[id].vsel_mask =
PALMAS_SMPS12_VOLTAGE_VSEL_MASK;
/* Read the smps mode for later use. */
addr = palmas_regs_info[id].ctrl_addr;
ret = palmas_smps_read(pmic->palmas, addr, &reg);
if (ret)
goto err_unregister_regulator;
pmic->current_reg_mode[id] = reg &
PALMAS_SMPS12_CTRL_MODE_ACTIVE_MASK;
}
pmic->desc[id].type = REGULATOR_VOLTAGE;
pmic->desc[id].owner = THIS_MODULE;
if (pdata)
config.init_data = pdata->reg_data[id];
else
config.init_data = NULL;
pmic->desc[id].supply_name = palmas_regs_info[id].sname;
config.of_node = palmas_matches[id].of_node;
rdev = regulator_register(&pmic->desc[id], &config);
if (IS_ERR(rdev)) {
dev_err(&pdev->dev,
"failed to register %s regulator\n",
pdev->name);
ret = PTR_ERR(rdev);
goto err_unregister_regulator;
}
/* Save regulator for cleanup */
pmic->rdev[id] = rdev;
}
/* Start this loop from the id left from previous loop */
for (; id < PALMAS_NUM_REGS; id++) {
if (pdata && pdata->reg_init[id])
reg_init = pdata->reg_init[id];
else
reg_init = NULL;
/* Miss out regulators which are not available due
* to alternate functions.
*/
/* Register the regulators */
pmic->desc[id].name = palmas_regs_info[id].name;
pmic->desc[id].id = id;
pmic->desc[id].type = REGULATOR_VOLTAGE;
pmic->desc[id].owner = THIS_MODULE;
if (id < PALMAS_REG_REGEN1) {
pmic->desc[id].n_voltages = PALMAS_LDO_NUM_VOLTAGES;
if (reg_init && reg_init->roof_floor)
pmic->desc[id].ops =
&palmas_ops_ext_control_ldo;
else
pmic->desc[id].ops = &palmas_ops_ldo;
pmic->desc[id].min_uV = 900000;
pmic->desc[id].uV_step = 50000;
pmic->desc[id].linear_min_sel = 1;
pmic->desc[id].vsel_reg =
PALMAS_BASE_TO_REG(PALMAS_LDO_BASE,
palmas_regs_info[id].vsel_addr);
pmic->desc[id].vsel_mask =
PALMAS_LDO1_VOLTAGE_VSEL_MASK;
pmic->desc[id].enable_reg =
PALMAS_BASE_TO_REG(PALMAS_LDO_BASE,
palmas_regs_info[id].ctrl_addr);
pmic->desc[id].enable_mask =
PALMAS_LDO1_CTRL_MODE_ACTIVE;
/* Check if LDO8 is in tracking mode or not */
if (pdata && (id == PALMAS_REG_LDO8) &&
pdata->enable_ldo8_tracking) {
palmas_enable_ldo8_track(palmas);
pmic->desc[id].min_uV = 450000;
pmic->desc[id].uV_step = 25000;
}
} else {
pmic->desc[id].n_voltages = 1;
if (reg_init && reg_init->roof_floor)
pmic->desc[id].ops =
&palmas_ops_ext_control_extreg;
else
pmic->desc[id].ops = &palmas_ops_extreg;
pmic->desc[id].enable_reg =
PALMAS_BASE_TO_REG(PALMAS_RESOURCE_BASE,
palmas_regs_info[id].ctrl_addr);
pmic->desc[id].enable_mask =
PALMAS_REGEN1_CTRL_MODE_ACTIVE;
}
if (pdata)
config.init_data = pdata->reg_data[id];
else
config.init_data = NULL;
pmic->desc[id].supply_name = palmas_regs_info[id].sname;
config.of_node = palmas_matches[id].of_node;
rdev = regulator_register(&pmic->desc[id], &config);
if (IS_ERR(rdev)) {
dev_err(&pdev->dev,
"failed to register %s regulator\n",
pdev->name);
ret = PTR_ERR(rdev);
goto err_unregister_regulator;
}
/* Save regulator for cleanup */
pmic->rdev[id] = rdev;
/* Initialise sleep/init values from platform data */
if (pdata) {
reg_init = pdata->reg_init[id];
if (reg_init) {
if (id < PALMAS_REG_REGEN1)
ret = palmas_ldo_init(palmas,
id, reg_init);
else
ret = palmas_extreg_init(palmas,
id, reg_init);
if (ret) {
regulator_unregister(pmic->rdev[id]);
goto err_unregister_regulator;
}
}
}
}
return 0;
err_unregister_regulator:
while (--id >= 0)
regulator_unregister(pmic->rdev[id]);
return ret;
}
static int palmas_regulators_remove(struct platform_device *pdev)
{
struct palmas_pmic *pmic = platform_get_drvdata(pdev);
int id;
for (id = 0; id < PALMAS_NUM_REGS; id++)
regulator_unregister(pmic->rdev[id]);
return 0;
}
static struct of_device_id of_palmas_match_tbl[] = {
{ .compatible = "ti,palmas-pmic", },
{ .compatible = "ti,twl6035-pmic", },
{ .compatible = "ti,twl6036-pmic", },
{ .compatible = "ti,twl6037-pmic", },
{ .compatible = "ti,tps65913-pmic", },
{ .compatible = "ti,tps65914-pmic", },
{ .compatible = "ti,tps80036-pmic", },
{ .compatible = "ti,tps659038-pmic", },
{ /* end */ }
};
static struct platform_driver palmas_driver = {
.driver = {
.name = "palmas-pmic",
.of_match_table = of_palmas_match_tbl,
.owner = THIS_MODULE,
},
.probe = palmas_regulators_probe,
.remove = palmas_regulators_remove,
};
static int __init palmas_init(void)
{
return platform_driver_register(&palmas_driver);
}
subsys_initcall(palmas_init);
static void __exit palmas_exit(void)
{
platform_driver_unregister(&palmas_driver);
}
module_exit(palmas_exit);
MODULE_AUTHOR("Graeme Gregory <gg@slimlogic.co.uk>");
MODULE_DESCRIPTION("Palmas voltage regulator driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:palmas-pmic");
MODULE_DEVICE_TABLE(of, of_palmas_match_tbl);