Skip to content

Commit 6f51a04

Browse files
LegoLivesMatterstorulf
authored andcommitted
pmdomain: marvell: Add PXA1908 power domains
Marvell's PXA1908 SoC has a few power domains for its VPU, GPU, image processor and DSI PHY. Add a driver to control these. Signed-off-by: Duje Mihanović <duje@dujemihanovic.xyz> Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
1 parent 614106a commit 6f51a04

File tree

6 files changed

+298
-0
lines changed

6 files changed

+298
-0
lines changed

MAINTAINERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2871,6 +2871,7 @@ L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
28712871
S: Maintained
28722872
F: arch/arm64/boot/dts/marvell/mmp/
28732873
F: drivers/clk/mmp/clk-pxa1908*.c
2874+
F: drivers/pmdomain/marvell/
28742875
F: include/dt-bindings/clock/marvell,pxa1908.h
28752876
F: include/dt-bindings/power/marvell,pxa1908-power.h
28762877

drivers/pmdomain/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ source "drivers/pmdomain/apple/Kconfig"
77
source "drivers/pmdomain/arm/Kconfig"
88
source "drivers/pmdomain/bcm/Kconfig"
99
source "drivers/pmdomain/imx/Kconfig"
10+
source "drivers/pmdomain/marvell/Kconfig"
1011
source "drivers/pmdomain/mediatek/Kconfig"
1112
source "drivers/pmdomain/qcom/Kconfig"
1213
source "drivers/pmdomain/renesas/Kconfig"

drivers/pmdomain/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ obj-y += apple/
55
obj-y += arm/
66
obj-y += bcm/
77
obj-y += imx/
8+
obj-y += marvell/
89
obj-y += mediatek/
910
obj-y += qcom/
1011
obj-y += renesas/

drivers/pmdomain/marvell/Kconfig

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# SPDX-License-Identifier: GPL-2.0-only
2+
3+
menu "Marvell PM Domains"
4+
depends on ARCH_MMP || COMPILE_TEST
5+
6+
config PXA1908_PM_DOMAINS
7+
tristate "Marvell PXA1908 power domains"
8+
depends on OF
9+
depends on PM
10+
default y if ARCH_MMP && ARM64
11+
select AUXILIARY_BUS
12+
select MFD_SYSCON
13+
select PM_GENERIC_DOMAINS
14+
select PM_GENERIC_DOMAINS_OF
15+
help
16+
Say Y here to enable support for Marvell PXA1908's power domanis.
17+
18+
endmenu

drivers/pmdomain/marvell/Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# SPDX-License-Identifier: GPL-2.0-only
2+
3+
obj-$(CONFIG_PXA1908_PM_DOMAINS) += pxa1908-power-controller.o
Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
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+
36+
struct 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+
43+
struct 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+
51+
struct 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+
58+
static 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+
67+
static 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+
89+
static 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+
110+
static 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+
118+
static 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+
141+
static 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+
164+
static 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+
185+
static int
186+
pxa1908_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+
225+
static int
226+
pxa1908_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+
254+
err:
255+
pxa1908_pd_remove(auxdev);
256+
return ret;
257+
}
258+
259+
static const struct auxiliary_device_id pxa1908_pd_id[] = {
260+
{ .name = "clk_pxa1908_apmu.power" },
261+
{ }
262+
};
263+
MODULE_DEVICE_TABLE(auxiliary, pxa1908_pd_id);
264+
265+
static struct auxiliary_driver pxa1908_pd_driver = {
266+
.probe = pxa1908_pd_probe,
267+
.remove = pxa1908_pd_remove,
268+
.id_table = pxa1908_pd_id,
269+
};
270+
module_auxiliary_driver(pxa1908_pd_driver);
271+
272+
MODULE_AUTHOR("Duje Mihanović <duje@dujemihanovic.xyz>");
273+
MODULE_DESCRIPTION("Marvell PXA1908 power domain driver");
274+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)