File Index
Files and directories
Sign in
FILE

pxa1908-power-controller.c

File preview with syntax highlighting.

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