blob: 41e8ad464e9d8786443b8bbf66f3f4bae19caeb5 [file] [log] [blame]
/*
* Author: Jisheng Zhang <jszhang@marvell.com>
* Copyright (c) 2013 Marvell Technology Group Ltd.
* http://www.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/slab.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/regulator/of_regulator.h>
#define PG86x_NUM_REGS 2
static int enable = 1;
module_param(enable, int, 0444);
struct pg86x_priv {
struct i2c_client *i2c;
u8 reg[PG86x_NUM_REGS];
u8 vol1v[PG86x_NUM_REGS];
u8 vol1p6v[PG86x_NUM_REGS];
struct regulator_dev *rdev[PG86x_NUM_REGS];
struct regulator_init_data *reg_data[PG86x_NUM_REGS];
struct regulator_desc desc[PG86x_NUM_REGS];
};
static int pg86x_is_enabled(struct regulator_dev *rdev)
{
return enable;
}
static int pg86x_list_voltage(struct regulator_dev *rdev, unsigned selector)
{
int id = rdev_get_id(rdev);
struct pg86x_priv *priv = rdev_get_drvdata(rdev);
if (selector >= priv->desc[id].n_voltages)
return -EINVAL;
if (selector >= 24)
return 1600000 + 50000 * (selector - 24);
return 1000000 + 25000 * selector;
}
static int pg86x_get_voltage(struct regulator_dev *rdev)
{
int ret, id = rdev_get_id(rdev);
struct pg86x_priv *priv = rdev_get_drvdata(rdev);
ret = i2c_smbus_read_byte_data(priv->i2c, priv->reg[id]);
if (ret < 0) {
dev_err(&priv->i2c->dev, "I2C read error\n");
return ret;
}
if (ret >= priv->desc[id].n_voltages)
return -EINVAL;
if (ret >= priv->vol1p6v[id])
return 1600000 + 50000 * (ret - priv->vol1p6v[id]);
if (ret >= priv->vol1v[id])
return 1000000 + 25000 * (ret - priv->vol1v[id]);
return -EINVAL;
}
static int pg86x_set_voltage(struct regulator_dev *rdev, int min_uV,
int max_uV, unsigned *selector)
{
u8 val;
int ret, delta, id = rdev_get_id(rdev);
struct pg86x_priv *priv = rdev_get_drvdata(rdev);
if (min_uV > 2200000 || min_uV < 1000000)
return -EINVAL;
if (max_uV > 2200000 || max_uV < 1000000)
return -EINVAL;
if (min_uV >= 1600000) {
delta = min_uV - 1600000;
delta = (delta + 50000 - 1 ) / 50000 * 50000;
if (delta > max_uV - 1600000)
return -EINVAL;
delta /= 50000;
*selector = delta;
val = delta + priv->vol1p6v[id];
}
if (min_uV >= 1000000) {
delta = min_uV - 1000000;
delta = (delta + 25000 - 1 ) / 25000 * 25000;
if (delta > max_uV - 1000000)
return -EINVAL;
delta /= 25000;
*selector = delta;
val = delta + priv->vol1v[id];
}
ret = i2c_smbus_write_byte_data(priv->i2c, priv->reg[id], val);
if (ret < 0)
dev_err(&priv->i2c->dev, "I2C write error\n");
return 0;
}
static struct regulator_ops pg86x_regulator_ops = {
.is_enabled = pg86x_is_enabled,
.list_voltage = pg86x_list_voltage,
.set_voltage = pg86x_set_voltage,
.get_voltage = pg86x_get_voltage,
};
static const struct i2c_device_id pg86x_i2c_id[] = {
{ "pg86x", 0x867 },
{},
};
MODULE_DEVICE_TABLE(i2c, pg86x_i2c_id);
static const struct of_device_id pg86x_dt_ids[] = {
{ .compatible = "marvell,pg86x", },
{},
};
MODULE_DEVICE_TABLE(of, pg86x_dt_ids);
static struct of_regulator_match pg86x_matches[] = {
{ .name = "BK1_TV", },
{ .name = "BK2_TV", },
};
static int pg86x_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
{
int i, ret;
struct pg86x_priv *priv;
struct regulator_config config = { };
struct device_node *np = i2c->dev.of_node;
if (enable != 1)
return -ENODEV;
priv = devm_kzalloc(&i2c->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
ret = of_regulator_match(&i2c->dev, np, pg86x_matches, PG86x_NUM_REGS);
if (ret < 0) {
dev_err(&i2c->dev, "Error parsing regulator init data: %d\n", ret);
return ret;
}
priv->i2c = i2c;
i2c_set_clientdata(i2c, priv);
for (i = 0; i < PG86x_NUM_REGS; i++) {
struct regulator_dev *rdev;
if (!pg86x_matches[i].init_data ||
!pg86x_matches[i].of_node) {
continue;
}
priv->reg_data[i] = pg86x_matches[i].init_data;
priv->desc[i].name = pg86x_matches[i].name;
priv->desc[i].id = i;
priv->desc[i].ops = &pg86x_regulator_ops;
priv->desc[i].type = REGULATOR_VOLTAGE;
priv->desc[i].owner = THIS_MODULE;
priv->desc[i].n_voltages = 37;
priv->reg[i] = (i == 0)? 0x24: 0x13;
priv->vol1v[i] = (i == 0)? 0xB: 0x10;
priv->vol1p6v[i] = (i == 0)? 0x23: 0x28;
config.dev = &i2c->dev;
config.init_data = priv->reg_data[i];
config.driver_data = priv;
config.of_node = pg86x_matches[i].of_node;
rdev = regulator_register(&priv->desc[i], &config);
if (IS_ERR(rdev)) {
dev_err(&i2c->dev, "failed to register pg86x\n");
ret = PTR_ERR(rdev);
while (--i >= 0)
regulator_unregister(priv->rdev[i]);
return ret;
}
priv->rdev[i] = rdev;
}
dev_info(&i2c->dev, "pg86x_probe done");
return 0;
}
static int pg86x_remove(struct i2c_client *i2c)
{
struct pg86x_priv *priv = i2c_get_clientdata(i2c);
int i;
for (i = 0; i < PG86x_NUM_REGS; i++)
regulator_unregister(priv->rdev[i]);
return 0;
}
static struct i2c_driver pg86x_driver = {
.id_table = pg86x_i2c_id,
.driver = {
.name = "pg86x",
.owner = THIS_MODULE,
.of_match_table = pg86x_dt_ids,
},
.probe = pg86x_probe,
.remove = pg86x_remove,
};
module_i2c_driver(pg86x_driver);
MODULE_DESCRIPTION("Driver for Marvell 88PG86x PMIC");
MODULE_AUTHOR("Jisheng Zhang <jszhang@marvell.com>");
MODULE_LICENSE("GPL v2");