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