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 | |
21 | struct 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 | */ |
32 | static 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 | |
100 | static 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 | |
127 | static 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 | |
138 | static 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 | |
146 | static 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 | } |
155 | arch_initcall(mediatek_regulator_coupler_init); |
156 | |
157 | MODULE_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>" ); |
158 | MODULE_DESCRIPTION("MediaTek Regulator Coupler driver" ); |
159 | MODULE_LICENSE("GPL" ); |
160 | |