1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Voltage regulators coupler for MediaTek SoCs
4 *
5 * Copyright (C) 2022 Collabora, Ltd.
6 * Author: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
7 */
8
9#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
10
11#include <linux/init.h>
12#include <linux/kernel.h>
13#include <linux/of.h>
14#include <linux/regulator/coupler.h>
15#include <linux/regulator/driver.h>
16#include <linux/regulator/machine.h>
17#include <linux/suspend.h>
18
19#define to_mediatek_coupler(x) container_of(x, struct mediatek_regulator_coupler, coupler)
20
21struct mediatek_regulator_coupler {
22 struct regulator_coupler coupler;
23 struct regulator_dev *vsram_rdev;
24};
25
26/*
27 * We currently support only couples of not more than two vregs and
28 * modify the vsram voltage only when changing voltage of vgpu.
29 *
30 * This function is limited to the GPU<->SRAM voltages relationships.
31 */
32static int mediatek_regulator_balance_voltage(struct regulator_coupler *coupler,
33 struct regulator_dev *rdev,
34 suspend_state_t state)
35{
36 struct mediatek_regulator_coupler *mrc = to_mediatek_coupler(coupler);
37 int max_spread = rdev->constraints->max_spread[0];
38 int vsram_min_uV = mrc->vsram_rdev->constraints->min_uV;
39 int vsram_max_uV = mrc->vsram_rdev->constraints->max_uV;
40 int vsram_target_min_uV, vsram_target_max_uV;
41 int min_uV = 0;
42 int max_uV = INT_MAX;
43 int ret;
44
45 /*
46 * If the target device is on, setting the SRAM voltage directly
47 * is not supported as it scales through its coupled supply voltage.
48 *
49 * An exception is made in case the use_count is zero: this means
50 * that this is the first time we power up the SRAM regulator, which
51 * implies that the target device has yet to perform initialization
52 * and setting a voltage at that time is harmless.
53 */
54 if (rdev == mrc->vsram_rdev) {
55 if (rdev->use_count == 0)
56 return regulator_do_balance_voltage(rdev, state, skip_coupled: true);
57
58 return -EPERM;
59 }
60
61 ret = regulator_check_consumers(rdev, min_uV: &min_uV, max_uV: &max_uV, state);
62 if (ret < 0)
63 return ret;
64
65 if (min_uV == 0) {
66 ret = regulator_get_voltage_rdev(rdev);
67 if (ret < 0)
68 return ret;
69 min_uV = ret;
70 }
71
72 ret = regulator_check_voltage(rdev, min_uV: &min_uV, max_uV: &max_uV);
73 if (ret < 0)
74 return ret;
75
76 /*
77 * If we're asked to set a voltage less than VSRAM min_uV, set
78 * the minimum allowed voltage on VSRAM, as in this case it is
79 * safe to ignore the max_spread parameter.
80 */
81 vsram_target_min_uV = max(vsram_min_uV, min_uV + max_spread);
82 vsram_target_max_uV = min(vsram_max_uV, vsram_target_min_uV + max_spread);
83
84 /* Make sure we're not out of range */
85 vsram_target_min_uV = min(vsram_target_min_uV, vsram_max_uV);
86
87 pr_debug("Setting voltage %d-%duV on %s (minuV %d)\n",
88 vsram_target_min_uV, vsram_target_max_uV,
89 rdev_get_name(mrc->vsram_rdev), min_uV);
90
91 ret = regulator_set_voltage_rdev(rdev: mrc->vsram_rdev, min_uV: vsram_target_min_uV,
92 max_uV: vsram_target_max_uV, state);
93 if (ret)
94 return ret;
95
96 /* The sram voltage is now balanced: update the target vreg voltage */
97 return regulator_do_balance_voltage(rdev, state, skip_coupled: true);
98}
99
100static int mediatek_regulator_attach(struct regulator_coupler *coupler,
101 struct regulator_dev *rdev)
102{
103 struct mediatek_regulator_coupler *mrc = to_mediatek_coupler(coupler);
104 const char *rdev_name = rdev_get_name(rdev);
105
106 /*
107 * If we're getting a coupling of more than two regulators here and
108 * this means that this is surely not a GPU<->SRAM couple: in that
109 * case, we may want to use another coupler implementation, if any,
110 * or the generic one: the regulator core will keep walking through
111 * the list of couplers when any .attach_regulator() cb returns 1.
112 */
113 if (rdev->coupling_desc.n_coupled > 2)
114 return 1;
115
116 if (strstr(rdev_name, "sram")) {
117 if (mrc->vsram_rdev)
118 return -EINVAL;
119 mrc->vsram_rdev = rdev;
120 } else if (!strstr(rdev_name, "vgpu") && !strstr(rdev_name, "Vgpu")) {
121 return 1;
122 }
123
124 return 0;
125}
126
127static int mediatek_regulator_detach(struct regulator_coupler *coupler,
128 struct regulator_dev *rdev)
129{
130 struct mediatek_regulator_coupler *mrc = to_mediatek_coupler(coupler);
131
132 if (rdev == mrc->vsram_rdev)
133 mrc->vsram_rdev = NULL;
134
135 return 0;
136}
137
138static struct mediatek_regulator_coupler mediatek_coupler = {
139 .coupler = {
140 .attach_regulator = mediatek_regulator_attach,
141 .detach_regulator = mediatek_regulator_detach,
142 .balance_voltage = mediatek_regulator_balance_voltage,
143 },
144};
145
146static int mediatek_regulator_coupler_init(void)
147{
148 if (!of_machine_is_compatible(compat: "mediatek,mt8183") &&
149 !of_machine_is_compatible(compat: "mediatek,mt8186") &&
150 !of_machine_is_compatible(compat: "mediatek,mt8192"))
151 return 0;
152
153 return regulator_coupler_register(coupler: &mediatek_coupler.coupler);
154}
155arch_initcall(mediatek_regulator_coupler_init);
156
157MODULE_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>");
158MODULE_DESCRIPTION("MediaTek Regulator Coupler driver");
159MODULE_LICENSE("GPL");
160

source code of linux/drivers/soc/mediatek/mtk-regulator-coupler.c