394 lines
10 KiB
C
394 lines
10 KiB
C
|
/* Copyright (c) 2012, The Linux Foundation. All rights reserved.
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License version 2 and
|
||
|
* only version 2 as published by the Free Software Foundation.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*/
|
||
|
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/err.h>
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/i2c.h>
|
||
|
#include <linux/regulator/driver.h>
|
||
|
#include <linux/regmap.h>
|
||
|
#include <linux/regulator/onsemi-ncp6335d.h>
|
||
|
|
||
|
/* registers */
|
||
|
#define REG_NCP6335D_PID 0x03
|
||
|
#define REG_NCP6335D_PROGVSEL1 0x10
|
||
|
#define REG_NCP6335D_PROGVSEL0 0x11
|
||
|
#define REG_NCP6335D_PGOOD 0x12
|
||
|
#define REG_NCP6335D_TIMING 0x13
|
||
|
#define REG_NCP6335D_COMMAND 0x14
|
||
|
|
||
|
/* constraints */
|
||
|
#define NCP6335D_MIN_VOLTAGE_UV 600000
|
||
|
#define NCP6335D_STEP_VOLTAGE_UV 6250
|
||
|
#define NCP6335D_MIN_SLEW_NS 166
|
||
|
#define NCP6335D_MAX_SLEW_NS 1333
|
||
|
|
||
|
/* bits */
|
||
|
#define NCP6335D_ENABLE BIT(7)
|
||
|
#define NCP6335D_DVS_PWM_MODE BIT(5)
|
||
|
#define NCP6335D_PWM_MODE1 BIT(6)
|
||
|
#define NCP6335D_PWM_MODE0 BIT(7)
|
||
|
#define NCP6335D_PGOOD_DISCHG BIT(4)
|
||
|
#define NCP6335D_SLEEP_MODE BIT(4)
|
||
|
|
||
|
#define NCP6335D_VOUT_SEL_MASK 0x7F
|
||
|
#define NCP6335D_SLEW_MASK 0x18
|
||
|
#define NCP6335D_SLEW_SHIFT 0x3
|
||
|
|
||
|
struct ncp6335d_info {
|
||
|
struct regulator_dev *regulator;
|
||
|
struct regulator_init_data *init_data;
|
||
|
struct regmap *regmap;
|
||
|
struct device *dev;
|
||
|
unsigned int vsel_reg;
|
||
|
unsigned int mode_bit;
|
||
|
int curr_voltage;
|
||
|
int slew_rate;
|
||
|
};
|
||
|
|
||
|
static void dump_registers(struct ncp6335d_info *dd,
|
||
|
unsigned int reg, const char *func)
|
||
|
{
|
||
|
unsigned int val = 0;
|
||
|
|
||
|
regmap_read(dd->regmap, reg, &val);
|
||
|
dev_dbg(dd->dev, "%s: NCP6335D: Reg = %x, Val = %x\n", func, reg, val);
|
||
|
}
|
||
|
|
||
|
static void ncp633d_slew_delay(struct ncp6335d_info *dd,
|
||
|
int prev_uV, int new_uV)
|
||
|
{
|
||
|
u8 val;
|
||
|
int delay;
|
||
|
|
||
|
val = abs(prev_uV - new_uV) / NCP6335D_STEP_VOLTAGE_UV;
|
||
|
delay = (val * dd->slew_rate / 1000) + 1;
|
||
|
|
||
|
dev_dbg(dd->dev, "Slew Delay = %d\n", delay);
|
||
|
|
||
|
udelay(delay);
|
||
|
}
|
||
|
|
||
|
static int ncp6335d_enable(struct regulator_dev *rdev)
|
||
|
{
|
||
|
int rc;
|
||
|
struct ncp6335d_info *dd = rdev_get_drvdata(rdev);
|
||
|
|
||
|
rc = regmap_update_bits(dd->regmap, dd->vsel_reg,
|
||
|
NCP6335D_ENABLE, NCP6335D_ENABLE);
|
||
|
if (rc)
|
||
|
dev_err(dd->dev, "Unable to enable regualtor rc(%d)", rc);
|
||
|
|
||
|
dump_registers(dd, dd->vsel_reg, __func__);
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static int ncp6335d_disable(struct regulator_dev *rdev)
|
||
|
{
|
||
|
int rc;
|
||
|
struct ncp6335d_info *dd = rdev_get_drvdata(rdev);
|
||
|
|
||
|
rc = regmap_update_bits(dd->regmap, dd->vsel_reg,
|
||
|
NCP6335D_ENABLE, 0);
|
||
|
if (rc)
|
||
|
dev_err(dd->dev, "Unable to disable regualtor rc(%d)", rc);
|
||
|
|
||
|
dump_registers(dd, dd->vsel_reg, __func__);
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static int ncp6335d_get_voltage(struct regulator_dev *rdev)
|
||
|
{
|
||
|
unsigned int val;
|
||
|
int rc;
|
||
|
struct ncp6335d_info *dd = rdev_get_drvdata(rdev);
|
||
|
|
||
|
rc = regmap_read(dd->regmap, dd->vsel_reg, &val);
|
||
|
if (rc) {
|
||
|
dev_err(dd->dev, "Unable to get volatge rc(%d)", rc);
|
||
|
return rc;
|
||
|
}
|
||
|
dd->curr_voltage = ((val & NCP6335D_VOUT_SEL_MASK) *
|
||
|
NCP6335D_STEP_VOLTAGE_UV) + NCP6335D_MIN_VOLTAGE_UV;
|
||
|
|
||
|
dump_registers(dd, dd->vsel_reg, __func__);
|
||
|
|
||
|
return dd->curr_voltage;
|
||
|
}
|
||
|
|
||
|
static int ncp6335d_set_voltage(struct regulator_dev *rdev,
|
||
|
int min_uV, int max_uV, unsigned *selector)
|
||
|
{
|
||
|
int rc, set_val, new_uV;
|
||
|
struct ncp6335d_info *dd = rdev_get_drvdata(rdev);
|
||
|
|
||
|
set_val = DIV_ROUND_UP(min_uV - NCP6335D_MIN_VOLTAGE_UV,
|
||
|
NCP6335D_STEP_VOLTAGE_UV);
|
||
|
new_uV = (set_val * NCP6335D_STEP_VOLTAGE_UV) +
|
||
|
NCP6335D_MIN_VOLTAGE_UV;
|
||
|
if (new_uV > max_uV) {
|
||
|
dev_err(dd->dev, "Unable to set volatge (%d %d)\n",
|
||
|
min_uV, max_uV);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
rc = regmap_update_bits(dd->regmap, dd->vsel_reg,
|
||
|
NCP6335D_VOUT_SEL_MASK, (set_val & NCP6335D_VOUT_SEL_MASK));
|
||
|
if (rc) {
|
||
|
dev_err(dd->dev, "Unable to set volatge (%d %d)\n",
|
||
|
min_uV, max_uV);
|
||
|
} else {
|
||
|
ncp633d_slew_delay(dd, dd->curr_voltage, new_uV);
|
||
|
dd->curr_voltage = new_uV;
|
||
|
}
|
||
|
|
||
|
dump_registers(dd, dd->vsel_reg, __func__);
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static int ncp6335d_set_mode(struct regulator_dev *rdev,
|
||
|
unsigned int mode)
|
||
|
{
|
||
|
int rc;
|
||
|
struct ncp6335d_info *dd = rdev_get_drvdata(rdev);
|
||
|
|
||
|
/* only FAST and NORMAL mode types are supported */
|
||
|
if (mode != REGULATOR_MODE_FAST && mode != REGULATOR_MODE_NORMAL) {
|
||
|
dev_err(dd->dev, "Mode %d not supported\n", mode);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
rc = regmap_update_bits(dd->regmap, REG_NCP6335D_COMMAND, dd->mode_bit,
|
||
|
(mode == REGULATOR_MODE_FAST) ? dd->mode_bit : 0);
|
||
|
if (rc) {
|
||
|
dev_err(dd->dev, "Unable to set operating mode rc(%d)", rc);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
rc = regmap_update_bits(dd->regmap, REG_NCP6335D_COMMAND,
|
||
|
NCP6335D_DVS_PWM_MODE,
|
||
|
(mode == REGULATOR_MODE_FAST) ?
|
||
|
NCP6335D_DVS_PWM_MODE : 0);
|
||
|
if (rc)
|
||
|
dev_err(dd->dev, "Unable to set DVS trans. mode rc(%d)", rc);
|
||
|
|
||
|
dump_registers(dd, REG_NCP6335D_COMMAND, __func__);
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static unsigned int ncp6335d_get_mode(struct regulator_dev *rdev)
|
||
|
{
|
||
|
unsigned int val;
|
||
|
int rc;
|
||
|
struct ncp6335d_info *dd = rdev_get_drvdata(rdev);
|
||
|
|
||
|
rc = regmap_read(dd->regmap, REG_NCP6335D_COMMAND, &val);
|
||
|
if (rc) {
|
||
|
dev_err(dd->dev, "Unable to get regulator mode rc(%d)\n", rc);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
dump_registers(dd, REG_NCP6335D_COMMAND, __func__);
|
||
|
|
||
|
if (val & dd->mode_bit)
|
||
|
return REGULATOR_MODE_FAST;
|
||
|
|
||
|
return REGULATOR_MODE_NORMAL;
|
||
|
}
|
||
|
|
||
|
static struct regulator_ops ncp6335d_ops = {
|
||
|
.set_voltage = ncp6335d_set_voltage,
|
||
|
.get_voltage = ncp6335d_get_voltage,
|
||
|
.enable = ncp6335d_enable,
|
||
|
.disable = ncp6335d_disable,
|
||
|
.set_mode = ncp6335d_set_mode,
|
||
|
.get_mode = ncp6335d_get_mode,
|
||
|
};
|
||
|
|
||
|
static struct regulator_desc rdesc = {
|
||
|
.name = "ncp6335d",
|
||
|
.owner = THIS_MODULE,
|
||
|
.n_voltages = 128,
|
||
|
.ops = &ncp6335d_ops,
|
||
|
};
|
||
|
|
||
|
static int __devinit ncp6335d_init(struct ncp6335d_info *dd,
|
||
|
const struct ncp6335d_platform_data *pdata)
|
||
|
{
|
||
|
int rc;
|
||
|
unsigned int val;
|
||
|
|
||
|
switch (pdata->default_vsel) {
|
||
|
case NCP6335D_VSEL0:
|
||
|
dd->vsel_reg = REG_NCP6335D_PROGVSEL0;
|
||
|
dd->mode_bit = NCP6335D_PWM_MODE0;
|
||
|
break;
|
||
|
case NCP6335D_VSEL1:
|
||
|
dd->vsel_reg = REG_NCP6335D_PROGVSEL1;
|
||
|
dd->mode_bit = NCP6335D_PWM_MODE1;
|
||
|
break;
|
||
|
default:
|
||
|
dev_err(dd->dev, "Invalid VSEL ID %d\n", pdata->default_vsel);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
/* get the current programmed voltage */
|
||
|
rc = regmap_read(dd->regmap, dd->vsel_reg, &val);
|
||
|
if (rc) {
|
||
|
dev_err(dd->dev, "Unable to get volatge rc(%d)", rc);
|
||
|
return rc;
|
||
|
}
|
||
|
dd->curr_voltage = ((val & NCP6335D_VOUT_SEL_MASK) *
|
||
|
NCP6335D_STEP_VOLTAGE_UV) + NCP6335D_MIN_VOLTAGE_UV;
|
||
|
|
||
|
/* set discharge */
|
||
|
rc = regmap_update_bits(dd->regmap, REG_NCP6335D_PGOOD,
|
||
|
NCP6335D_PGOOD_DISCHG,
|
||
|
(pdata->discharge_enable ?
|
||
|
NCP6335D_PGOOD_DISCHG : 0));
|
||
|
if (rc) {
|
||
|
dev_err(dd->dev, "Unable to set Active Discharge rc(%d)\n", rc);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
/* set slew rate */
|
||
|
if (pdata->slew_rate_ns < NCP6335D_MIN_SLEW_NS ||
|
||
|
pdata->slew_rate_ns > NCP6335D_MAX_SLEW_NS) {
|
||
|
dev_err(dd->dev, "Invalid slew rate %d\n", pdata->slew_rate_ns);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
val = DIV_ROUND_UP(pdata->slew_rate_ns - NCP6335D_MIN_SLEW_NS,
|
||
|
NCP6335D_MIN_SLEW_NS);
|
||
|
val >>= 1;
|
||
|
dd->slew_rate = val * NCP6335D_MIN_SLEW_NS;
|
||
|
|
||
|
rc = regmap_update_bits(dd->regmap, REG_NCP6335D_TIMING,
|
||
|
NCP6335D_SLEW_MASK, val << NCP6335D_SLEW_SHIFT);
|
||
|
if (rc)
|
||
|
dev_err(dd->dev, "Unable to set slew rate rc(%d)\n", rc);
|
||
|
|
||
|
/* Set Sleep mode bit */
|
||
|
rc = regmap_update_bits(dd->regmap, REG_NCP6335D_COMMAND,
|
||
|
NCP6335D_SLEEP_MODE, pdata->sleep_enable ?
|
||
|
NCP6335D_SLEEP_MODE : 0);
|
||
|
if (rc)
|
||
|
dev_err(dd->dev, "Unable to set sleep mode (%d)\n", rc);
|
||
|
|
||
|
dump_registers(dd, REG_NCP6335D_COMMAND, __func__);
|
||
|
dump_registers(dd, REG_NCP6335D_PROGVSEL0, __func__);
|
||
|
dump_registers(dd, REG_NCP6335D_TIMING, __func__);
|
||
|
dump_registers(dd, REG_NCP6335D_PGOOD, __func__);
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static struct regmap_config ncp6335d_regmap_config = {
|
||
|
.reg_bits = 8,
|
||
|
.val_bits = 8,
|
||
|
};
|
||
|
|
||
|
static int __devinit ncp6335d_regulator_probe(struct i2c_client *client,
|
||
|
const struct i2c_device_id *id)
|
||
|
{
|
||
|
int rc;
|
||
|
unsigned int val = 0;
|
||
|
struct ncp6335d_info *dd;
|
||
|
const struct ncp6335d_platform_data *pdata;
|
||
|
|
||
|
pdata = client->dev.platform_data;
|
||
|
if (!pdata) {
|
||
|
dev_err(&client->dev, "Platform data not specified\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
dd = devm_kzalloc(&client->dev, sizeof(*dd), GFP_KERNEL);
|
||
|
if (!dd) {
|
||
|
dev_err(&client->dev, "Unable to allocate memory\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
dd->regmap = devm_regmap_init_i2c(client, &ncp6335d_regmap_config);
|
||
|
if (IS_ERR(dd->regmap)) {
|
||
|
dev_err(&client->dev, "Error allocating regmap\n");
|
||
|
return PTR_ERR(dd->regmap);
|
||
|
}
|
||
|
|
||
|
rc = regmap_read(dd->regmap, REG_NCP6335D_PID, &val);
|
||
|
if (rc) {
|
||
|
dev_err(&client->dev, "Unable to identify NCP6335D, rc(%d)\n",
|
||
|
rc);
|
||
|
return rc;
|
||
|
}
|
||
|
dev_info(&client->dev, "Detected Regulator NCP6335D PID = %d\n", val);
|
||
|
|
||
|
dd->init_data = pdata->init_data;
|
||
|
dd->dev = &client->dev;
|
||
|
i2c_set_clientdata(client, dd);
|
||
|
|
||
|
rc = ncp6335d_init(dd, pdata);
|
||
|
if (rc) {
|
||
|
dev_err(&client->dev, "Unable to intialize the regulator\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
dd->regulator = regulator_register(&rdesc, &client->dev,
|
||
|
dd->init_data, dd, NULL);
|
||
|
if (IS_ERR(dd->regulator)) {
|
||
|
dev_err(&client->dev, "Unable to register regulator rc(%ld)",
|
||
|
PTR_ERR(dd->regulator));
|
||
|
return PTR_ERR(dd->regulator);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int __devexit ncp6335d_regulator_remove(struct i2c_client *client)
|
||
|
{
|
||
|
struct ncp6335d_info *dd = i2c_get_clientdata(client);
|
||
|
|
||
|
regulator_unregister(dd->regulator);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct i2c_device_id ncp6335d_id[] = {
|
||
|
{"ncp6335d", -1},
|
||
|
{ },
|
||
|
};
|
||
|
|
||
|
static struct i2c_driver ncp6335d_regulator_driver = {
|
||
|
.driver = {
|
||
|
.name = "ncp6335d-regulator",
|
||
|
},
|
||
|
.probe = ncp6335d_regulator_probe,
|
||
|
.remove = __devexit_p(ncp6335d_regulator_remove),
|
||
|
.id_table = ncp6335d_id,
|
||
|
};
|
||
|
static int __init ncp6335d_regulator_init(void)
|
||
|
{
|
||
|
return i2c_add_driver(&ncp6335d_regulator_driver);
|
||
|
}
|
||
|
subsys_initcall(ncp6335d_regulator_init);
|
||
|
|
||
|
static void __exit ncp6335d_regulator_exit(void)
|
||
|
{
|
||
|
i2c_del_driver(&ncp6335d_regulator_driver);
|
||
|
}
|
||
|
module_exit(ncp6335d_regulator_exit);
|
||
|
MODULE_DESCRIPTION("OnSemi-NCP6335D regulator driver");
|
||
|
MODULE_LICENSE("GPL v2");
|