root/drivers/regulator/sgm3804-regulator.c
// SPDX-License-Identifier: GPL-2.0-only
//
// SGMicro SGM3804 regulator Driver
//
// Copyright (C) 2025 Kancy Joe <kancy2333@outlook.com>
// Copyright (C) 2026 Linaro Limited
// Author: Neil Armstrong <neil.armstrong@linaro.org>

#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,
};

/*
 * The registers are only writable when the gpio is enabled, so
 * we need to use the cache for read operations and set the regmap
 * as cache_only when both GPIOs are down.
 */
struct sgm3804_data {
        struct regmap *regmap;
        /* Protects the regcache state update */
        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),
};

/*
 * The cache is populated with those hardware default values
 * so the regmap_update_bits operation will use the cached
 * value to build a new register value and write it when GPIOs
 * are enabled.
 */
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 },
};

/* Registers are only writable */
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;
        }
}

/*
 * Since all registers are only writeable, regmap will only read from the cache data.
 */
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 both GPIOs are down, IC is powered down and I2C writes will fail */
        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;

                /* At least a GPIO is up, we can write registers */
                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;

        /* Force setting a voltage on probe */
        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[] = {
        /* Positive Output */
        {
                .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,
        },
        /* Negative Output */
        {
                .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");

        /* Get enable GPIOs */
        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");