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