Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v5.9-rc1 370 lines 11 kB view raw
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 */ 60struct imx_sc_msg_req_set_resource_power_mode { 61 struct imx_sc_rpc_msg hdr; 62 u16 resource; 63 u8 mode; 64} __packed __aligned(4); 65 66#define IMX_SCU_PD_NAME_SIZE 20 67struct imx_sc_pm_domain { 68 struct generic_pm_domain pd; 69 char name[IMX_SCU_PD_NAME_SIZE]; 70 u32 rsrc; 71}; 72 73struct imx_sc_pd_range { 74 char *name; 75 u32 rsrc; 76 u8 num; 77 78 /* add domain index */ 79 bool postfix; 80 u8 start_from; 81}; 82 83struct imx_sc_pd_soc { 84 const struct imx_sc_pd_range *pd_ranges; 85 u8 num_ranges; 86}; 87 88static const struct imx_sc_pd_range imx8qxp_scu_pd_ranges[] = { 89 /* LSIO SS */ 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 }, 95 { "mu_a", IMX_SC_R_MU_0A, 14, true, 0 }, 96 { "mu_b", IMX_SC_R_MU_5B, 9, true, 5 }, 97 98 /* CONN SS */ 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 { "audio-clk-1", IMX_SC_R_AUDIO_CLK_1, 1, false, 0 }, 113 { "dma0-ch", IMX_SC_R_DMA_0_CH0, 16, true, 0 }, 114 { "dma1-ch", IMX_SC_R_DMA_1_CH0, 16, true, 0 }, 115 { "dma2-ch", IMX_SC_R_DMA_2_CH0, 5, true, 0 }, 116 { "asrc0", IMX_SC_R_ASRC_0, 1, false, 0 }, 117 { "asrc1", IMX_SC_R_ASRC_1, 1, false, 0 }, 118 { "esai0", IMX_SC_R_ESAI_0, 1, false, 0 }, 119 { "spdif0", IMX_SC_R_SPDIF_0, 1, false, 0 }, 120 { "spdif1", IMX_SC_R_SPDIF_1, 1, false, 0 }, 121 { "sai", IMX_SC_R_SAI_0, 3, true, 0 }, 122 { "sai3", IMX_SC_R_SAI_3, 1, false, 0 }, 123 { "sai4", IMX_SC_R_SAI_4, 1, false, 0 }, 124 { "sai5", IMX_SC_R_SAI_5, 1, false, 0 }, 125 { "sai6", IMX_SC_R_SAI_6, 1, false, 0 }, 126 { "sai7", IMX_SC_R_SAI_7, 1, false, 0 }, 127 { "amix", IMX_SC_R_AMIX, 1, false, 0 }, 128 { "mqs0", IMX_SC_R_MQS_0, 1, false, 0 }, 129 { "dsp", IMX_SC_R_DSP, 1, false, 0 }, 130 { "dsp-ram", IMX_SC_R_DSP_RAM, 1, false, 0 }, 131 132 /* DMA SS */ 133 { "can", IMX_SC_R_CAN_0, 3, true, 0 }, 134 { "ftm", IMX_SC_R_FTM_0, 2, true, 0 }, 135 { "lpi2c", IMX_SC_R_I2C_0, 4, true, 0 }, 136 { "adc", IMX_SC_R_ADC_0, 1, true, 0 }, 137 { "lcd", IMX_SC_R_LCD_0, 1, true, 0 }, 138 { "lcd0-pwm", IMX_SC_R_LCD_0_PWM_0, 1, true, 0 }, 139 { "lpuart", IMX_SC_R_UART_0, 4, true, 0 }, 140 { "lpspi", IMX_SC_R_SPI_0, 4, true, 0 }, 141 { "irqstr_dsp", IMX_SC_R_IRQSTR_DSP, 1, false, 0 }, 142 143 /* VPU SS */ 144 { "vpu", IMX_SC_R_VPU, 1, false, 0 }, 145 { "vpu-pid", IMX_SC_R_VPU_PID0, 8, true, 0 }, 146 { "vpu-dec0", IMX_SC_R_VPU_DEC_0, 1, false, 0 }, 147 { "vpu-enc0", IMX_SC_R_VPU_ENC_0, 1, false, 0 }, 148 149 /* GPU SS */ 150 { "gpu0-pid", IMX_SC_R_GPU_0_PID0, 4, true, 0 }, 151 152 /* HSIO SS */ 153 { "pcie-b", IMX_SC_R_PCIE_B, 1, false, 0 }, 154 { "serdes-1", IMX_SC_R_SERDES_1, 1, false, 0 }, 155 { "hsio-gpio", IMX_SC_R_HSIO_GPIO, 1, false, 0 }, 156 157 /* MIPI SS */ 158 { "mipi0", IMX_SC_R_MIPI_0, 1, false, 0 }, 159 { "mipi0-pwm0", IMX_SC_R_MIPI_0_PWM_0, 1, false, 0 }, 160 { "mipi0-i2c", IMX_SC_R_MIPI_0_I2C_0, 2, true, 0 }, 161 162 /* LVDS SS */ 163 { "lvds0", IMX_SC_R_LVDS_0, 1, false, 0 }, 164 165 /* DC SS */ 166 { "dc0", IMX_SC_R_DC_0, 1, false, 0 }, 167 { "dc0-pll", IMX_SC_R_DC_0_PLL_0, 2, true, 0 }, 168 169 /* CM40 SS */ 170 { "cm40-i2c", IMX_SC_R_M4_0_I2C, 1, false, 0 }, 171 { "cm40-intmux", IMX_SC_R_M4_0_INTMUX, 1, false, 0 }, 172 { "cm40-pid", IMX_SC_R_M4_0_PID0, 5, true, 0}, 173 { "cm40-mu-a1", IMX_SC_R_M4_0_MU_1A, 1, false, 0}, 174 { "cm40-lpuart", IMX_SC_R_M4_0_UART, 1, false, 0}, 175 176 /* CM41 SS */ 177 { "cm41-i2c", IMX_SC_R_M4_1_I2C, 1, false, 0 }, 178 { "cm41-intmux", IMX_SC_R_M4_1_INTMUX, 1, false, 0 }, 179 { "cm41-pid", IMX_SC_R_M4_1_PID0, 5, true, 0}, 180 { "cm41-mu-a1", IMX_SC_R_M4_1_MU_1A, 1, false, 0}, 181 { "cm41-lpuart", IMX_SC_R_M4_1_UART, 1, false, 0}, 182}; 183 184static const struct imx_sc_pd_soc imx8qxp_scu_pd = { 185 .pd_ranges = imx8qxp_scu_pd_ranges, 186 .num_ranges = ARRAY_SIZE(imx8qxp_scu_pd_ranges), 187}; 188 189static struct imx_sc_ipc *pm_ipc_handle; 190 191static inline struct imx_sc_pm_domain * 192to_imx_sc_pd(struct generic_pm_domain *genpd) 193{ 194 return container_of(genpd, struct imx_sc_pm_domain, pd); 195} 196 197static int imx_sc_pd_power(struct generic_pm_domain *domain, bool power_on) 198{ 199 struct imx_sc_msg_req_set_resource_power_mode msg; 200 struct imx_sc_rpc_msg *hdr = &msg.hdr; 201 struct imx_sc_pm_domain *pd; 202 int ret; 203 204 pd = to_imx_sc_pd(domain); 205 206 hdr->ver = IMX_SC_RPC_VERSION; 207 hdr->svc = IMX_SC_RPC_SVC_PM; 208 hdr->func = IMX_SC_PM_FUNC_SET_RESOURCE_POWER_MODE; 209 hdr->size = 2; 210 211 msg.resource = pd->rsrc; 212 msg.mode = power_on ? IMX_SC_PM_PW_MODE_ON : IMX_SC_PM_PW_MODE_LP; 213 214 ret = imx_scu_call_rpc(pm_ipc_handle, &msg, true); 215 if (ret) 216 dev_err(&domain->dev, "failed to power %s resource %d ret %d\n", 217 power_on ? "up" : "off", pd->rsrc, ret); 218 219 return ret; 220} 221 222static int imx_sc_pd_power_on(struct generic_pm_domain *domain) 223{ 224 return imx_sc_pd_power(domain, true); 225} 226 227static int imx_sc_pd_power_off(struct generic_pm_domain *domain) 228{ 229 return imx_sc_pd_power(domain, false); 230} 231 232static struct generic_pm_domain *imx_scu_pd_xlate(struct of_phandle_args *spec, 233 void *data) 234{ 235 struct generic_pm_domain *domain = ERR_PTR(-ENOENT); 236 struct genpd_onecell_data *pd_data = data; 237 unsigned int i; 238 239 for (i = 0; i < pd_data->num_domains; i++) { 240 struct imx_sc_pm_domain *sc_pd; 241 242 sc_pd = to_imx_sc_pd(pd_data->domains[i]); 243 if (sc_pd->rsrc == spec->args[0]) { 244 domain = &sc_pd->pd; 245 break; 246 } 247 } 248 249 return domain; 250} 251 252static struct imx_sc_pm_domain * 253imx_scu_add_pm_domain(struct device *dev, int idx, 254 const struct imx_sc_pd_range *pd_ranges) 255{ 256 struct imx_sc_pm_domain *sc_pd; 257 int ret; 258 259 sc_pd = devm_kzalloc(dev, sizeof(*sc_pd), GFP_KERNEL); 260 if (!sc_pd) 261 return ERR_PTR(-ENOMEM); 262 263 sc_pd->rsrc = pd_ranges->rsrc + idx; 264 sc_pd->pd.power_off = imx_sc_pd_power_off; 265 sc_pd->pd.power_on = imx_sc_pd_power_on; 266 267 if (pd_ranges->postfix) 268 snprintf(sc_pd->name, sizeof(sc_pd->name), 269 "%s%i", pd_ranges->name, pd_ranges->start_from + idx); 270 else 271 snprintf(sc_pd->name, sizeof(sc_pd->name), 272 "%s", pd_ranges->name); 273 274 sc_pd->pd.name = sc_pd->name; 275 276 if (sc_pd->rsrc >= IMX_SC_R_LAST) { 277 dev_warn(dev, "invalid pd %s rsrc id %d found", 278 sc_pd->name, sc_pd->rsrc); 279 280 devm_kfree(dev, sc_pd); 281 return NULL; 282 } 283 284 ret = pm_genpd_init(&sc_pd->pd, NULL, true); 285 if (ret) { 286 dev_warn(dev, "failed to init pd %s rsrc id %d", 287 sc_pd->name, sc_pd->rsrc); 288 devm_kfree(dev, sc_pd); 289 return NULL; 290 } 291 292 return sc_pd; 293} 294 295static int imx_scu_init_pm_domains(struct device *dev, 296 const struct imx_sc_pd_soc *pd_soc) 297{ 298 const struct imx_sc_pd_range *pd_ranges = pd_soc->pd_ranges; 299 struct generic_pm_domain **domains; 300 struct genpd_onecell_data *pd_data; 301 struct imx_sc_pm_domain *sc_pd; 302 u32 count = 0; 303 int i, j; 304 305 for (i = 0; i < pd_soc->num_ranges; i++) 306 count += pd_ranges[i].num; 307 308 domains = devm_kcalloc(dev, count, sizeof(*domains), GFP_KERNEL); 309 if (!domains) 310 return -ENOMEM; 311 312 pd_data = devm_kzalloc(dev, sizeof(*pd_data), GFP_KERNEL); 313 if (!pd_data) 314 return -ENOMEM; 315 316 count = 0; 317 for (i = 0; i < pd_soc->num_ranges; i++) { 318 for (j = 0; j < pd_ranges[i].num; j++) { 319 sc_pd = imx_scu_add_pm_domain(dev, j, &pd_ranges[i]); 320 if (IS_ERR_OR_NULL(sc_pd)) 321 continue; 322 323 domains[count++] = &sc_pd->pd; 324 dev_dbg(dev, "added power domain %s\n", sc_pd->pd.name); 325 } 326 } 327 328 pd_data->domains = domains; 329 pd_data->num_domains = count; 330 pd_data->xlate = imx_scu_pd_xlate; 331 332 of_genpd_add_provider_onecell(dev->of_node, pd_data); 333 334 return 0; 335} 336 337static int imx_sc_pd_probe(struct platform_device *pdev) 338{ 339 const struct imx_sc_pd_soc *pd_soc; 340 int ret; 341 342 ret = imx_scu_get_handle(&pm_ipc_handle); 343 if (ret) 344 return ret; 345 346 pd_soc = of_device_get_match_data(&pdev->dev); 347 if (!pd_soc) 348 return -ENODEV; 349 350 return imx_scu_init_pm_domains(&pdev->dev, pd_soc); 351} 352 353static const struct of_device_id imx_sc_pd_match[] = { 354 { .compatible = "fsl,imx8qxp-scu-pd", &imx8qxp_scu_pd}, 355 { .compatible = "fsl,scu-pd", &imx8qxp_scu_pd}, 356 { /* sentinel */ } 357}; 358 359static struct platform_driver imx_sc_pd_driver = { 360 .driver = { 361 .name = "imx-scu-pd", 362 .of_match_table = imx_sc_pd_match, 363 }, 364 .probe = imx_sc_pd_probe, 365}; 366builtin_platform_driver(imx_sc_pd_driver); 367 368MODULE_AUTHOR("Dong Aisheng <aisheng.dong@nxp.com>"); 369MODULE_DESCRIPTION("IMX SCU Power Domain driver"); 370MODULE_LICENSE("GPL v2");