Commit | Line | Data |
---|---|---|
c800cd78 D |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* | |
3 | * Copyright (C) 2016 Freescale Semiconductor, Inc. | |
4 | * Copyright 2017-2018 NXP | |
5 | * Dong Aisheng <aisheng.dong@nxp.com> | |
6 | * | |
7 | * Implementation of the SCU based Power Domains | |
8 | * | |
9 | * NOTE: a better implementation suggested by Ulf Hansson is using a | |
10 | * single global power domain and implement the ->attach|detach_dev() | |
11 | * callback for the genpd and use the regular of_genpd_add_provider_simple(). | |
12 | * From within the ->attach_dev(), we could get the OF node for | |
13 | * the device that is being attached and then parse the power-domain | |
14 | * cell containing the "resource id" and store that in the per device | |
15 | * struct generic_pm_domain_data (we have void pointer there for | |
16 | * storing these kind of things). | |
17 | * | |
18 | * Additionally, we need to implement the ->stop() and ->start() | |
19 | * callbacks of genpd, which is where you "power on/off" devices, | |
20 | * rather than using the above ->power_on|off() callbacks. | |
21 | * | |
22 | * However, there're two known issues: | |
23 | * 1. The ->attach_dev() of power domain infrastructure still does | |
24 | * not support multi domains case as the struct device *dev passed | |
25 | * in is a virtual PD device, it does not help for parsing the real | |
26 | * device resource id from device tree, so it's unware of which | |
27 | * real sub power domain of device should be attached. | |
28 | * | |
29 | * The framework needs some proper extension to support multi power | |
30 | * domain cases. | |
31 | * | |
32 | * 2. It also breaks most of current drivers as the driver probe sequence | |
33 | * behavior changed if removing ->power_on|off() callback and use | |
34 | * ->start() and ->stop() instead. genpd_dev_pm_attach will only power | |
35 | * up the domain and attach device, but will not call .start() which | |
36 | * relies on device runtime pm. That means the device power is still | |
37 | * not up before running driver probe function. For SCU enabled | |
38 | * platforms, all device drivers accessing registers/clock without power | |
39 | * domain enabled will trigger a HW access error. That means we need fix | |
40 | * most drivers probe sequence with proper runtime pm. | |
41 | * | |
42 | * In summary, we need fix above two issue before being able to switch to | |
43 | * the "single global power domain" way. | |
44 | * | |
45 | */ | |
46 | ||
47 | #include <dt-bindings/firmware/imx/rsrc.h> | |
48 | #include <linux/firmware/imx/sci.h> | |
49 | #include <linux/io.h> | |
50 | #include <linux/module.h> | |
51 | #include <linux/of.h> | |
52 | #include <linux/of_address.h> | |
53 | #include <linux/of_platform.h> | |
54 | #include <linux/platform_device.h> | |
55 | #include <linux/pm.h> | |
56 | #include <linux/pm_domain.h> | |
57 | #include <linux/slab.h> | |
58 | ||
59 | /* SCU Power Mode Protocol definition */ | |
60 | struct imx_sc_msg_req_set_resource_power_mode { | |
61 | struct imx_sc_rpc_msg hdr; | |
62 | u16 resource; | |
63 | u8 mode; | |
7c1a1c81 | 64 | } __packed __aligned(4); |
c800cd78 D |
65 | |
66 | #define IMX_SCU_PD_NAME_SIZE 20 | |
67 | struct imx_sc_pm_domain { | |
68 | struct generic_pm_domain pd; | |
69 | char name[IMX_SCU_PD_NAME_SIZE]; | |
70 | u32 rsrc; | |
71 | }; | |
72 | ||
73 | struct imx_sc_pd_range { | |
74 | char *name; | |
75 | u32 rsrc; | |
76 | u8 num; | |
ad8cc071 AD |
77 | |
78 | /* add domain index */ | |
c800cd78 | 79 | bool postfix; |
ad8cc071 | 80 | u8 start_from; |
c800cd78 D |
81 | }; |
82 | ||
83 | struct imx_sc_pd_soc { | |
84 | const struct imx_sc_pd_range *pd_ranges; | |
85 | u8 num_ranges; | |
86 | }; | |
87 | ||
88 | static const struct imx_sc_pd_range imx8qxp_scu_pd_ranges[] = { | |
89 | /* LSIO SS */ | |
32654dad AD |
90 | { "pwm", IMX_SC_R_PWM_0, 8, true, 0 }, |
91 | { "gpio", IMX_SC_R_GPIO_0, 8, true, 0 }, | |
92 | { "gpt", IMX_SC_R_GPT_0, 5, true, 0 }, | |
93 | { "kpp", IMX_SC_R_KPP, 1, false, 0 }, | |
94 | { "fspi", IMX_SC_R_FSPI_0, 2, true, 0 }, | |
6d9d2171 | 95 | { "mu_a", IMX_SC_R_MU_0A, 14, true, 0 }, |
590b346b | 96 | { "mu_b", IMX_SC_R_MU_13B, 1, true, 13 }, |
c800cd78 D |
97 | |
98 | /* CONN SS */ | |
32654dad AD |
99 | { "usb", IMX_SC_R_USB_0, 2, true, 0 }, |
100 | { "usb0phy", IMX_SC_R_USB_0_PHY, 1, false, 0 }, | |
101 | { "usb2", IMX_SC_R_USB_2, 1, false, 0 }, | |
102 | { "usb2phy", IMX_SC_R_USB_2_PHY, 1, false, 0 }, | |
103 | { "sdhc", IMX_SC_R_SDHC_0, 3, true, 0 }, | |
104 | { "enet", IMX_SC_R_ENET_0, 2, true, 0 }, | |
105 | { "nand", IMX_SC_R_NAND, 1, false, 0 }, | |
106 | { "mlb", IMX_SC_R_MLB_0, 1, true, 0 }, | |
107 | ||
108 | /* AUDIO SS */ | |
109 | { "audio-pll0", IMX_SC_R_AUDIO_PLL_0, 1, false, 0 }, | |
110 | { "audio-pll1", IMX_SC_R_AUDIO_PLL_1, 1, false, 0 }, | |
111 | { "audio-clk-0", IMX_SC_R_AUDIO_CLK_0, 1, false, 0 }, | |
112 | { "dma0-ch", IMX_SC_R_DMA_0_CH0, 16, true, 0 }, | |
113 | { "dma1-ch", IMX_SC_R_DMA_1_CH0, 16, true, 0 }, | |
114 | { "dma2-ch", IMX_SC_R_DMA_2_CH0, 5, true, 0 }, | |
115 | { "asrc0", IMX_SC_R_ASRC_0, 1, false, 0 }, | |
116 | { "asrc1", IMX_SC_R_ASRC_1, 1, false, 0 }, | |
117 | { "esai0", IMX_SC_R_ESAI_0, 1, false, 0 }, | |
118 | { "spdif0", IMX_SC_R_SPDIF_0, 1, false, 0 }, | |
119 | { "sai", IMX_SC_R_SAI_0, 3, true, 0 }, | |
120 | { "amix", IMX_SC_R_AMIX, 1, false, 0 }, | |
121 | { "mqs0", IMX_SC_R_MQS_0, 1, false, 0 }, | |
122 | { "dsp", IMX_SC_R_DSP, 1, false, 0 }, | |
123 | { "dsp-ram", IMX_SC_R_DSP_RAM, 1, false, 0 }, | |
124 | ||
125 | /* DMA SS */ | |
126 | { "can", IMX_SC_R_CAN_0, 3, true, 0 }, | |
127 | { "ftm", IMX_SC_R_FTM_0, 2, true, 0 }, | |
128 | { "lpi2c", IMX_SC_R_I2C_0, 4, true, 0 }, | |
129 | { "adc", IMX_SC_R_ADC_0, 1, true, 0 }, | |
130 | { "lcd", IMX_SC_R_LCD_0, 1, true, 0 }, | |
131 | { "lcd0-pwm", IMX_SC_R_LCD_0_PWM_0, 1, true, 0 }, | |
132 | { "lpuart", IMX_SC_R_UART_0, 4, true, 0 }, | |
133 | { "lpspi", IMX_SC_R_SPI_0, 4, true, 0 }, | |
d43dc522 | 134 | { "irqstr_dsp", IMX_SC_R_IRQSTR_DSP, 1, false, 0 }, |
32654dad AD |
135 | |
136 | /* VPU SS */ | |
ad8cc071 AD |
137 | { "vpu", IMX_SC_R_VPU, 1, false, 0 }, |
138 | { "vpu-pid", IMX_SC_R_VPU_PID0, 8, true, 0 }, | |
139 | { "vpu-dec0", IMX_SC_R_VPU_DEC_0, 1, false, 0 }, | |
140 | { "vpu-enc0", IMX_SC_R_VPU_ENC_0, 1, false, 0 }, | |
c800cd78 D |
141 | |
142 | /* GPU SS */ | |
ad8cc071 | 143 | { "gpu0-pid", IMX_SC_R_GPU_0_PID0, 4, true, 0 }, |
c800cd78 D |
144 | |
145 | /* HSIO SS */ | |
32654dad AD |
146 | { "pcie-b", IMX_SC_R_PCIE_B, 1, false, 0 }, |
147 | { "serdes-1", IMX_SC_R_SERDES_1, 1, false, 0 }, | |
ad8cc071 | 148 | { "hsio-gpio", IMX_SC_R_HSIO_GPIO, 1, false, 0 }, |
c800cd78 | 149 | |
32654dad | 150 | /* MIPI SS */ |
ad8cc071 AD |
151 | { "mipi0", IMX_SC_R_MIPI_0, 1, false, 0 }, |
152 | { "mipi0-pwm0", IMX_SC_R_MIPI_0_PWM_0, 1, false, 0 }, | |
153 | { "mipi0-i2c", IMX_SC_R_MIPI_0_I2C_0, 2, true, 0 }, | |
32654dad AD |
154 | |
155 | /* LVDS SS */ | |
ad8cc071 | 156 | { "lvds0", IMX_SC_R_LVDS_0, 1, false, 0 }, |
c800cd78 D |
157 | |
158 | /* DC SS */ | |
ad8cc071 AD |
159 | { "dc0", IMX_SC_R_DC_0, 1, false, 0 }, |
160 | { "dc0-pll", IMX_SC_R_DC_0_PLL_0, 2, true, 0 }, | |
c800cd78 D |
161 | }; |
162 | ||
163 | static const struct imx_sc_pd_soc imx8qxp_scu_pd = { | |
164 | .pd_ranges = imx8qxp_scu_pd_ranges, | |
165 | .num_ranges = ARRAY_SIZE(imx8qxp_scu_pd_ranges), | |
166 | }; | |
167 | ||
168 | static struct imx_sc_ipc *pm_ipc_handle; | |
169 | ||
170 | static inline struct imx_sc_pm_domain * | |
171 | to_imx_sc_pd(struct generic_pm_domain *genpd) | |
172 | { | |
173 | return container_of(genpd, struct imx_sc_pm_domain, pd); | |
174 | } | |
175 | ||
176 | static int imx_sc_pd_power(struct generic_pm_domain *domain, bool power_on) | |
177 | { | |
178 | struct imx_sc_msg_req_set_resource_power_mode msg; | |
179 | struct imx_sc_rpc_msg *hdr = &msg.hdr; | |
180 | struct imx_sc_pm_domain *pd; | |
181 | int ret; | |
182 | ||
183 | pd = to_imx_sc_pd(domain); | |
184 | ||
185 | hdr->ver = IMX_SC_RPC_VERSION; | |
186 | hdr->svc = IMX_SC_RPC_SVC_PM; | |
187 | hdr->func = IMX_SC_PM_FUNC_SET_RESOURCE_POWER_MODE; | |
188 | hdr->size = 2; | |
189 | ||
190 | msg.resource = pd->rsrc; | |
191 | msg.mode = power_on ? IMX_SC_PM_PW_MODE_ON : IMX_SC_PM_PW_MODE_LP; | |
192 | ||
193 | ret = imx_scu_call_rpc(pm_ipc_handle, &msg, true); | |
194 | if (ret) | |
195 | dev_err(&domain->dev, "failed to power %s resource %d ret %d\n", | |
196 | power_on ? "up" : "off", pd->rsrc, ret); | |
197 | ||
198 | return ret; | |
199 | } | |
200 | ||
201 | static int imx_sc_pd_power_on(struct generic_pm_domain *domain) | |
202 | { | |
203 | return imx_sc_pd_power(domain, true); | |
204 | } | |
205 | ||
206 | static int imx_sc_pd_power_off(struct generic_pm_domain *domain) | |
207 | { | |
208 | return imx_sc_pd_power(domain, false); | |
209 | } | |
210 | ||
211 | static struct generic_pm_domain *imx_scu_pd_xlate(struct of_phandle_args *spec, | |
212 | void *data) | |
213 | { | |
214 | struct generic_pm_domain *domain = ERR_PTR(-ENOENT); | |
215 | struct genpd_onecell_data *pd_data = data; | |
216 | unsigned int i; | |
217 | ||
218 | for (i = 0; i < pd_data->num_domains; i++) { | |
219 | struct imx_sc_pm_domain *sc_pd; | |
220 | ||
221 | sc_pd = to_imx_sc_pd(pd_data->domains[i]); | |
222 | if (sc_pd->rsrc == spec->args[0]) { | |
223 | domain = &sc_pd->pd; | |
224 | break; | |
225 | } | |
226 | } | |
227 | ||
228 | return domain; | |
229 | } | |
230 | ||
231 | static struct imx_sc_pm_domain * | |
232 | imx_scu_add_pm_domain(struct device *dev, int idx, | |
233 | const struct imx_sc_pd_range *pd_ranges) | |
234 | { | |
235 | struct imx_sc_pm_domain *sc_pd; | |
236 | int ret; | |
237 | ||
238 | sc_pd = devm_kzalloc(dev, sizeof(*sc_pd), GFP_KERNEL); | |
239 | if (!sc_pd) | |
240 | return ERR_PTR(-ENOMEM); | |
241 | ||
242 | sc_pd->rsrc = pd_ranges->rsrc + idx; | |
243 | sc_pd->pd.power_off = imx_sc_pd_power_off; | |
244 | sc_pd->pd.power_on = imx_sc_pd_power_on; | |
245 | ||
246 | if (pd_ranges->postfix) | |
247 | snprintf(sc_pd->name, sizeof(sc_pd->name), | |
ad8cc071 | 248 | "%s%i", pd_ranges->name, pd_ranges->start_from + idx); |
c800cd78 D |
249 | else |
250 | snprintf(sc_pd->name, sizeof(sc_pd->name), | |
251 | "%s", pd_ranges->name); | |
252 | ||
253 | sc_pd->pd.name = sc_pd->name; | |
254 | ||
255 | if (sc_pd->rsrc >= IMX_SC_R_LAST) { | |
256 | dev_warn(dev, "invalid pd %s rsrc id %d found", | |
257 | sc_pd->name, sc_pd->rsrc); | |
258 | ||
259 | devm_kfree(dev, sc_pd); | |
260 | return NULL; | |
261 | } | |
262 | ||
263 | ret = pm_genpd_init(&sc_pd->pd, NULL, true); | |
264 | if (ret) { | |
265 | dev_warn(dev, "failed to init pd %s rsrc id %d", | |
266 | sc_pd->name, sc_pd->rsrc); | |
267 | devm_kfree(dev, sc_pd); | |
268 | return NULL; | |
269 | } | |
270 | ||
271 | return sc_pd; | |
272 | } | |
273 | ||
274 | static int imx_scu_init_pm_domains(struct device *dev, | |
275 | const struct imx_sc_pd_soc *pd_soc) | |
276 | { | |
277 | const struct imx_sc_pd_range *pd_ranges = pd_soc->pd_ranges; | |
278 | struct generic_pm_domain **domains; | |
279 | struct genpd_onecell_data *pd_data; | |
280 | struct imx_sc_pm_domain *sc_pd; | |
281 | u32 count = 0; | |
282 | int i, j; | |
283 | ||
284 | for (i = 0; i < pd_soc->num_ranges; i++) | |
285 | count += pd_ranges[i].num; | |
286 | ||
287 | domains = devm_kcalloc(dev, count, sizeof(*domains), GFP_KERNEL); | |
288 | if (!domains) | |
289 | return -ENOMEM; | |
290 | ||
291 | pd_data = devm_kzalloc(dev, sizeof(*pd_data), GFP_KERNEL); | |
292 | if (!pd_data) | |
293 | return -ENOMEM; | |
294 | ||
295 | count = 0; | |
296 | for (i = 0; i < pd_soc->num_ranges; i++) { | |
297 | for (j = 0; j < pd_ranges[i].num; j++) { | |
298 | sc_pd = imx_scu_add_pm_domain(dev, j, &pd_ranges[i]); | |
299 | if (IS_ERR_OR_NULL(sc_pd)) | |
300 | continue; | |
301 | ||
302 | domains[count++] = &sc_pd->pd; | |
303 | dev_dbg(dev, "added power domain %s\n", sc_pd->pd.name); | |
304 | } | |
305 | } | |
306 | ||
307 | pd_data->domains = domains; | |
308 | pd_data->num_domains = count; | |
309 | pd_data->xlate = imx_scu_pd_xlate; | |
310 | ||
311 | of_genpd_add_provider_onecell(dev->of_node, pd_data); | |
312 | ||
313 | return 0; | |
314 | } | |
315 | ||
316 | static int imx_sc_pd_probe(struct platform_device *pdev) | |
317 | { | |
318 | const struct imx_sc_pd_soc *pd_soc; | |
319 | int ret; | |
320 | ||
321 | ret = imx_scu_get_handle(&pm_ipc_handle); | |
322 | if (ret) | |
323 | return ret; | |
324 | ||
325 | pd_soc = of_device_get_match_data(&pdev->dev); | |
326 | if (!pd_soc) | |
327 | return -ENODEV; | |
328 | ||
329 | return imx_scu_init_pm_domains(&pdev->dev, pd_soc); | |
330 | } | |
331 | ||
332 | static const struct of_device_id imx_sc_pd_match[] = { | |
333 | { .compatible = "fsl,imx8qxp-scu-pd", &imx8qxp_scu_pd}, | |
e59e59b8 | 334 | { .compatible = "fsl,scu-pd", &imx8qxp_scu_pd}, |
c800cd78 D |
335 | { /* sentinel */ } |
336 | }; | |
337 | ||
338 | static struct platform_driver imx_sc_pd_driver = { | |
339 | .driver = { | |
340 | .name = "imx-scu-pd", | |
341 | .of_match_table = imx_sc_pd_match, | |
342 | }, | |
343 | .probe = imx_sc_pd_probe, | |
344 | }; | |
345 | builtin_platform_driver(imx_sc_pd_driver); | |
346 | ||
347 | MODULE_AUTHOR("Dong Aisheng <aisheng.dong@nxp.com>"); | |
348 | MODULE_DESCRIPTION("IMX SCU Power Domain driver"); | |
349 | MODULE_LICENSE("GPL v2"); |