mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-07 08:48:05 -04:00
721 lines
17 KiB
C
721 lines
17 KiB
C
/*
|
|
* s2mpb01.c - Regulator driver for the S.LSI S2MPB01
|
|
*
|
|
* Copyright (C) 2013 Samsung Electronics
|
|
* SangYoung Son <hello.son@samsung.com>
|
|
*
|
|
* 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.
|
|
*
|
|
* 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
* This driver is based on max77826.c
|
|
*/
|
|
|
|
#include <linux/bug.h>
|
|
#include <linux/err.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/regulator/driver.h>
|
|
#include <linux/regulator/of_regulator.h>
|
|
#include <linux/regulator/s2mpb01.h>
|
|
|
|
struct voltage_map_desc {
|
|
int min;
|
|
int max;
|
|
int step;
|
|
unsigned int n_bits;
|
|
};
|
|
|
|
static struct voltage_map_desc ldo_nmos_type1_voltage_map_desc = {
|
|
.min = 800000, .max = 1500000, .step = 25000, .n_bits = 6,
|
|
};
|
|
|
|
static struct voltage_map_desc ldo_nmos_type2_voltage_map_desc = {
|
|
.min = 600000, .max = 1500000, .step = 25000, .n_bits = 6,
|
|
};
|
|
|
|
static struct voltage_map_desc ldo_pmos_type1_voltage_map_desc = {
|
|
.min = 800000, .max = 2375000, .step = 25000, .n_bits = 6,
|
|
};
|
|
|
|
static struct voltage_map_desc ldo_pmos_type2_voltage_map_desc = {
|
|
.min = 1800000, .max = 3375000, .step = 25000, .n_bits = 6,
|
|
};
|
|
|
|
static struct voltage_map_desc buck_voltage_map_desc = {
|
|
.min = 400000, .max = 1500000, .step = 6250, .n_bits = 8,
|
|
};
|
|
|
|
static struct voltage_map_desc buck_boost_voltage_map_desc = {
|
|
.min = 2600000, .max = 4000000, .step = 12500, .n_bits = 7,
|
|
};
|
|
|
|
static struct voltage_map_desc *reg_voltage_map[] = {
|
|
[S2MPB01_LDO1] = &ldo_nmos_type1_voltage_map_desc,
|
|
[S2MPB01_LDO2] = &ldo_nmos_type2_voltage_map_desc,
|
|
[S2MPB01_LDO3] = &ldo_nmos_type2_voltage_map_desc,
|
|
[S2MPB01_LDO4] = &ldo_pmos_type1_voltage_map_desc,
|
|
[S2MPB01_LDO5] = &ldo_pmos_type1_voltage_map_desc,
|
|
[S2MPB01_LDO6] = &ldo_pmos_type1_voltage_map_desc,
|
|
[S2MPB01_LDO7] = &ldo_pmos_type1_voltage_map_desc,
|
|
[S2MPB01_LDO8] = &ldo_pmos_type1_voltage_map_desc,
|
|
[S2MPB01_LDO9] = &ldo_pmos_type1_voltage_map_desc,
|
|
[S2MPB01_LDO10] = &ldo_pmos_type2_voltage_map_desc,
|
|
[S2MPB01_LDO11] = &ldo_pmos_type2_voltage_map_desc,
|
|
[S2MPB01_LDO12] = &ldo_pmos_type2_voltage_map_desc,
|
|
[S2MPB01_LDO13] = &ldo_pmos_type2_voltage_map_desc,
|
|
[S2MPB01_LDO14] = &ldo_pmos_type2_voltage_map_desc,
|
|
[S2MPB01_LDO15] = &ldo_pmos_type2_voltage_map_desc,
|
|
[S2MPB01_BUCK1] = &buck_voltage_map_desc,
|
|
[S2MPB01_BUCK2] = &buck_boost_voltage_map_desc,
|
|
};
|
|
|
|
int s2mpb01_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest)
|
|
{
|
|
struct s2mpb01_dev *s2mpb01 = i2c_get_clientdata(i2c);
|
|
int ret;
|
|
|
|
mutex_lock(&s2mpb01->io_lock);
|
|
ret = i2c_smbus_read_byte_data(i2c, reg);
|
|
mutex_unlock(&s2mpb01->io_lock);
|
|
|
|
if (ret < 0) {
|
|
pr_err("%s: client=%d, reg=%d, ret=%d\n",
|
|
__func__, i2c->addr, reg, ret);
|
|
return ret;
|
|
}
|
|
|
|
ret &= 0xff;
|
|
*dest = ret;
|
|
return 0;
|
|
}
|
|
|
|
int s2mpb01_bulk_read(struct i2c_client *i2c, u8 reg, int count, u8 *buf)
|
|
{
|
|
struct s2mpb01_dev *s2mpb01 = i2c_get_clientdata(i2c);
|
|
int ret;
|
|
|
|
mutex_lock(&s2mpb01->io_lock);
|
|
ret = i2c_smbus_read_i2c_block_data(i2c, reg, count, buf);
|
|
mutex_unlock(&s2mpb01->io_lock);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int s2mpb01_write_reg(struct i2c_client *i2c, u8 reg, u8 value)
|
|
{
|
|
struct s2mpb01_dev *s2mpb01 = i2c_get_clientdata(i2c);
|
|
int ret;
|
|
|
|
mutex_lock(&s2mpb01->io_lock);
|
|
ret = i2c_smbus_write_byte_data(i2c, reg, value);
|
|
mutex_unlock(&s2mpb01->io_lock);
|
|
return ret;
|
|
}
|
|
|
|
int s2mpb01_bulk_write(struct i2c_client *i2c, u8 reg, int count, u8 *buf)
|
|
{
|
|
struct s2mpb01_dev *s2mpb01 = i2c_get_clientdata(i2c);
|
|
int ret;
|
|
|
|
mutex_lock(&s2mpb01->io_lock);
|
|
ret = i2c_smbus_write_i2c_block_data(i2c, reg, count, buf);
|
|
mutex_unlock(&s2mpb01->io_lock);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int s2mpb01_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask)
|
|
{
|
|
struct s2mpb01_dev *s2mpb01 = i2c_get_clientdata(i2c);
|
|
int ret;
|
|
|
|
mutex_lock(&s2mpb01->io_lock);
|
|
ret = i2c_smbus_read_byte_data(i2c, reg);
|
|
if (ret >= 0) {
|
|
u8 old_val = ret & 0xff;
|
|
u8 new_val = (val & mask) | (old_val & (~mask));
|
|
ret = i2c_smbus_write_byte_data(i2c, reg, new_val);
|
|
}
|
|
mutex_unlock(&s2mpb01->io_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int s2mpb01_get_en_register(struct regulator_dev *rdev,
|
|
int *reg, int *shift, int *mask)
|
|
{
|
|
int rid = rdev_get_id(rdev);
|
|
|
|
switch (rid) {
|
|
case S2MPB01_LDO1 ... S2MPB01_LDO15:
|
|
*reg = S2MPB01_REG_LDO1_CTRL + (rid - S2MPB01_LDO1);
|
|
*shift = 6;
|
|
*mask = 0x03;
|
|
break;
|
|
|
|
case S2MPB01_BUCK1:
|
|
*reg = S2MPB01_REG_BUCK_CTRL;
|
|
*shift = 6;
|
|
*mask = 0x03;
|
|
break;
|
|
|
|
case S2MPB01_BUCK2:
|
|
*reg = S2MPB01_REG_BB_CTRL;
|
|
*shift = 6;
|
|
*mask = 0x03;
|
|
break;
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int s2mpb01_get_voltage_register(struct regulator_dev *rdev,
|
|
int *reg, int *shift, int *mask)
|
|
{
|
|
int rid = rdev_get_id(rdev);
|
|
|
|
switch (rid) {
|
|
case S2MPB01_LDO1 ... S2MPB01_LDO15:
|
|
*reg = S2MPB01_REG_LDO1_CTRL + (rid - S2MPB01_LDO1);
|
|
*shift = 0;
|
|
*mask = 0x3F;
|
|
break;
|
|
|
|
case S2MPB01_BUCK1:
|
|
*reg = S2MPB01_REG_BUCK_OUT;
|
|
*shift = 0;
|
|
*mask = 0xFF;
|
|
break;
|
|
|
|
case S2MPB01_BUCK2:
|
|
*reg = S2MPB01_REG_BB_OUT;
|
|
*shift = 0;
|
|
*mask = 0x7F;
|
|
break;
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
pr_info("%s: id=%d, reg=0x%x, shift=0x%x, mask=0x%x\n",
|
|
__func__, rid, *reg, *shift, *mask);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int s2mpb01_ldo_is_enabled(struct regulator_dev *rdev)
|
|
{
|
|
struct s2mpb01_dev *s2mpb01 = rdev_get_drvdata(rdev);
|
|
int ret = 0, reg = 0, shift = 0, mask = 0;
|
|
u8 val;
|
|
|
|
ret = s2mpb01_get_en_register(rdev, ®, &shift, &mask);
|
|
if (ret) {
|
|
pr_err("%s: id=%d, error=%d\n", __func__,
|
|
rdev_get_id(rdev), ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = s2mpb01_read_reg(s2mpb01->i2c, reg, &val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
pr_debug("%s: id=%d, val=0x%x, enval=0x%x\n",
|
|
__func__, rdev_get_id(rdev), val, ((val >> shift) == 0x02));
|
|
|
|
return ((val >> shift) == 0x02);
|
|
}
|
|
|
|
static int s2mpb01_ldo_enable(struct regulator_dev *rdev)
|
|
{
|
|
struct s2mpb01_dev *s2mpb01 = rdev_get_drvdata(rdev);
|
|
int ret = 0, reg = 0, shift = 0, mask = 0;
|
|
|
|
ret = s2mpb01_get_en_register(rdev, ®, &shift, &mask);
|
|
if (ret) {
|
|
pr_err("%s: id=%d, error=%d\n", __func__,
|
|
rdev_get_id(rdev), ret);
|
|
return ret;
|
|
}
|
|
|
|
pr_debug("%s: id=%d, reg=0x%x, shift=0x%x, mask=0x%x\n",
|
|
__func__, rdev_get_id(rdev), reg, shift, mask);
|
|
|
|
return s2mpb01_update_reg(s2mpb01->i2c, reg,
|
|
(0x02 << shift), (mask << shift));
|
|
}
|
|
|
|
static int s2mpb01_ldo_disable(struct regulator_dev *rdev)
|
|
{
|
|
struct s2mpb01_dev *s2mpb01 = rdev_get_drvdata(rdev);
|
|
int ret = 0, reg = 0, shift = 0, mask = 0;
|
|
|
|
ret = s2mpb01_get_en_register(rdev, ®, &shift, &mask);
|
|
if (ret) {
|
|
pr_err("%s: id=%d, error=%d\n", __func__,
|
|
rdev_get_id(rdev), ret);
|
|
return ret;
|
|
}
|
|
|
|
pr_debug("%s: id=%d, reg=0x%x, shift=0x%x, mask=0x%x\n",
|
|
__func__, rdev_get_id(rdev), reg, shift, mask);
|
|
|
|
return s2mpb01_update_reg(s2mpb01->i2c, reg,
|
|
((~mask) << shift), (mask << shift));
|
|
}
|
|
|
|
static int s2mpb01_ldo_list_voltage(struct regulator_dev *rdev,
|
|
unsigned int selector)
|
|
{
|
|
const struct voltage_map_desc *desc;
|
|
int rid = rdev_get_id(rdev);
|
|
int val;
|
|
|
|
if (rid >= ARRAY_SIZE(reg_voltage_map) ||
|
|
rid < 0)
|
|
return -EINVAL;
|
|
|
|
desc = reg_voltage_map[rid];
|
|
if (desc == NULL)
|
|
return -EINVAL;
|
|
|
|
val = desc->min + desc->step * selector;
|
|
if (val > desc->max)
|
|
return -EINVAL;
|
|
|
|
pr_debug("%s: id=%d, val=%d\n", __func__, rdev_get_id(rdev), val);
|
|
|
|
return val;
|
|
}
|
|
|
|
static int s2mpb01_ldo_get_voltage(struct regulator_dev *rdev)
|
|
{
|
|
struct s2mpb01_dev *s2mpb01 = rdev_get_drvdata(rdev);
|
|
int reg, shift, mask, ret;
|
|
u8 val;
|
|
|
|
ret = s2mpb01_get_voltage_register(rdev, ®, &shift, &mask);
|
|
if (ret) {
|
|
pr_err("%s: id=%d, error=%d\n", __func__,
|
|
rdev_get_id(rdev), ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = s2mpb01_read_reg(s2mpb01->i2c, reg, &val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
val = (val & mask) >> shift;
|
|
|
|
pr_debug("%s: id=%d, reg=0x%x, mask=0x%x, val=0x%x\n",
|
|
__func__, rdev_get_id(rdev), reg, mask, val);
|
|
|
|
return s2mpb01_ldo_list_voltage(rdev, val);
|
|
}
|
|
|
|
static inline int s2mpb01_get_voltage_proper_val(
|
|
const struct voltage_map_desc *desc,
|
|
int min_vol, int max_vol)
|
|
{
|
|
int i = 0;
|
|
|
|
if (desc == NULL)
|
|
return -EINVAL;
|
|
|
|
if (max_vol < desc->min || min_vol > desc->max)
|
|
return -EINVAL;
|
|
|
|
while (desc->min + desc->step * i < min_vol &&
|
|
desc->min + desc->step * i < desc->max)
|
|
i++;
|
|
|
|
if (desc->min + desc->step * i > max_vol)
|
|
return -EINVAL;
|
|
|
|
if (i >= (1 << desc->n_bits))
|
|
return -EINVAL;
|
|
|
|
return i;
|
|
}
|
|
|
|
static int s2mpb01_ldo_set_voltage(struct regulator_dev *rdev,
|
|
int min_uV, int max_uV, unsigned *selector)
|
|
{
|
|
struct s2mpb01_dev *s2mpb01 = rdev_get_drvdata(rdev);
|
|
const struct voltage_map_desc *desc;
|
|
int rid = rdev_get_id(rdev);
|
|
int reg, shift, mask, ret;
|
|
int i;
|
|
u8 val;
|
|
|
|
desc = reg_voltage_map[rid];
|
|
|
|
i = s2mpb01_get_voltage_proper_val(desc, min_uV, max_uV);
|
|
if (i < 0)
|
|
return i;
|
|
|
|
ret = s2mpb01_get_voltage_register(rdev, ®, &shift, &mask);
|
|
if (ret) {
|
|
pr_err("%s: id=%d, error=%d\n", __func__,
|
|
rdev_get_id(rdev), ret);
|
|
return ret;
|
|
}
|
|
|
|
s2mpb01_read_reg(s2mpb01->i2c, reg, &val);
|
|
val = (val & mask) >> shift;
|
|
|
|
pr_info("%s: id=%d, reg=0x%x, mask=0x%x, val=0x%x, new=0x%x\n",
|
|
__func__, rdev_get_id(rdev), reg, mask, val, i);
|
|
|
|
ret = s2mpb01_update_reg(s2mpb01->i2c, reg, i << shift, mask << shift);
|
|
*selector = i;
|
|
|
|
switch (rid) {
|
|
case S2MPB01_LDO1:
|
|
case S2MPB01_LDO3:
|
|
case S2MPB01_LDO10:
|
|
case S2MPB01_LDO12:
|
|
case S2MPB01_LDO14:
|
|
case S2MPB01_BUCK1:
|
|
if (val < i)
|
|
udelay(DIV_ROUND_UP(desc->step * (i - val), 12500));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct regulator_ops s2mpb01_ldo_ops = {
|
|
.is_enabled = s2mpb01_ldo_is_enabled,
|
|
.enable = s2mpb01_ldo_enable,
|
|
.disable = s2mpb01_ldo_disable,
|
|
.list_voltage = s2mpb01_ldo_list_voltage,
|
|
.get_voltage = s2mpb01_ldo_get_voltage,
|
|
.set_voltage = s2mpb01_ldo_set_voltage,
|
|
};
|
|
|
|
#define regulator_desc_ldo(num) { \
|
|
.name = "LDO"#num, \
|
|
.id = S2MPB01_LDO##num, \
|
|
.ops = &s2mpb01_ldo_ops, \
|
|
.type = REGULATOR_VOLTAGE, \
|
|
.owner = THIS_MODULE, \
|
|
}
|
|
#define regulator_desc_buck(num) { \
|
|
.name = "BUCK"#num, \
|
|
.id = S2MPB01_BUCK##num, \
|
|
.ops = &s2mpb01_ldo_ops, \
|
|
.type = REGULATOR_VOLTAGE, \
|
|
.owner = THIS_MODULE, \
|
|
}
|
|
|
|
static struct regulator_desc regulators[] = {
|
|
regulator_desc_ldo(1),
|
|
regulator_desc_ldo(2),
|
|
regulator_desc_ldo(3),
|
|
regulator_desc_ldo(4),
|
|
regulator_desc_ldo(5),
|
|
regulator_desc_ldo(6),
|
|
regulator_desc_ldo(7),
|
|
regulator_desc_ldo(8),
|
|
regulator_desc_ldo(9),
|
|
regulator_desc_ldo(10),
|
|
regulator_desc_ldo(11),
|
|
regulator_desc_ldo(12),
|
|
regulator_desc_ldo(13),
|
|
regulator_desc_ldo(14),
|
|
regulator_desc_ldo(15),
|
|
regulator_desc_buck(1),
|
|
regulator_desc_buck(2),
|
|
};
|
|
|
|
#ifdef CONFIG_OF
|
|
static int s2mpb01_pmic_dt_parse_pdata(struct s2mpb01_dev *s2mpb01,
|
|
struct s2mpb01_platform_data *pdata)
|
|
{
|
|
|
|
struct device_node *pmic_np, *regulators_np, *reg_np;
|
|
struct s2mpb01_regulator_subdev *rdata;
|
|
unsigned int i;
|
|
|
|
pmic_np = s2mpb01->dev->of_node;
|
|
if (!pmic_np) {
|
|
dev_err(s2mpb01->dev, "could not find pmic sub-node\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
regulators_np = of_find_node_by_name(pmic_np, "regulators");
|
|
if (!regulators_np) {
|
|
dev_err(s2mpb01->dev, "could not find regulators sub-node\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* count the number of regulators to be supported in pmic */
|
|
pdata->num_regulators = 0;
|
|
for_each_child_of_node(regulators_np, reg_np) {
|
|
pdata->num_regulators++;
|
|
}
|
|
|
|
rdata = devm_kzalloc(s2mpb01->dev, sizeof(*rdata) *
|
|
pdata->num_regulators, GFP_KERNEL);
|
|
if (!rdata) {
|
|
dev_err(s2mpb01->dev,
|
|
"could not allocate memory for regulator data\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
pdata->regulators = rdata;
|
|
for_each_child_of_node(regulators_np, reg_np) {
|
|
for (i = 0; i < ARRAY_SIZE(regulators); i++)
|
|
if (!of_node_cmp(reg_np->name,
|
|
regulators[i].name))
|
|
break;
|
|
|
|
if (i == ARRAY_SIZE(regulators)) {
|
|
dev_warn(s2mpb01->dev,
|
|
"don't know how to configure regulator %s\n",
|
|
reg_np->name);
|
|
continue;
|
|
}
|
|
|
|
rdata->id = i;
|
|
rdata->initdata = of_get_regulator_init_data(
|
|
s2mpb01->dev, reg_np);
|
|
rdata->reg_node = reg_np;
|
|
rdata++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#else
|
|
static int s2mpb01_pmic_dt_parse_pdata(struct s2mpb01_dev *s2mpb01,
|
|
struct s2mpb01_platform_data *pdata)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_OF */
|
|
|
|
static int s2mpb01_setup_regulators(struct s2mpb01_dev *s2mpb01,
|
|
struct s2mpb01_platform_data *pdata)
|
|
{
|
|
int i, err, ret;
|
|
struct regulator_config config = { };
|
|
|
|
|
|
#ifdef CONFIG_OF
|
|
if (s2mpb01->dev->of_node) {
|
|
ret = s2mpb01_pmic_dt_parse_pdata(s2mpb01, pdata);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
s2mpb01->num_regulators = pdata->num_regulators;
|
|
s2mpb01->rdev = kcalloc(pdata->num_regulators,
|
|
sizeof(struct regulator_dev *), GFP_KERNEL);
|
|
if (!s2mpb01->rdev) {
|
|
err = -ENOMEM;
|
|
goto err_nomem;
|
|
}
|
|
|
|
/* Register the regulators */
|
|
for (i = 0; i < s2mpb01->num_regulators; i++) {
|
|
struct s2mpb01_regulator_subdev *reg = &pdata->regulators[i];
|
|
|
|
config.dev = s2mpb01->dev;
|
|
config.init_data = reg->initdata;
|
|
config.driver_data = s2mpb01;
|
|
config.of_node = reg->reg_node;
|
|
|
|
s2mpb01->rdev[i] =
|
|
regulator_register(®ulators[reg->id], &config);
|
|
if (IS_ERR(s2mpb01->rdev[i])) {
|
|
err = PTR_ERR(s2mpb01->rdev[i]);
|
|
dev_err(s2mpb01->dev, "regulator init failed: %d\n",
|
|
err);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
error:
|
|
while (--i >= 0)
|
|
regulator_unregister(s2mpb01->rdev[i]);
|
|
|
|
kfree(s2mpb01->rdev);
|
|
s2mpb01->rdev = NULL;
|
|
|
|
err_nomem:
|
|
return err;
|
|
}
|
|
|
|
#ifdef CONFIG_OF
|
|
static int s2mpb01_pmic_i2c_dt_parse_pdata(struct i2c_client *i2c,
|
|
struct s2mpb01_platform_data *pdata)
|
|
{
|
|
return 0;
|
|
}
|
|
#else
|
|
static int s2mpb01_pmic_i2c_dt_parse_pdata(struct i2c_client *i2c,
|
|
struct s2mpb01_platform_data *pdata)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_OF */
|
|
|
|
static int s2mpb01_i2c_probe(struct i2c_client *i2c,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct s2mpb01_dev *s2mpb01;
|
|
struct s2mpb01_platform_data *pdata;
|
|
int ret;
|
|
|
|
pr_info("%s\n", __func__);
|
|
|
|
/*Check I2C functionality */
|
|
ret = i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C);
|
|
if (!ret) {
|
|
printk(KERN_ERR "%s: No I2C functionality found\n", __func__);
|
|
ret = -ENODEV;
|
|
goto err_i2c_fail;
|
|
}
|
|
|
|
if (i2c->dev.of_node) {
|
|
pr_info("%s: of_node\n", __func__);
|
|
|
|
pdata = devm_kzalloc(&i2c->dev,
|
|
sizeof(struct s2mpb01_platform_data), GFP_KERNEL);
|
|
if (!pdata) {
|
|
dev_err(&i2c->dev,
|
|
"%s: Failed to allocate memory\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ret = s2mpb01_pmic_i2c_dt_parse_pdata(i2c, pdata);
|
|
if (ret)
|
|
return ret;
|
|
} else {
|
|
pr_info("%s: pdata\n", __func__);
|
|
|
|
pdata = i2c->dev.platform_data;
|
|
if (!pdata) {
|
|
dev_err(&i2c->dev, "%s: no pdata\n", __func__);
|
|
ret = -ENODEV;
|
|
goto err_i2c_fail;
|
|
}
|
|
}
|
|
|
|
s2mpb01 = kzalloc(sizeof(struct s2mpb01_dev), GFP_KERNEL);
|
|
if (!s2mpb01)
|
|
return -ENOMEM;
|
|
|
|
s2mpb01->i2c = i2c;
|
|
s2mpb01->dev = &i2c->dev;
|
|
|
|
mutex_init(&s2mpb01->io_lock);
|
|
|
|
i2c_set_clientdata(i2c, s2mpb01);
|
|
|
|
{
|
|
u8 reg = 0;
|
|
ret = s2mpb01_read_reg(s2mpb01->i2c, S2MPB01_REG_PMIC_ID, ®);
|
|
if (ret) {
|
|
pr_err("%s: device cannot detect\n", __func__);
|
|
goto err_detect;
|
|
}
|
|
pr_info("%s: PMIC ID: 0x%x\n", __func__, reg);
|
|
|
|
}
|
|
|
|
ret = s2mpb01_setup_regulators(s2mpb01, pdata);
|
|
if (ret < 0)
|
|
goto err_detect;
|
|
|
|
return 0;
|
|
|
|
err_detect:
|
|
kfree(s2mpb01);
|
|
err_i2c_fail:
|
|
return ret;
|
|
}
|
|
|
|
static int s2mpb01_i2c_remove(struct i2c_client *i2c)
|
|
{
|
|
struct s2mpb01_dev *s2mpb01 = i2c_get_clientdata(i2c);
|
|
int i;
|
|
|
|
for (i = 0; i < s2mpb01->num_regulators; i++)
|
|
regulator_unregister(s2mpb01->rdev[i]);
|
|
kfree(s2mpb01->rdev);
|
|
kfree(s2mpb01);
|
|
return 0;
|
|
}
|
|
|
|
static const struct i2c_device_id s2mpb01_i2c_id[] = {
|
|
{ "s2mpb01", 0 },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, s2mpb01_i2c_id);
|
|
|
|
#ifdef CONFIG_OF
|
|
static struct of_device_id s2mpb01_match_table[] = {
|
|
{ .compatible = "s2mpb01,s2mpb01-regulator",},
|
|
{ },
|
|
};
|
|
#endif
|
|
|
|
static struct i2c_driver s2mpb01_i2c_driver = {
|
|
.driver = {
|
|
.name = "s2mpb01",
|
|
.owner = THIS_MODULE,
|
|
#ifdef CONFIG_OF
|
|
.of_match_table = s2mpb01_match_table,
|
|
#endif
|
|
},
|
|
.probe = s2mpb01_i2c_probe,
|
|
.remove = s2mpb01_i2c_remove,
|
|
.id_table = s2mpb01_i2c_id,
|
|
};
|
|
|
|
static int __init s2mpb01_module_init(void)
|
|
{
|
|
pr_info("%s\n", __func__);
|
|
|
|
return i2c_add_driver(&s2mpb01_i2c_driver);
|
|
}
|
|
subsys_initcall(s2mpb01_module_init);
|
|
|
|
static void __exit s2mpb01_module_exit(void)
|
|
{
|
|
i2c_del_driver(&s2mpb01_i2c_driver);
|
|
}
|
|
module_exit(s2mpb01_module_exit);
|
|
|
|
MODULE_DESCRIPTION("S.LSI S2MPB01 Regulator Driver");
|
|
MODULE_AUTHOR("SangYoung Son <hello.son@samsung.com>");
|
|
MODULE_LICENSE("GPL");
|