#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/of_regulator.h>
#include <linux/gpio/consumer.h>
#define SGM3804_POS_RAIL_VOLTAGE_REG 0x0
#define SGM3804_NEG_RAIL_VOLTAGE_REG 0x1
#define SGM3804_RAIL_DISCHARGE_REG 0x3
#define RAIL_VOLTAGE_MASK GENMASK(5, 0)
#define POS_RAIL_DISCHARGE_EN BIT(1)
#define NEG_RAIL_DISCHARGE_EN BIT(0)
#define RAIL_VOLTAGE_INVALID RAIL_VOLTAGE_MASK
#define RAIL_DISCHARGE_REG_DEFAULT (POS_RAIL_DISCHARGE_EN | NEG_RAIL_DISCHARGE_EN)
#define SGM3804_VOLTAGES_MAX_SELECTOR 0x2f
enum {
SGM3804_POS_RAIL = 0,
SGM3804_NEG_RAIL,
SGM3804_RAIL_COUNT,
};
struct sgm3804_data {
struct regmap *regmap;
struct mutex lock;
struct gpio_desc *gpios[SGM3804_RAIL_COUNT];
};
static const struct linear_range sgm3804_voltages[] = {
REGULATOR_LINEAR_RANGE(2400000, 0x20, 0x2f, 100000),
REGULATOR_LINEAR_RANGE(4000000, 0x00, 0x17, 100000),
};
static const struct reg_default sgm3804_reg_defaults[] = {
{ SGM3804_POS_RAIL_VOLTAGE_REG, RAIL_VOLTAGE_INVALID },
{ SGM3804_NEG_RAIL_VOLTAGE_REG, RAIL_VOLTAGE_INVALID },
{ SGM3804_RAIL_DISCHARGE_REG, RAIL_DISCHARGE_REG_DEFAULT },
};
static bool sgm3804_writeable_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case SGM3804_POS_RAIL_VOLTAGE_REG:
case SGM3804_NEG_RAIL_VOLTAGE_REG:
case SGM3804_RAIL_DISCHARGE_REG:
return true;
default:
return false;
}
}
static bool sgm3804_readable_reg(struct device *dev, unsigned int reg)
{
return false;
}
static const struct regmap_config sgm3804_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = 0x03,
.writeable_reg = sgm3804_writeable_reg,
.readable_reg = sgm3804_readable_reg,
.cache_type = REGCACHE_MAPLE,
.reg_defaults = sgm3804_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(sgm3804_reg_defaults),
};
static int sgm3804_sync_regcache_state(struct sgm3804_data *ctx)
{
guard(mutex)(&ctx->lock);
if (!gpiod_get_value_cansleep(ctx->gpios[SGM3804_POS_RAIL]) &&
!gpiod_get_value_cansleep(ctx->gpios[SGM3804_NEG_RAIL])) {
regcache_cache_only(ctx->regmap, true);
regcache_mark_dirty(ctx->regmap);
} else {
int ret;
regcache_cache_only(ctx->regmap, false);
ret = regcache_sync(ctx->regmap);
if (ret) {
regcache_cache_only(ctx->regmap, true);
return ret;
}
}
return 0;
}
static int sgm3804_get_voltage_sel(struct regulator_dev *rdev)
{
int ret;
ret = regulator_get_voltage_sel_regmap(rdev);
if (ret < 0)
return ret;
if (ret == RAIL_VOLTAGE_INVALID)
return -ENOTRECOVERABLE;
return ret;
}
static int sgm3804_enable(struct regulator_dev *rdev)
{
struct sgm3804_data *ctx = rdev->reg_data;
int ret;
ret = gpiod_set_value_cansleep(ctx->gpios[rdev_get_id(rdev)], 1);
if (ret)
return ret;
ret = sgm3804_sync_regcache_state(ctx);
if (ret)
goto err;
return 0;
err:
gpiod_set_value_cansleep(ctx->gpios[rdev_get_id(rdev)], 0);
return ret;
}
static int sgm3804_disable(struct regulator_dev *rdev)
{
struct sgm3804_data *ctx = rdev->reg_data;
int ret;
ret = gpiod_set_value_cansleep(ctx->gpios[rdev_get_id(rdev)], 0);
if (ret)
return ret;
return sgm3804_sync_regcache_state(ctx);
}
static int sgm3804_is_enabled(struct regulator_dev *rdev)
{
struct sgm3804_data *ctx = rdev->reg_data;
return gpiod_get_value_cansleep(ctx->gpios[rdev_get_id(rdev)]);
}
static const struct regulator_ops sgm3804_ops = {
.list_voltage = regulator_list_voltage_linear_range,
.map_voltage = regulator_map_voltage_linear_range,
.set_voltage_sel = regulator_set_voltage_sel_regmap,
.get_voltage_sel = sgm3804_get_voltage_sel,
.set_active_discharge = regulator_set_active_discharge_regmap,
.enable = sgm3804_enable,
.disable = sgm3804_disable,
.is_enabled = sgm3804_is_enabled,
};
static const struct regulator_desc sgm3804_regulator_desc[] = {
{
.name = "pos",
.of_match = "pos",
.supply_name = "vin",
.id = SGM3804_POS_RAIL,
.ops = &sgm3804_ops,
.type = REGULATOR_VOLTAGE,
.linear_ranges = sgm3804_voltages,
.n_linear_ranges = ARRAY_SIZE(sgm3804_voltages),
.n_voltages = SGM3804_VOLTAGES_MAX_SELECTOR + 1,
.vsel_reg = SGM3804_POS_RAIL_VOLTAGE_REG,
.vsel_mask = RAIL_VOLTAGE_MASK,
.active_discharge_on = POS_RAIL_DISCHARGE_EN,
.active_discharge_mask = POS_RAIL_DISCHARGE_EN,
.active_discharge_reg = SGM3804_RAIL_DISCHARGE_REG,
.enable_time = 40000,
.owner = THIS_MODULE,
},
{
.name = "neg",
.of_match = "neg",
.supply_name = "vin",
.id = SGM3804_NEG_RAIL,
.ops = &sgm3804_ops,
.type = REGULATOR_VOLTAGE,
.linear_ranges = sgm3804_voltages,
.n_linear_ranges = ARRAY_SIZE(sgm3804_voltages),
.n_voltages = SGM3804_VOLTAGES_MAX_SELECTOR + 1,
.vsel_reg = SGM3804_NEG_RAIL_VOLTAGE_REG,
.vsel_mask = RAIL_VOLTAGE_MASK,
.active_discharge_on = NEG_RAIL_DISCHARGE_EN,
.active_discharge_mask = NEG_RAIL_DISCHARGE_EN,
.active_discharge_reg = SGM3804_RAIL_DISCHARGE_REG,
.enable_time = 40000,
.owner = THIS_MODULE,
},
};
static int sgm3804_probe(struct i2c_client *i2c)
{
struct device *dev = &i2c->dev;
struct sgm3804_data *ctx;
int ret, i;
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
mutex_init(&ctx->lock);
ctx->regmap = devm_regmap_init_i2c(i2c, &sgm3804_regmap_config);
if (IS_ERR(ctx->regmap))
return dev_err_probe(dev, PTR_ERR(ctx->regmap),
"failed to init regmap\n");
for (i = 0; i < ARRAY_SIZE(sgm3804_regulator_desc); i++) {
const struct regulator_desc *reg = &sgm3804_regulator_desc[i];
struct fwnode_handle *child;
child = device_get_named_child_node(dev, reg->of_match);
if (!child) {
dev_err(dev, "missing child '%s'\n", reg->of_match);
return -EINVAL;
}
ctx->gpios[i] = devm_fwnode_gpiod_get(dev, child, "enable",
GPIOD_ASIS, reg->name);
fwnode_handle_put(child);
if (IS_ERR(ctx->gpios[i]))
return dev_err_probe(dev, PTR_ERR(ctx->gpios[i]),
"failed to get '%s' enable GPIO\n",
reg->name);
}
ret = sgm3804_sync_regcache_state(ctx);
if (ret)
return ret;
for (i = 0; i < ARRAY_SIZE(sgm3804_regulator_desc); i++) {
struct regulator_config config = { };
struct regulator_dev *rdev;
config.dev = dev;
config.regmap = ctx->regmap;
config.of_node = dev_of_node(dev);
config.driver_data = ctx;
rdev = devm_regulator_register(dev, &sgm3804_regulator_desc[i],
&config);
if (IS_ERR(rdev))
return dev_err_probe(dev, PTR_ERR(rdev),
"failed to register regulator %d\n", i);
}
return 0;
}
static const struct i2c_device_id sgm3804_id[] = {
{ "sgm3804" },
{ }
};
MODULE_DEVICE_TABLE(i2c, sgm3804_id);
static const struct of_device_id sgm3804_of_match[] = {
{ .compatible = "sgmicro,sgm3804" },
{ }
};
MODULE_DEVICE_TABLE(of, sgm3804_of_match);
static struct i2c_driver sgm3804_regulator_driver = {
.driver = {
.name = "sgm3804",
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
.of_match_table = sgm3804_of_match,
},
.probe = sgm3804_probe,
.id_table = sgm3804_id,
};
module_i2c_driver(sgm3804_regulator_driver);
MODULE_DESCRIPTION("SGMicro SGM3804 regulator Driver");
MODULE_AUTHOR("Kancy Joe <kancy2333@outlook.com>");
MODULE_AUTHOR("Neil Armstrong <neil.armstrong@linaro.org>");
MODULE_LICENSE("GPL");