xref: /linux/drivers/pmdomain/marvell/pxa1908-power-controller.c (revision d797ecf3ffc5cc3e622bfee4cee6b17372c5bcc7)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright 2025 Duje Mihanović <duje@dujemihanovic.xyz>
4  */
5 
6 #include <linux/auxiliary_bus.h>
7 #include <linux/container_of.h>
8 #include <linux/dev_printk.h>
9 #include <linux/device.h>
10 #include <linux/mfd/syscon.h>
11 #include <linux/mod_devicetable.h>
12 #include <linux/module.h>
13 #include <linux/pm_domain.h>
14 #include <linux/regmap.h>
15 
16 #include <dt-bindings/power/marvell,pxa1908-power.h>
17 
18 /* VPU, GPU, ISP */
19 #define APMU_PWR_CTRL_REG	0xd8
20 #define APMU_PWR_BLK_TMR_REG	0xdc
21 #define APMU_PWR_STATUS_REG	0xf0
22 
23 /* DSI */
24 #define APMU_DEBUG		0x88
25 #define DSI_PHY_DVM_MASK	BIT(31)
26 
27 #define APMU_AUDIO_CLK		0x80
28 #define AUDIO_ULCX_ENABLE	0x0d
29 
30 #define POWER_ON_LATENCY_US	300
31 #define POWER_OFF_LATENCY_US	20
32 #define POWER_POLL_TIMEOUT_US	(25 * USEC_PER_MSEC)
33 #define POWER_POLL_SLEEP_US	6
34 
35 #define NR_DOMAINS	6
36 
37 #define to_pxa1908_pd(_genpd) container_of(_genpd, struct pxa1908_pd, genpd)
38 
39 struct pxa1908_pd_ctrl {
40 	struct generic_pm_domain *domains[NR_DOMAINS];
41 	struct genpd_onecell_data onecell_data;
42 	struct regmap *base;
43 	struct device *dev;
44 };
45 
46 struct pxa1908_pd_data {
47 	u32 reg_clk_res_ctrl;
48 	u32 pwr_state;
49 	u32 hw_mode;
50 	bool keep_on;
51 	int id;
52 };
53 
54 struct pxa1908_pd {
55 	const struct pxa1908_pd_data data;
56 	struct pxa1908_pd_ctrl *ctrl;
57 	struct generic_pm_domain genpd;
58 	bool initialized;
59 };
60 
61 static inline bool pxa1908_pd_is_on(struct pxa1908_pd *pd)
62 {
63 	struct pxa1908_pd_ctrl *ctrl = pd->ctrl;
64 
65 	switch (pd->data.id) {
66 	case PXA1908_POWER_DOMAIN_AUDIO:
67 		return regmap_test_bits(ctrl->base, APMU_AUDIO_CLK, AUDIO_ULCX_ENABLE);
68 	case PXA1908_POWER_DOMAIN_DSI:
69 		return regmap_test_bits(ctrl->base, APMU_DEBUG, DSI_PHY_DVM_MASK);
70 	default:
71 		return regmap_test_bits(ctrl->base, APMU_PWR_STATUS_REG, pd->data.pwr_state);
72 	}
73 }
74 
75 static int pxa1908_pd_power_on(struct generic_pm_domain *genpd)
76 {
77 	struct pxa1908_pd *pd = to_pxa1908_pd(genpd);
78 	const struct pxa1908_pd_data *data = &pd->data;
79 	struct pxa1908_pd_ctrl *ctrl = pd->ctrl;
80 	unsigned int status;
81 	int ret = 0;
82 
83 	regmap_set_bits(ctrl->base, data->reg_clk_res_ctrl, data->hw_mode);
84 	if (data->id != PXA1908_POWER_DOMAIN_ISP)
85 		regmap_write(ctrl->base, APMU_PWR_BLK_TMR_REG, 0x20001fff);
86 	regmap_set_bits(ctrl->base, APMU_PWR_CTRL_REG, data->pwr_state);
87 
88 	ret = regmap_read_poll_timeout(ctrl->base, APMU_PWR_STATUS_REG, status,
89 				       status & data->pwr_state, POWER_POLL_SLEEP_US,
90 				       POWER_ON_LATENCY_US + POWER_POLL_TIMEOUT_US);
91 	if (ret == -ETIMEDOUT)
92 		dev_err(ctrl->dev, "timed out powering on domain '%s'\n", pd->genpd.name);
93 
94 	return ret;
95 }
96 
97 static int pxa1908_pd_power_off(struct generic_pm_domain *genpd)
98 {
99 	struct pxa1908_pd *pd = to_pxa1908_pd(genpd);
100 	const struct pxa1908_pd_data *data = &pd->data;
101 	struct pxa1908_pd_ctrl *ctrl = pd->ctrl;
102 	unsigned int status;
103 	int ret;
104 
105 	regmap_clear_bits(ctrl->base, APMU_PWR_CTRL_REG, data->pwr_state);
106 
107 	ret = regmap_read_poll_timeout(ctrl->base, APMU_PWR_STATUS_REG, status,
108 				       !(status & data->pwr_state), POWER_POLL_SLEEP_US,
109 				       POWER_OFF_LATENCY_US + POWER_POLL_TIMEOUT_US);
110 	if (ret == -ETIMEDOUT) {
111 		dev_err(ctrl->dev, "timed out powering off domain '%s'\n", pd->genpd.name);
112 		return ret;
113 	}
114 
115 	return regmap_clear_bits(ctrl->base, data->reg_clk_res_ctrl, data->hw_mode);
116 }
117 
118 static inline int pxa1908_dsi_power_on(struct generic_pm_domain *genpd)
119 {
120 	struct pxa1908_pd *pd = to_pxa1908_pd(genpd);
121 	struct pxa1908_pd_ctrl *ctrl = pd->ctrl;
122 
123 	return regmap_set_bits(ctrl->base, APMU_DEBUG, DSI_PHY_DVM_MASK);
124 }
125 
126 static inline int pxa1908_dsi_power_off(struct generic_pm_domain *genpd)
127 {
128 	struct pxa1908_pd *pd = to_pxa1908_pd(genpd);
129 	struct pxa1908_pd_ctrl *ctrl = pd->ctrl;
130 
131 	return regmap_clear_bits(ctrl->base, APMU_DEBUG, DSI_PHY_DVM_MASK);
132 }
133 
134 static inline int pxa1908_audio_power_on(struct generic_pm_domain *genpd)
135 {
136 	struct pxa1908_pd *pd = to_pxa1908_pd(genpd);
137 	struct pxa1908_pd_ctrl *ctrl = pd->ctrl;
138 
139 	return regmap_set_bits(ctrl->base, APMU_AUDIO_CLK, AUDIO_ULCX_ENABLE);
140 }
141 
142 static inline int pxa1908_audio_power_off(struct generic_pm_domain *genpd)
143 {
144 	struct pxa1908_pd *pd = to_pxa1908_pd(genpd);
145 	struct pxa1908_pd_ctrl *ctrl = pd->ctrl;
146 
147 	return regmap_clear_bits(ctrl->base, APMU_AUDIO_CLK, AUDIO_ULCX_ENABLE);
148 }
149 
150 #define DOMAIN(_id, _name, ctrl, mode, state) \
151 	[_id] = { \
152 		.data = { \
153 			.reg_clk_res_ctrl = ctrl, \
154 			.hw_mode = BIT(mode), \
155 			.pwr_state = BIT(state), \
156 			.id = _id, \
157 		}, \
158 		.genpd = { \
159 			.name = _name, \
160 			.power_on = pxa1908_pd_power_on, \
161 			.power_off = pxa1908_pd_power_off, \
162 		}, \
163 	}
164 
165 static struct pxa1908_pd domains[NR_DOMAINS] = {
166 	DOMAIN(PXA1908_POWER_DOMAIN_VPU, "vpu", 0xa4, 19, 2),
167 	DOMAIN(PXA1908_POWER_DOMAIN_GPU, "gpu", 0xcc, 11, 0),
168 	DOMAIN(PXA1908_POWER_DOMAIN_GPU2D, "gpu2d", 0xf4, 11, 6),
169 	DOMAIN(PXA1908_POWER_DOMAIN_ISP, "isp", 0x38, 15, 4),
170 	[PXA1908_POWER_DOMAIN_DSI] = {
171 		.genpd = {
172 			.name = "dsi",
173 			.power_on = pxa1908_dsi_power_on,
174 			.power_off = pxa1908_dsi_power_off,
175 			/*
176 			 * TODO: There is no DSI driver written yet and until then we probably
177 			 * don't want to power off the DSI PHY ever.
178 			 */
179 			.flags = GENPD_FLAG_ALWAYS_ON,
180 		},
181 		.data = {
182 			/* See above. */
183 			.keep_on = true,
184 		},
185 	},
186 	[PXA1908_POWER_DOMAIN_AUDIO] = {
187 		.genpd = {
188 			.name = "audio",
189 			.power_on = pxa1908_audio_power_on,
190 			.power_off = pxa1908_audio_power_off,
191 		},
192 	},
193 };
194 
195 static void pxa1908_pd_remove(struct auxiliary_device *auxdev)
196 {
197 	struct pxa1908_pd *pd;
198 	int ret;
199 
200 	for (int i = NR_DOMAINS - 1; i >= 0; i--) {
201 		pd = &domains[i];
202 
203 		if (!pd->initialized)
204 			continue;
205 
206 		if (pxa1908_pd_is_on(pd) && !pd->data.keep_on)
207 			pxa1908_pd_power_off(&pd->genpd);
208 
209 		ret = pm_genpd_remove(&pd->genpd);
210 		if (ret)
211 			dev_err(&auxdev->dev, "failed to remove domain '%s': %d\n",
212 				pd->genpd.name, ret);
213 	}
214 }
215 
216 static int
217 pxa1908_pd_init(struct pxa1908_pd_ctrl *ctrl, int id, struct device *dev)
218 {
219 	struct pxa1908_pd *pd = &domains[id];
220 	int ret;
221 
222 	ctrl->domains[id] = &pd->genpd;
223 
224 	pd->ctrl = ctrl;
225 
226 	/* Make sure the state of the hardware is synced with the domain table above. */
227 	if (pd->data.keep_on) {
228 		ret = pd->genpd.power_on(&pd->genpd);
229 		if (ret)
230 			return dev_err_probe(dev, ret, "failed to power on domain '%s'\n",
231 					     pd->genpd.name);
232 	} else {
233 		if (pxa1908_pd_is_on(pd)) {
234 			dev_warn(dev,
235 				 "domain '%s' is on despite being default off; powering off\n",
236 				 pd->genpd.name);
237 
238 			ret = pd->genpd.power_off(&pd->genpd);
239 			if (ret)
240 				return dev_err_probe(dev, ret,
241 						     "failed to power off domain '%s'\n",
242 						     pd->genpd.name);
243 		}
244 	}
245 
246 	ret = pm_genpd_init(&pd->genpd, NULL, !pd->data.keep_on);
247 	if (ret)
248 		return dev_err_probe(dev, ret, "domain '%s' failed to initialize\n",
249 				     pd->genpd.name);
250 
251 	pd->initialized = true;
252 
253 	return 0;
254 }
255 
256 static int
257 pxa1908_pd_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *aux_id)
258 {
259 	struct pxa1908_pd_ctrl *ctrl;
260 	struct device *dev = &auxdev->dev;
261 	int ret;
262 
263 	ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
264 	if (!ctrl)
265 		return -ENOMEM;
266 
267 	auxiliary_set_drvdata(auxdev, ctrl);
268 
269 	ctrl->base = syscon_node_to_regmap(dev->parent->of_node);
270 	if (IS_ERR(ctrl->base))
271 		return dev_err_probe(dev, PTR_ERR(ctrl->base), "no regmap available\n");
272 
273 	ctrl->dev = dev;
274 	ctrl->onecell_data.domains = ctrl->domains;
275 	ctrl->onecell_data.num_domains = NR_DOMAINS;
276 
277 	for (int i = 0; i < NR_DOMAINS; i++) {
278 		ret = pxa1908_pd_init(ctrl, i, dev);
279 		if (ret)
280 			goto err;
281 	}
282 
283 	return of_genpd_add_provider_onecell(dev->parent->of_node, &ctrl->onecell_data);
284 
285 err:
286 	pxa1908_pd_remove(auxdev);
287 	return ret;
288 }
289 
290 static const struct auxiliary_device_id pxa1908_pd_id[] = {
291 	{ .name = "clk_pxa1908_apmu.power" },
292 	{ }
293 };
294 MODULE_DEVICE_TABLE(auxiliary, pxa1908_pd_id);
295 
296 static struct auxiliary_driver pxa1908_pd_driver = {
297 	.probe = pxa1908_pd_probe,
298 	.remove = pxa1908_pd_remove,
299 	.id_table = pxa1908_pd_id,
300 };
301 module_auxiliary_driver(pxa1908_pd_driver);
302 
303 MODULE_AUTHOR("Duje Mihanović <duje@dujemihanovic.xyz>");
304 MODULE_DESCRIPTION("Marvell PXA1908 power domain driver");
305 MODULE_LICENSE("GPL");
306