root/drivers/soc/mediatek/mtk-regulator-coupler.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Voltage regulators coupler for MediaTek SoCs
 *
 * Copyright (C) 2022 Collabora, Ltd.
 * Author: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/regulator/coupler.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/suspend.h>

#define to_mediatek_coupler(x)  container_of(x, struct mediatek_regulator_coupler, coupler)

struct mediatek_regulator_coupler {
        struct regulator_coupler coupler;
        struct regulator_dev *vsram_rdev;
};

/*
 * We currently support only couples of not more than two vregs and
 * modify the vsram voltage only when changing voltage of vgpu.
 *
 * This function is limited to the GPU<->SRAM voltages relationships.
 */
static int mediatek_regulator_balance_voltage(struct regulator_coupler *coupler,
                                              struct regulator_dev *rdev,
                                              suspend_state_t state)
{
        struct mediatek_regulator_coupler *mrc = to_mediatek_coupler(coupler);
        int max_spread = rdev->constraints->max_spread[0];
        int vsram_min_uV = mrc->vsram_rdev->constraints->min_uV;
        int vsram_max_uV = mrc->vsram_rdev->constraints->max_uV;
        int vsram_target_min_uV, vsram_target_max_uV;
        int min_uV = 0;
        int max_uV = INT_MAX;
        int ret;

        /*
         * If the target device is on, setting the SRAM voltage directly
         * is not supported as it scales through its coupled supply voltage.
         *
         * An exception is made in case the use_count is zero: this means
         * that this is the first time we power up the SRAM regulator, which
         * implies that the target device has yet to perform initialization
         * and setting a voltage at that time is harmless.
         */
        if (rdev == mrc->vsram_rdev) {
                if (rdev->use_count == 0)
                        return regulator_do_balance_voltage(rdev, state, true);

                return -EPERM;
        }

        ret = regulator_check_consumers(rdev, &min_uV, &max_uV, state);
        if (ret < 0)
                return ret;

        if (min_uV == 0) {
                ret = regulator_get_voltage_rdev(rdev);
                if (ret < 0)
                        return ret;
                min_uV = ret;
        }

        ret = regulator_check_voltage(rdev, &min_uV, &max_uV);
        if (ret < 0)
                return ret;

        /*
         * If we're asked to set a voltage less than VSRAM min_uV, set
         * the minimum allowed voltage on VSRAM, as in this case it is
         * safe to ignore the max_spread parameter.
         */
        vsram_target_min_uV = max(vsram_min_uV, min_uV + max_spread);
        vsram_target_max_uV = min(vsram_max_uV, vsram_target_min_uV + max_spread);

        /* Make sure we're not out of range */
        vsram_target_min_uV = min(vsram_target_min_uV, vsram_max_uV);

        pr_debug("Setting voltage %d-%duV on %s (minuV %d)\n",
                 vsram_target_min_uV, vsram_target_max_uV,
                 rdev_get_name(mrc->vsram_rdev), min_uV);

        ret = regulator_set_voltage_rdev(mrc->vsram_rdev, vsram_target_min_uV,
                                         vsram_target_max_uV, state);
        if (ret)
                return ret;

        /* The sram voltage is now balanced: update the target vreg voltage */
        return regulator_do_balance_voltage(rdev, state, true);
}

static int mediatek_regulator_attach(struct regulator_coupler *coupler,
                                     struct regulator_dev *rdev)
{
        struct mediatek_regulator_coupler *mrc = to_mediatek_coupler(coupler);
        const char *rdev_name = rdev_get_name(rdev);

        /*
         * If we're getting a coupling of more than two regulators here and
         * this means that this is surely not a GPU<->SRAM couple: in that
         * case, we may want to use another coupler implementation, if any,
         * or the generic one: the regulator core will keep walking through
         * the list of couplers when any .attach_regulator() cb returns 1.
         */
        if (rdev->coupling_desc.n_coupled > 2)
                return 1;

        if (strstr(rdev_name, "sram")) {
                if (mrc->vsram_rdev)
                        return -EINVAL;
                mrc->vsram_rdev = rdev;
        } else if (!strstr(rdev_name, "vgpu") && !strstr(rdev_name, "Vgpu")) {
                return 1;
        }

        return 0;
}

static int mediatek_regulator_detach(struct regulator_coupler *coupler,
                                     struct regulator_dev *rdev)
{
        struct mediatek_regulator_coupler *mrc = to_mediatek_coupler(coupler);

        if (rdev == mrc->vsram_rdev)
                mrc->vsram_rdev = NULL;

        return 0;
}

static struct mediatek_regulator_coupler mediatek_coupler = {
        .coupler = {
                .attach_regulator = mediatek_regulator_attach,
                .detach_regulator = mediatek_regulator_detach,
                .balance_voltage = mediatek_regulator_balance_voltage,
        },
};

static int mediatek_regulator_coupler_init(void)
{
        if (!of_machine_is_compatible("mediatek,mt8183") &&
            !of_machine_is_compatible("mediatek,mt8186") &&
            !of_machine_is_compatible("mediatek,mt8188") &&
            !of_machine_is_compatible("mediatek,mt8192"))
                return 0;

        return regulator_coupler_register(&mediatek_coupler.coupler);
}
arch_initcall(mediatek_regulator_coupler_init);

MODULE_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>");
MODULE_DESCRIPTION("MediaTek Regulator Coupler driver");
MODULE_LICENSE("GPL");