Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
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
39struct 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
46struct 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
54struct 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
61static 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
75static 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
97static 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
118static 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
126static 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
134static 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
142static 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
165static 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
195static 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
216static int
217pxa1908_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
256static int
257pxa1908_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
285err:
286 pxa1908_pd_remove(auxdev);
287 return ret;
288}
289
290static const struct auxiliary_device_id pxa1908_pd_id[] = {
291 { .name = "clk_pxa1908_apmu.power" },
292 { }
293};
294MODULE_DEVICE_TABLE(auxiliary, pxa1908_pd_id);
295
296static struct auxiliary_driver pxa1908_pd_driver = {
297 .probe = pxa1908_pd_probe,
298 .remove = pxa1908_pd_remove,
299 .id_table = pxa1908_pd_id,
300};
301module_auxiliary_driver(pxa1908_pd_driver);
302
303MODULE_AUTHOR("Duje Mihanović <duje@dujemihanovic.xyz>");
304MODULE_DESCRIPTION("Marvell PXA1908 power domain driver");
305MODULE_LICENSE("GPL");