162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Voltage regulators coupler for MediaTek SoCs
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2022 Collabora, Ltd.
662306a36Sopenharmony_ci * Author: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <linux/init.h>
1262306a36Sopenharmony_ci#include <linux/kernel.h>
1362306a36Sopenharmony_ci#include <linux/of.h>
1462306a36Sopenharmony_ci#include <linux/regulator/coupler.h>
1562306a36Sopenharmony_ci#include <linux/regulator/driver.h>
1662306a36Sopenharmony_ci#include <linux/regulator/machine.h>
1762306a36Sopenharmony_ci#include <linux/suspend.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#define to_mediatek_coupler(x)	container_of(x, struct mediatek_regulator_coupler, coupler)
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_cistruct mediatek_regulator_coupler {
2262306a36Sopenharmony_ci	struct regulator_coupler coupler;
2362306a36Sopenharmony_ci	struct regulator_dev *vsram_rdev;
2462306a36Sopenharmony_ci};
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci/*
2762306a36Sopenharmony_ci * We currently support only couples of not more than two vregs and
2862306a36Sopenharmony_ci * modify the vsram voltage only when changing voltage of vgpu.
2962306a36Sopenharmony_ci *
3062306a36Sopenharmony_ci * This function is limited to the GPU<->SRAM voltages relationships.
3162306a36Sopenharmony_ci */
3262306a36Sopenharmony_cistatic int mediatek_regulator_balance_voltage(struct regulator_coupler *coupler,
3362306a36Sopenharmony_ci					      struct regulator_dev *rdev,
3462306a36Sopenharmony_ci					      suspend_state_t state)
3562306a36Sopenharmony_ci{
3662306a36Sopenharmony_ci	struct mediatek_regulator_coupler *mrc = to_mediatek_coupler(coupler);
3762306a36Sopenharmony_ci	int max_spread = rdev->constraints->max_spread[0];
3862306a36Sopenharmony_ci	int vsram_min_uV = mrc->vsram_rdev->constraints->min_uV;
3962306a36Sopenharmony_ci	int vsram_max_uV = mrc->vsram_rdev->constraints->max_uV;
4062306a36Sopenharmony_ci	int vsram_target_min_uV, vsram_target_max_uV;
4162306a36Sopenharmony_ci	int min_uV = 0;
4262306a36Sopenharmony_ci	int max_uV = INT_MAX;
4362306a36Sopenharmony_ci	int ret;
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	/*
4662306a36Sopenharmony_ci	 * If the target device is on, setting the SRAM voltage directly
4762306a36Sopenharmony_ci	 * is not supported as it scales through its coupled supply voltage.
4862306a36Sopenharmony_ci	 *
4962306a36Sopenharmony_ci	 * An exception is made in case the use_count is zero: this means
5062306a36Sopenharmony_ci	 * that this is the first time we power up the SRAM regulator, which
5162306a36Sopenharmony_ci	 * implies that the target device has yet to perform initialization
5262306a36Sopenharmony_ci	 * and setting a voltage at that time is harmless.
5362306a36Sopenharmony_ci	 */
5462306a36Sopenharmony_ci	if (rdev == mrc->vsram_rdev) {
5562306a36Sopenharmony_ci		if (rdev->use_count == 0)
5662306a36Sopenharmony_ci			return regulator_do_balance_voltage(rdev, state, true);
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci		return -EPERM;
5962306a36Sopenharmony_ci	}
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	ret = regulator_check_consumers(rdev, &min_uV, &max_uV, state);
6262306a36Sopenharmony_ci	if (ret < 0)
6362306a36Sopenharmony_ci		return ret;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	if (min_uV == 0) {
6662306a36Sopenharmony_ci		ret = regulator_get_voltage_rdev(rdev);
6762306a36Sopenharmony_ci		if (ret < 0)
6862306a36Sopenharmony_ci			return ret;
6962306a36Sopenharmony_ci		min_uV = ret;
7062306a36Sopenharmony_ci	}
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	ret = regulator_check_voltage(rdev, &min_uV, &max_uV);
7362306a36Sopenharmony_ci	if (ret < 0)
7462306a36Sopenharmony_ci		return ret;
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	/*
7762306a36Sopenharmony_ci	 * If we're asked to set a voltage less than VSRAM min_uV, set
7862306a36Sopenharmony_ci	 * the minimum allowed voltage on VSRAM, as in this case it is
7962306a36Sopenharmony_ci	 * safe to ignore the max_spread parameter.
8062306a36Sopenharmony_ci	 */
8162306a36Sopenharmony_ci	vsram_target_min_uV = max(vsram_min_uV, min_uV + max_spread);
8262306a36Sopenharmony_ci	vsram_target_max_uV = min(vsram_max_uV, vsram_target_min_uV + max_spread);
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	/* Make sure we're not out of range */
8562306a36Sopenharmony_ci	vsram_target_min_uV = min(vsram_target_min_uV, vsram_max_uV);
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	pr_debug("Setting voltage %d-%duV on %s (minuV %d)\n",
8862306a36Sopenharmony_ci		 vsram_target_min_uV, vsram_target_max_uV,
8962306a36Sopenharmony_ci		 rdev_get_name(mrc->vsram_rdev), min_uV);
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	ret = regulator_set_voltage_rdev(mrc->vsram_rdev, vsram_target_min_uV,
9262306a36Sopenharmony_ci					 vsram_target_max_uV, state);
9362306a36Sopenharmony_ci	if (ret)
9462306a36Sopenharmony_ci		return ret;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	/* The sram voltage is now balanced: update the target vreg voltage */
9762306a36Sopenharmony_ci	return regulator_do_balance_voltage(rdev, state, true);
9862306a36Sopenharmony_ci}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_cistatic int mediatek_regulator_attach(struct regulator_coupler *coupler,
10162306a36Sopenharmony_ci				     struct regulator_dev *rdev)
10262306a36Sopenharmony_ci{
10362306a36Sopenharmony_ci	struct mediatek_regulator_coupler *mrc = to_mediatek_coupler(coupler);
10462306a36Sopenharmony_ci	const char *rdev_name = rdev_get_name(rdev);
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	/*
10762306a36Sopenharmony_ci	 * If we're getting a coupling of more than two regulators here and
10862306a36Sopenharmony_ci	 * this means that this is surely not a GPU<->SRAM couple: in that
10962306a36Sopenharmony_ci	 * case, we may want to use another coupler implementation, if any,
11062306a36Sopenharmony_ci	 * or the generic one: the regulator core will keep walking through
11162306a36Sopenharmony_ci	 * the list of couplers when any .attach_regulator() cb returns 1.
11262306a36Sopenharmony_ci	 */
11362306a36Sopenharmony_ci	if (rdev->coupling_desc.n_coupled > 2)
11462306a36Sopenharmony_ci		return 1;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	if (strstr(rdev_name, "sram")) {
11762306a36Sopenharmony_ci		if (mrc->vsram_rdev)
11862306a36Sopenharmony_ci			return -EINVAL;
11962306a36Sopenharmony_ci		mrc->vsram_rdev = rdev;
12062306a36Sopenharmony_ci	} else if (!strstr(rdev_name, "vgpu") && !strstr(rdev_name, "Vgpu")) {
12162306a36Sopenharmony_ci		return 1;
12262306a36Sopenharmony_ci	}
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	return 0;
12562306a36Sopenharmony_ci}
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_cistatic int mediatek_regulator_detach(struct regulator_coupler *coupler,
12862306a36Sopenharmony_ci				     struct regulator_dev *rdev)
12962306a36Sopenharmony_ci{
13062306a36Sopenharmony_ci	struct mediatek_regulator_coupler *mrc = to_mediatek_coupler(coupler);
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	if (rdev == mrc->vsram_rdev)
13362306a36Sopenharmony_ci		mrc->vsram_rdev = NULL;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	return 0;
13662306a36Sopenharmony_ci}
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_cistatic struct mediatek_regulator_coupler mediatek_coupler = {
13962306a36Sopenharmony_ci	.coupler = {
14062306a36Sopenharmony_ci		.attach_regulator = mediatek_regulator_attach,
14162306a36Sopenharmony_ci		.detach_regulator = mediatek_regulator_detach,
14262306a36Sopenharmony_ci		.balance_voltage = mediatek_regulator_balance_voltage,
14362306a36Sopenharmony_ci	},
14462306a36Sopenharmony_ci};
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_cistatic int mediatek_regulator_coupler_init(void)
14762306a36Sopenharmony_ci{
14862306a36Sopenharmony_ci	if (!of_machine_is_compatible("mediatek,mt8183") &&
14962306a36Sopenharmony_ci	    !of_machine_is_compatible("mediatek,mt8186") &&
15062306a36Sopenharmony_ci	    !of_machine_is_compatible("mediatek,mt8192"))
15162306a36Sopenharmony_ci		return 0;
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	return regulator_coupler_register(&mediatek_coupler.coupler);
15462306a36Sopenharmony_ci}
15562306a36Sopenharmony_ciarch_initcall(mediatek_regulator_coupler_init);
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ciMODULE_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>");
15862306a36Sopenharmony_ciMODULE_DESCRIPTION("MediaTek Regulator Coupler driver");
15962306a36Sopenharmony_ciMODULE_LICENSE("GPL");
160