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 | * Update: Genpd assigns the ->of_node for the virtual device before it |
33 | * invokes ->attach_dev() callback, hence parsing for device resources via |
34 | * DT should work fine. |
35 | * |
36 | * 2. It also breaks most of current drivers as the driver probe sequence |
37 | * behavior changed if removing ->power_on|off() callback and use |
38 | * ->start() and ->stop() instead. genpd_dev_pm_attach will only power |
39 | * up the domain and attach device, but will not call .start() which |
40 | * relies on device runtime pm. That means the device power is still |
41 | * not up before running driver probe function. For SCU enabled |
42 | * platforms, all device drivers accessing registers/clock without power |
43 | * domain enabled will trigger a HW access error. That means we need fix |
44 | * most drivers probe sequence with proper runtime pm. |
45 | * |
46 | * Update: Runtime PM support isn't necessary. Instead, this can easily be |
47 | * fixed in drivers by adding a call to dev_pm_domain_start() during probe. |
48 | * |
49 | * In summary, the second part needs to be addressed via minor updates to the |
50 | * relevant drivers, before the "single global power domain" model can be used. |
51 | * |
52 | */ |
53 | |
54 | #include <dt-bindings/firmware/imx/rsrc.h> |
55 | #include <linux/console.h> |
56 | #include <linux/firmware/imx/sci.h> |
57 | #include <linux/firmware/imx/svc/rm.h> |
58 | #include <linux/io.h> |
59 | #include <linux/module.h> |
60 | #include <linux/of.h> |
61 | #include <linux/of_address.h> |
62 | #include <linux/of_platform.h> |
63 | #include <linux/platform_device.h> |
64 | #include <linux/pm.h> |
65 | #include <linux/pm_domain.h> |
66 | #include <linux/slab.h> |
67 | |
68 | /* SCU Power Mode Protocol definition */ |
69 | struct imx_sc_msg_req_set_resource_power_mode { |
70 | struct imx_sc_rpc_msg hdr; |
71 | u16 resource; |
72 | u8 mode; |
73 | } __packed __aligned(4); |
74 | |
75 | struct req_get_resource_mode { |
76 | u16 resource; |
77 | }; |
78 | |
79 | struct resp_get_resource_mode { |
80 | u8 mode; |
81 | }; |
82 | |
83 | struct imx_sc_msg_req_get_resource_power_mode { |
84 | struct imx_sc_rpc_msg hdr; |
85 | union { |
86 | struct req_get_resource_mode req; |
87 | struct resp_get_resource_mode resp; |
88 | } data; |
89 | } __packed __aligned(4); |
90 | |
91 | #define IMX_SCU_PD_NAME_SIZE 20 |
92 | struct imx_sc_pm_domain { |
93 | struct generic_pm_domain pd; |
94 | char name[IMX_SCU_PD_NAME_SIZE]; |
95 | u32 rsrc; |
96 | }; |
97 | |
98 | struct imx_sc_pd_range { |
99 | char *name; |
100 | u32 rsrc; |
101 | u8 num; |
102 | |
103 | /* add domain index */ |
104 | bool postfix; |
105 | u8 start_from; |
106 | }; |
107 | |
108 | struct imx_sc_pd_soc { |
109 | const struct imx_sc_pd_range *pd_ranges; |
110 | u8 num_ranges; |
111 | }; |
112 | |
113 | static int imx_con_rsrc; |
114 | |
115 | /* Align with the IMX_SC_PM_PW_MODE_[OFF,STBY,LP,ON] macros */ |
116 | static const char * const imx_sc_pm_mode[] = { |
117 | "IMX_SC_PM_PW_MODE_OFF", |
118 | "IMX_SC_PM_PW_MODE_STBY", |
119 | "IMX_SC_PM_PW_MODE_LP", |
120 | "IMX_SC_PM_PW_MODE_ON" |
121 | }; |
122 | |
123 | static const struct imx_sc_pd_range imx8qxp_scu_pd_ranges[] = { |
124 | /* LSIO SS */ |
125 | { "pwm", IMX_SC_R_PWM_0, 8, true, 0 }, |
126 | { "gpio", IMX_SC_R_GPIO_0, 8, true, 0 }, |
127 | { "gpt", IMX_SC_R_GPT_0, 5, true, 0 }, |
128 | { "kpp", IMX_SC_R_KPP, 1, false, 0 }, |
129 | { "fspi", IMX_SC_R_FSPI_0, 2, true, 0 }, |
130 | { "mu_a", IMX_SC_R_MU_0A, 14, true, 0 }, |
131 | { "mu_b", IMX_SC_R_MU_5B, 9, true, 5 }, |
132 | |
133 | /* CONN SS */ |
134 | { "usb", IMX_SC_R_USB_0, 2, true, 0 }, |
135 | { "usb0phy", IMX_SC_R_USB_0_PHY, 1, false, 0 }, |
136 | { "usb1phy", IMX_SC_R_USB_1_PHY, 1, false, 0}, |
137 | { "usb2", IMX_SC_R_USB_2, 1, false, 0 }, |
138 | { "usb2phy", IMX_SC_R_USB_2_PHY, 1, false, 0 }, |
139 | { "sdhc", IMX_SC_R_SDHC_0, 3, true, 0 }, |
140 | { "enet", IMX_SC_R_ENET_0, 2, true, 0 }, |
141 | { "nand", IMX_SC_R_NAND, 1, false, 0 }, |
142 | { "mlb", IMX_SC_R_MLB_0, 1, true, 0 }, |
143 | |
144 | /* AUDIO SS */ |
145 | { "audio-pll0", IMX_SC_R_AUDIO_PLL_0, 1, false, 0 }, |
146 | { "audio-pll1", IMX_SC_R_AUDIO_PLL_1, 1, false, 0 }, |
147 | { "audio-clk-0", IMX_SC_R_AUDIO_CLK_0, 1, false, 0 }, |
148 | { "audio-clk-1", IMX_SC_R_AUDIO_CLK_1, 1, false, 0 }, |
149 | { "mclk-out-0", IMX_SC_R_MCLK_OUT_0, 1, false, 0 }, |
150 | { "mclk-out-1", IMX_SC_R_MCLK_OUT_1, 1, false, 0 }, |
151 | { "dma0-ch", IMX_SC_R_DMA_0_CH0, 32, true, 0 }, |
152 | { "dma1-ch", IMX_SC_R_DMA_1_CH0, 16, true, 0 }, |
153 | { "dma2-ch-0", IMX_SC_R_DMA_2_CH0, 5, true, 0 }, |
154 | { "dma2-ch-1", IMX_SC_R_DMA_2_CH5, 27, true, 0 }, |
155 | { "dma3-ch", IMX_SC_R_DMA_3_CH0, 32, true, 0 }, |
156 | { "asrc0", IMX_SC_R_ASRC_0, 1, false, 0 }, |
157 | { "asrc1", IMX_SC_R_ASRC_1, 1, false, 0 }, |
158 | { "esai0", IMX_SC_R_ESAI_0, 1, false, 0 }, |
159 | { "esai1", IMX_SC_R_ESAI_1, 1, false, 0 }, |
160 | { "spdif0", IMX_SC_R_SPDIF_0, 1, false, 0 }, |
161 | { "spdif1", IMX_SC_R_SPDIF_1, 1, false, 0 }, |
162 | { "sai", IMX_SC_R_SAI_0, 3, true, 0 }, |
163 | { "sai3", IMX_SC_R_SAI_3, 1, false, 0 }, |
164 | { "sai4", IMX_SC_R_SAI_4, 1, false, 0 }, |
165 | { "sai5", IMX_SC_R_SAI_5, 1, false, 0 }, |
166 | { "sai6", IMX_SC_R_SAI_6, 1, false, 0 }, |
167 | { "sai7", IMX_SC_R_SAI_7, 1, false, 0 }, |
168 | { "amix", IMX_SC_R_AMIX, 1, false, 0 }, |
169 | { "mqs0", IMX_SC_R_MQS_0, 1, false, 0 }, |
170 | { "dsp", IMX_SC_R_DSP, 1, false, 0 }, |
171 | { "dsp-ram", IMX_SC_R_DSP_RAM, 1, false, 0 }, |
172 | |
173 | /* DMA SS */ |
174 | { "can", IMX_SC_R_CAN_0, 3, true, 0 }, |
175 | { "ftm", IMX_SC_R_FTM_0, 2, true, 0 }, |
176 | { "lpi2c", IMX_SC_R_I2C_0, 5, true, 0 }, |
177 | { "adc", IMX_SC_R_ADC_0, 2, true, 0 }, |
178 | { "lcd", IMX_SC_R_LCD_0, 1, true, 0 }, |
179 | { "lcd-pll", IMX_SC_R_ELCDIF_PLL, 1, true, 0 }, |
180 | { "lcd0-pwm", IMX_SC_R_LCD_0_PWM_0, 1, true, 0 }, |
181 | { "lpuart", IMX_SC_R_UART_0, 5, true, 0 }, |
182 | { "sim", IMX_SC_R_EMVSIM_0, 2, true, 0 }, |
183 | { "lpspi", IMX_SC_R_SPI_0, 4, true, 0 }, |
184 | { "irqstr_dsp", IMX_SC_R_IRQSTR_DSP, 1, false, 0 }, |
185 | |
186 | /* VPU SS */ |
187 | { "vpu", IMX_SC_R_VPU, 1, false, 0 }, |
188 | { "vpu-pid", IMX_SC_R_VPU_PID0, 8, true, 0 }, |
189 | { "vpu-dec0", IMX_SC_R_VPU_DEC_0, 1, false, 0 }, |
190 | { "vpu-enc0", IMX_SC_R_VPU_ENC_0, 1, false, 0 }, |
191 | { "vpu-enc1", IMX_SC_R_VPU_ENC_1, 1, false, 0 }, |
192 | { "vpu-mu0", IMX_SC_R_VPU_MU_0, 1, false, 0 }, |
193 | { "vpu-mu1", IMX_SC_R_VPU_MU_1, 1, false, 0 }, |
194 | { "vpu-mu2", IMX_SC_R_VPU_MU_2, 1, false, 0 }, |
195 | |
196 | /* GPU SS */ |
197 | { "gpu0-pid", IMX_SC_R_GPU_0_PID0, 4, true, 0 }, |
198 | { "gpu1-pid", IMX_SC_R_GPU_1_PID0, 4, true, 0 }, |
199 | |
200 | |
201 | /* HSIO SS */ |
202 | { "pcie-a", IMX_SC_R_PCIE_A, 1, false, 0 }, |
203 | { "serdes-0", IMX_SC_R_SERDES_0, 1, false, 0 }, |
204 | { "pcie-b", IMX_SC_R_PCIE_B, 1, false, 0 }, |
205 | { "serdes-1", IMX_SC_R_SERDES_1, 1, false, 0 }, |
206 | { "sata-0", IMX_SC_R_SATA_0, 1, false, 0 }, |
207 | { "hsio-gpio", IMX_SC_R_HSIO_GPIO, 1, false, 0 }, |
208 | |
209 | /* MIPI SS */ |
210 | { "mipi0", IMX_SC_R_MIPI_0, 1, false, 0 }, |
211 | { "mipi0-pwm0", IMX_SC_R_MIPI_0_PWM_0, 1, false, 0 }, |
212 | { "mipi0-i2c", IMX_SC_R_MIPI_0_I2C_0, 2, true, 0 }, |
213 | |
214 | { "mipi1", IMX_SC_R_MIPI_1, 1, false, 0 }, |
215 | { "mipi1-pwm0", IMX_SC_R_MIPI_1_PWM_0, 1, false, 0 }, |
216 | { "mipi1-i2c", IMX_SC_R_MIPI_1_I2C_0, 2, true, 0 }, |
217 | |
218 | /* LVDS SS */ |
219 | { "lvds0", IMX_SC_R_LVDS_0, 1, false, 0 }, |
220 | { "lvds0-pwm", IMX_SC_R_LVDS_0_PWM_0, 1, false, 0 }, |
221 | { "lvds0-lpi2c", IMX_SC_R_LVDS_0_I2C_0, 2, true, 0 }, |
222 | { "lvds1", IMX_SC_R_LVDS_1, 1, false, 0 }, |
223 | { "lvds1-pwm", IMX_SC_R_LVDS_1_PWM_0, 1, false, 0 }, |
224 | { "lvds1-lpi2c", IMX_SC_R_LVDS_1_I2C_0, 2, true, 0 }, |
225 | |
226 | { "mipi1", IMX_SC_R_MIPI_1, 1, 0 }, |
227 | { "mipi1-pwm0", IMX_SC_R_MIPI_1_PWM_0, 1, 0 }, |
228 | { "mipi1-i2c", IMX_SC_R_MIPI_1_I2C_0, 2, 1 }, |
229 | { "lvds1", IMX_SC_R_LVDS_1, 1, 0 }, |
230 | |
231 | /* DC SS */ |
232 | { "dc0", IMX_SC_R_DC_0, 1, false, 0 }, |
233 | { "dc0-pll", IMX_SC_R_DC_0_PLL_0, 2, true, 0 }, |
234 | { "dc0-video", IMX_SC_R_DC_0_VIDEO0, 2, true, 0 }, |
235 | |
236 | { "dc1", IMX_SC_R_DC_1, 1, false, 0 }, |
237 | { "dc1-pll", IMX_SC_R_DC_1_PLL_0, 2, true, 0 }, |
238 | { "dc1-video", IMX_SC_R_DC_1_VIDEO0, 2, true, 0 }, |
239 | |
240 | /* CM40 SS */ |
241 | { "cm40-i2c", IMX_SC_R_M4_0_I2C, 1, false, 0 }, |
242 | { "cm40-intmux", IMX_SC_R_M4_0_INTMUX, 1, false, 0 }, |
243 | { "cm40-pid", IMX_SC_R_M4_0_PID0, 5, true, 0}, |
244 | { "cm40-mu-a1", IMX_SC_R_M4_0_MU_1A, 1, false, 0}, |
245 | { "cm40-lpuart", IMX_SC_R_M4_0_UART, 1, false, 0}, |
246 | |
247 | /* CM41 SS */ |
248 | { "cm41-i2c", IMX_SC_R_M4_1_I2C, 1, false, 0 }, |
249 | { "cm41-intmux", IMX_SC_R_M4_1_INTMUX, 1, false, 0 }, |
250 | { "cm41-pid", IMX_SC_R_M4_1_PID0, 5, true, 0}, |
251 | { "cm41-mu-a1", IMX_SC_R_M4_1_MU_1A, 1, false, 0}, |
252 | { "cm41-lpuart", IMX_SC_R_M4_1_UART, 1, false, 0}, |
253 | |
254 | /* CM41 SS */ |
255 | { "cm41_i2c", IMX_SC_R_M4_1_I2C, 1, false, 0 }, |
256 | { "cm41_intmux", IMX_SC_R_M4_1_INTMUX, 1, false, 0 }, |
257 | |
258 | /* DB SS */ |
259 | { "perf", IMX_SC_R_PERF, 1, false, 0}, |
260 | |
261 | /* IMAGE SS */ |
262 | { "img-jpegdec-mp", IMX_SC_R_MJPEG_DEC_MP, 1, false, 0 }, |
263 | { "img-jpegdec-s0", IMX_SC_R_MJPEG_DEC_S0, 4, true, 0 }, |
264 | { "img-jpegenc-mp", IMX_SC_R_MJPEG_ENC_MP, 1, false, 0 }, |
265 | { "img-jpegenc-s0", IMX_SC_R_MJPEG_ENC_S0, 4, true, 0 }, |
266 | |
267 | /* SECO SS */ |
268 | { "seco_mu", IMX_SC_R_SECO_MU_2, 3, true, 2}, |
269 | |
270 | /* V2X SS */ |
271 | { "v2x_mu", IMX_SC_R_V2X_MU_0, 2, true, 0}, |
272 | { "v2x_mu", IMX_SC_R_V2X_MU_2, 1, true, 2}, |
273 | { "v2x_mu", IMX_SC_R_V2X_MU_3, 2, true, 3}, |
274 | { "img-pdma", IMX_SC_R_ISI_CH0, 8, true, 0 }, |
275 | { "img-csi0", IMX_SC_R_CSI_0, 1, false, 0 }, |
276 | { "img-csi0-i2c0", IMX_SC_R_CSI_0_I2C_0, 1, false, 0 }, |
277 | { "img-csi0-pwm0", IMX_SC_R_CSI_0_PWM_0, 1, false, 0 }, |
278 | { "img-csi1", IMX_SC_R_CSI_1, 1, false, 0 }, |
279 | { "img-csi1-i2c0", IMX_SC_R_CSI_1_I2C_0, 1, false, 0 }, |
280 | { "img-csi1-pwm0", IMX_SC_R_CSI_1_PWM_0, 1, false, 0 }, |
281 | { "img-parallel", IMX_SC_R_PI_0, 1, false, 0 }, |
282 | { "img-parallel-i2c0", IMX_SC_R_PI_0_I2C_0, 1, false, 0 }, |
283 | { "img-parallel-pwm0", IMX_SC_R_PI_0_PWM_0, 2, true, 0 }, |
284 | { "img-parallel-pll", IMX_SC_R_PI_0_PLL, 1, false, 0 }, |
285 | |
286 | /* HDMI TX SS */ |
287 | { "hdmi-tx", IMX_SC_R_HDMI, 1, false, 0}, |
288 | { "hdmi-tx-i2s", IMX_SC_R_HDMI_I2S, 1, false, 0}, |
289 | { "hdmi-tx-i2c0", IMX_SC_R_HDMI_I2C_0, 1, false, 0}, |
290 | { "hdmi-tx-pll0", IMX_SC_R_HDMI_PLL_0, 1, false, 0}, |
291 | { "hdmi-tx-pll1", IMX_SC_R_HDMI_PLL_1, 1, false, 0}, |
292 | |
293 | /* HDMI RX SS */ |
294 | { "hdmi-rx", IMX_SC_R_HDMI_RX, 1, false, 0}, |
295 | { "hdmi-rx-pwm", IMX_SC_R_HDMI_RX_PWM_0, 1, false, 0}, |
296 | { "hdmi-rx-i2c0", IMX_SC_R_HDMI_RX_I2C_0, 1, false, 0}, |
297 | { "hdmi-rx-bypass", IMX_SC_R_HDMI_RX_BYPASS, 1, false, 0}, |
298 | |
299 | /* SECURITY SS */ |
300 | { "sec-jr", IMX_SC_R_CAAM_JR2, 2, true, 2}, |
301 | |
302 | /* BOARD SS */ |
303 | { "board", IMX_SC_R_BOARD_R0, 8, true, 0}, |
304 | }; |
305 | |
306 | static const struct imx_sc_pd_soc imx8qxp_scu_pd = { |
307 | .pd_ranges = imx8qxp_scu_pd_ranges, |
308 | .num_ranges = ARRAY_SIZE(imx8qxp_scu_pd_ranges), |
309 | }; |
310 | |
311 | static struct imx_sc_ipc *pm_ipc_handle; |
312 | |
313 | static inline struct imx_sc_pm_domain * |
314 | to_imx_sc_pd(struct generic_pm_domain *genpd) |
315 | { |
316 | return container_of(genpd, struct imx_sc_pm_domain, pd); |
317 | } |
318 | |
319 | static void imx_sc_pd_get_console_rsrc(void) |
320 | { |
321 | struct of_phandle_args specs; |
322 | int ret; |
323 | |
324 | if (!of_stdout) |
325 | return; |
326 | |
327 | ret = of_parse_phandle_with_args(np: of_stdout, list_name: "power-domains", |
328 | cells_name: "#power-domain-cells", |
329 | index: 0, out_args: &specs); |
330 | if (ret) |
331 | return; |
332 | |
333 | imx_con_rsrc = specs.args[0]; |
334 | } |
335 | |
336 | static int imx_sc_get_pd_power(struct device *dev, u32 rsrc) |
337 | { |
338 | struct imx_sc_msg_req_get_resource_power_mode msg; |
339 | struct imx_sc_rpc_msg *hdr = &msg.hdr; |
340 | int ret; |
341 | |
342 | hdr->ver = IMX_SC_RPC_VERSION; |
343 | hdr->svc = IMX_SC_RPC_SVC_PM; |
344 | hdr->func = IMX_SC_PM_FUNC_GET_RESOURCE_POWER_MODE; |
345 | hdr->size = 2; |
346 | |
347 | msg.data.req.resource = rsrc; |
348 | |
349 | ret = imx_scu_call_rpc(ipc: pm_ipc_handle, msg: &msg, have_resp: true); |
350 | if (ret) |
351 | dev_err(dev, "failed to get power resource %d mode, ret %d\n", |
352 | rsrc, ret); |
353 | |
354 | return msg.data.resp.mode; |
355 | } |
356 | |
357 | static int imx_sc_pd_power(struct generic_pm_domain *domain, bool power_on) |
358 | { |
359 | struct imx_sc_msg_req_set_resource_power_mode msg; |
360 | struct imx_sc_rpc_msg *hdr = &msg.hdr; |
361 | struct imx_sc_pm_domain *pd; |
362 | int ret; |
363 | |
364 | pd = to_imx_sc_pd(genpd: domain); |
365 | |
366 | hdr->ver = IMX_SC_RPC_VERSION; |
367 | hdr->svc = IMX_SC_RPC_SVC_PM; |
368 | hdr->func = IMX_SC_PM_FUNC_SET_RESOURCE_POWER_MODE; |
369 | hdr->size = 2; |
370 | |
371 | msg.resource = pd->rsrc; |
372 | msg.mode = power_on ? IMX_SC_PM_PW_MODE_ON : IMX_SC_PM_PW_MODE_LP; |
373 | |
374 | /* keep uart console power on for no_console_suspend */ |
375 | if (imx_con_rsrc == pd->rsrc && !console_suspend_enabled && !power_on) |
376 | return -EBUSY; |
377 | |
378 | ret = imx_scu_call_rpc(ipc: pm_ipc_handle, msg: &msg, have_resp: true); |
379 | if (ret) |
380 | dev_err(&domain->dev, "failed to power %s resource %d ret %d\n", |
381 | power_on ? "up": "off", pd->rsrc, ret); |
382 | |
383 | return ret; |
384 | } |
385 | |
386 | static int imx_sc_pd_power_on(struct generic_pm_domain *domain) |
387 | { |
388 | return imx_sc_pd_power(domain, power_on: true); |
389 | } |
390 | |
391 | static int imx_sc_pd_power_off(struct generic_pm_domain *domain) |
392 | { |
393 | return imx_sc_pd_power(domain, power_on: false); |
394 | } |
395 | |
396 | static struct generic_pm_domain *imx_scu_pd_xlate(const struct of_phandle_args *spec, |
397 | void *data) |
398 | { |
399 | struct generic_pm_domain *domain = ERR_PTR(error: -ENOENT); |
400 | struct genpd_onecell_data *pd_data = data; |
401 | unsigned int i; |
402 | |
403 | for (i = 0; i < pd_data->num_domains; i++) { |
404 | struct imx_sc_pm_domain *sc_pd; |
405 | |
406 | sc_pd = to_imx_sc_pd(genpd: pd_data->domains[i]); |
407 | if (sc_pd->rsrc == spec->args[0]) { |
408 | domain = &sc_pd->pd; |
409 | break; |
410 | } |
411 | } |
412 | |
413 | return domain; |
414 | } |
415 | |
416 | static struct imx_sc_pm_domain * |
417 | imx_scu_add_pm_domain(struct device *dev, int idx, |
418 | const struct imx_sc_pd_range *pd_ranges) |
419 | { |
420 | struct imx_sc_pm_domain *sc_pd; |
421 | bool is_off; |
422 | int mode, ret; |
423 | |
424 | if (!imx_sc_rm_is_resource_owned(ipc: pm_ipc_handle, resource: pd_ranges->rsrc + idx)) |
425 | return NULL; |
426 | |
427 | sc_pd = devm_kzalloc(dev, size: sizeof(*sc_pd), GFP_KERNEL); |
428 | if (!sc_pd) |
429 | return ERR_PTR(error: -ENOMEM); |
430 | |
431 | sc_pd->rsrc = pd_ranges->rsrc + idx; |
432 | sc_pd->pd.power_off = imx_sc_pd_power_off; |
433 | sc_pd->pd.power_on = imx_sc_pd_power_on; |
434 | |
435 | if (pd_ranges->postfix) |
436 | snprintf(buf: sc_pd->name, size: sizeof(sc_pd->name), |
437 | fmt: "%s%i", pd_ranges->name, pd_ranges->start_from + idx); |
438 | else |
439 | snprintf(buf: sc_pd->name, size: sizeof(sc_pd->name), |
440 | fmt: "%s", pd_ranges->name); |
441 | |
442 | sc_pd->pd.name = sc_pd->name; |
443 | if (imx_con_rsrc == sc_pd->rsrc) |
444 | sc_pd->pd.flags = GENPD_FLAG_RPM_ALWAYS_ON; |
445 | |
446 | mode = imx_sc_get_pd_power(dev, rsrc: pd_ranges->rsrc + idx); |
447 | if (mode == IMX_SC_PM_PW_MODE_ON) |
448 | is_off = false; |
449 | else |
450 | is_off = true; |
451 | |
452 | dev_dbg(dev, "%s : %s\n", sc_pd->name, imx_sc_pm_mode[mode]); |
453 | |
454 | if (sc_pd->rsrc >= IMX_SC_R_LAST) { |
455 | dev_warn(dev, "invalid pd %s rsrc id %d found", |
456 | sc_pd->name, sc_pd->rsrc); |
457 | |
458 | devm_kfree(dev, p: sc_pd); |
459 | return NULL; |
460 | } |
461 | |
462 | ret = pm_genpd_init(genpd: &sc_pd->pd, NULL, is_off); |
463 | if (ret) { |
464 | dev_warn(dev, "failed to init pd %s rsrc id %d", |
465 | sc_pd->name, sc_pd->rsrc); |
466 | devm_kfree(dev, p: sc_pd); |
467 | return NULL; |
468 | } |
469 | |
470 | return sc_pd; |
471 | } |
472 | |
473 | static int imx_scu_init_pm_domains(struct device *dev, |
474 | const struct imx_sc_pd_soc *pd_soc) |
475 | { |
476 | const struct imx_sc_pd_range *pd_ranges = pd_soc->pd_ranges; |
477 | struct generic_pm_domain **domains; |
478 | struct genpd_onecell_data *pd_data; |
479 | struct imx_sc_pm_domain *sc_pd; |
480 | u32 count = 0; |
481 | int i, j; |
482 | |
483 | for (i = 0; i < pd_soc->num_ranges; i++) |
484 | count += pd_ranges[i].num; |
485 | |
486 | domains = devm_kcalloc(dev, n: count, size: sizeof(*domains), GFP_KERNEL); |
487 | if (!domains) |
488 | return -ENOMEM; |
489 | |
490 | pd_data = devm_kzalloc(dev, size: sizeof(*pd_data), GFP_KERNEL); |
491 | if (!pd_data) |
492 | return -ENOMEM; |
493 | |
494 | count = 0; |
495 | for (i = 0; i < pd_soc->num_ranges; i++) { |
496 | for (j = 0; j < pd_ranges[i].num; j++) { |
497 | sc_pd = imx_scu_add_pm_domain(dev, idx: j, pd_ranges: &pd_ranges[i]); |
498 | if (IS_ERR_OR_NULL(ptr: sc_pd)) |
499 | continue; |
500 | |
501 | domains[count++] = &sc_pd->pd; |
502 | dev_dbg(dev, "added power domain %s\n", sc_pd->pd.name); |
503 | } |
504 | } |
505 | |
506 | pd_data->domains = domains; |
507 | pd_data->num_domains = count; |
508 | pd_data->xlate = imx_scu_pd_xlate; |
509 | |
510 | of_genpd_add_provider_onecell(np: dev->of_node, data: pd_data); |
511 | |
512 | return 0; |
513 | } |
514 | |
515 | static int imx_sc_pd_probe(struct platform_device *pdev) |
516 | { |
517 | const struct imx_sc_pd_soc *pd_soc; |
518 | int ret; |
519 | |
520 | ret = imx_scu_get_handle(ipc: &pm_ipc_handle); |
521 | if (ret) |
522 | return ret; |
523 | |
524 | pd_soc = of_device_get_match_data(dev: &pdev->dev); |
525 | if (!pd_soc) |
526 | return -ENODEV; |
527 | |
528 | imx_sc_pd_get_console_rsrc(); |
529 | |
530 | return imx_scu_init_pm_domains(dev: &pdev->dev, pd_soc); |
531 | } |
532 | |
533 | static const struct of_device_id imx_sc_pd_match[] = { |
534 | { .compatible = "fsl,imx8qxp-scu-pd", &imx8qxp_scu_pd}, |
535 | { .compatible = "fsl,scu-pd", &imx8qxp_scu_pd}, |
536 | { /* sentinel */ } |
537 | }; |
538 | |
539 | static struct platform_driver imx_sc_pd_driver = { |
540 | .driver = { |
541 | .name = "imx-scu-pd", |
542 | .of_match_table = imx_sc_pd_match, |
543 | .suppress_bind_attrs = true, |
544 | }, |
545 | .probe = imx_sc_pd_probe, |
546 | }; |
547 | builtin_platform_driver(imx_sc_pd_driver); |
548 | |
549 | MODULE_AUTHOR("Dong Aisheng <aisheng.dong@nxp.com>"); |
550 | MODULE_DESCRIPTION("IMX SCU Power Domain driver"); |
551 | MODULE_LICENSE("GPL v2"); |
552 |
Definitions
- imx_sc_msg_req_set_resource_power_mode
- req_get_resource_mode
- resp_get_resource_mode
- imx_sc_msg_req_get_resource_power_mode
- imx_sc_pm_domain
- imx_sc_pd_range
- imx_sc_pd_soc
- imx_con_rsrc
- imx_sc_pm_mode
- imx8qxp_scu_pd_ranges
- imx8qxp_scu_pd
- pm_ipc_handle
- to_imx_sc_pd
- imx_sc_pd_get_console_rsrc
- imx_sc_get_pd_power
- imx_sc_pd_power
- imx_sc_pd_power_on
- imx_sc_pd_power_off
- imx_scu_pd_xlate
- imx_scu_add_pm_domain
- imx_scu_init_pm_domains
- imx_sc_pd_probe
- imx_sc_pd_match
Improve your Profiling and Debugging skills
Find out more