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 v4.5-rc4 490 lines 11 kB view raw
1/* 2 * Rockchip Generic power domain support. 3 * 4 * Copyright (c) 2015 ROCKCHIP, Co. Ltd. 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 2 as 8 * published by the Free Software Foundation. 9 */ 10 11#include <linux/io.h> 12#include <linux/err.h> 13#include <linux/pm_clock.h> 14#include <linux/pm_domain.h> 15#include <linux/of_address.h> 16#include <linux/of_platform.h> 17#include <linux/clk.h> 18#include <linux/regmap.h> 19#include <linux/mfd/syscon.h> 20#include <dt-bindings/power/rk3288-power.h> 21 22struct rockchip_domain_info { 23 int pwr_mask; 24 int status_mask; 25 int req_mask; 26 int idle_mask; 27 int ack_mask; 28}; 29 30struct rockchip_pmu_info { 31 u32 pwr_offset; 32 u32 status_offset; 33 u32 req_offset; 34 u32 idle_offset; 35 u32 ack_offset; 36 37 u32 core_pwrcnt_offset; 38 u32 gpu_pwrcnt_offset; 39 40 unsigned int core_power_transition_time; 41 unsigned int gpu_power_transition_time; 42 43 int num_domains; 44 const struct rockchip_domain_info *domain_info; 45}; 46 47struct rockchip_pm_domain { 48 struct generic_pm_domain genpd; 49 const struct rockchip_domain_info *info; 50 struct rockchip_pmu *pmu; 51 int num_clks; 52 struct clk *clks[]; 53}; 54 55struct rockchip_pmu { 56 struct device *dev; 57 struct regmap *regmap; 58 const struct rockchip_pmu_info *info; 59 struct mutex mutex; /* mutex lock for pmu */ 60 struct genpd_onecell_data genpd_data; 61 struct generic_pm_domain *domains[]; 62}; 63 64#define to_rockchip_pd(gpd) container_of(gpd, struct rockchip_pm_domain, genpd) 65 66#define DOMAIN(pwr, status, req, idle, ack) \ 67{ \ 68 .pwr_mask = BIT(pwr), \ 69 .status_mask = BIT(status), \ 70 .req_mask = BIT(req), \ 71 .idle_mask = BIT(idle), \ 72 .ack_mask = BIT(ack), \ 73} 74 75#define DOMAIN_RK3288(pwr, status, req) \ 76 DOMAIN(pwr, status, req, req, (req) + 16) 77 78static bool rockchip_pmu_domain_is_idle(struct rockchip_pm_domain *pd) 79{ 80 struct rockchip_pmu *pmu = pd->pmu; 81 const struct rockchip_domain_info *pd_info = pd->info; 82 unsigned int val; 83 84 regmap_read(pmu->regmap, pmu->info->idle_offset, &val); 85 return (val & pd_info->idle_mask) == pd_info->idle_mask; 86} 87 88static int rockchip_pmu_set_idle_request(struct rockchip_pm_domain *pd, 89 bool idle) 90{ 91 const struct rockchip_domain_info *pd_info = pd->info; 92 struct rockchip_pmu *pmu = pd->pmu; 93 unsigned int val; 94 95 regmap_update_bits(pmu->regmap, pmu->info->req_offset, 96 pd_info->req_mask, idle ? -1U : 0); 97 98 dsb(sy); 99 100 do { 101 regmap_read(pmu->regmap, pmu->info->ack_offset, &val); 102 } while ((val & pd_info->ack_mask) != (idle ? pd_info->ack_mask : 0)); 103 104 while (rockchip_pmu_domain_is_idle(pd) != idle) 105 cpu_relax(); 106 107 return 0; 108} 109 110static bool rockchip_pmu_domain_is_on(struct rockchip_pm_domain *pd) 111{ 112 struct rockchip_pmu *pmu = pd->pmu; 113 unsigned int val; 114 115 regmap_read(pmu->regmap, pmu->info->status_offset, &val); 116 117 /* 1'b0: power on, 1'b1: power off */ 118 return !(val & pd->info->status_mask); 119} 120 121static void rockchip_do_pmu_set_power_domain(struct rockchip_pm_domain *pd, 122 bool on) 123{ 124 struct rockchip_pmu *pmu = pd->pmu; 125 126 regmap_update_bits(pmu->regmap, pmu->info->pwr_offset, 127 pd->info->pwr_mask, on ? 0 : -1U); 128 129 dsb(sy); 130 131 while (rockchip_pmu_domain_is_on(pd) != on) 132 cpu_relax(); 133} 134 135static int rockchip_pd_power(struct rockchip_pm_domain *pd, bool power_on) 136{ 137 int i; 138 139 mutex_lock(&pd->pmu->mutex); 140 141 if (rockchip_pmu_domain_is_on(pd) != power_on) { 142 for (i = 0; i < pd->num_clks; i++) 143 clk_enable(pd->clks[i]); 144 145 if (!power_on) { 146 /* FIXME: add code to save AXI_QOS */ 147 148 /* if powering down, idle request to NIU first */ 149 rockchip_pmu_set_idle_request(pd, true); 150 } 151 152 rockchip_do_pmu_set_power_domain(pd, power_on); 153 154 if (power_on) { 155 /* if powering up, leave idle mode */ 156 rockchip_pmu_set_idle_request(pd, false); 157 158 /* FIXME: add code to restore AXI_QOS */ 159 } 160 161 for (i = pd->num_clks - 1; i >= 0; i--) 162 clk_disable(pd->clks[i]); 163 } 164 165 mutex_unlock(&pd->pmu->mutex); 166 return 0; 167} 168 169static int rockchip_pd_power_on(struct generic_pm_domain *domain) 170{ 171 struct rockchip_pm_domain *pd = to_rockchip_pd(domain); 172 173 return rockchip_pd_power(pd, true); 174} 175 176static int rockchip_pd_power_off(struct generic_pm_domain *domain) 177{ 178 struct rockchip_pm_domain *pd = to_rockchip_pd(domain); 179 180 return rockchip_pd_power(pd, false); 181} 182 183static int rockchip_pd_attach_dev(struct generic_pm_domain *genpd, 184 struct device *dev) 185{ 186 struct clk *clk; 187 int i; 188 int error; 189 190 dev_dbg(dev, "attaching to power domain '%s'\n", genpd->name); 191 192 error = pm_clk_create(dev); 193 if (error) { 194 dev_err(dev, "pm_clk_create failed %d\n", error); 195 return error; 196 } 197 198 i = 0; 199 while ((clk = of_clk_get(dev->of_node, i++)) && !IS_ERR(clk)) { 200 dev_dbg(dev, "adding clock '%pC' to list of PM clocks\n", clk); 201 error = pm_clk_add_clk(dev, clk); 202 if (error) { 203 dev_err(dev, "pm_clk_add_clk failed %d\n", error); 204 clk_put(clk); 205 pm_clk_destroy(dev); 206 return error; 207 } 208 } 209 210 return 0; 211} 212 213static void rockchip_pd_detach_dev(struct generic_pm_domain *genpd, 214 struct device *dev) 215{ 216 dev_dbg(dev, "detaching from power domain '%s'\n", genpd->name); 217 218 pm_clk_destroy(dev); 219} 220 221static int rockchip_pm_add_one_domain(struct rockchip_pmu *pmu, 222 struct device_node *node) 223{ 224 const struct rockchip_domain_info *pd_info; 225 struct rockchip_pm_domain *pd; 226 struct clk *clk; 227 int clk_cnt; 228 int i; 229 u32 id; 230 int error; 231 232 error = of_property_read_u32(node, "reg", &id); 233 if (error) { 234 dev_err(pmu->dev, 235 "%s: failed to retrieve domain id (reg): %d\n", 236 node->name, error); 237 return -EINVAL; 238 } 239 240 if (id >= pmu->info->num_domains) { 241 dev_err(pmu->dev, "%s: invalid domain id %d\n", 242 node->name, id); 243 return -EINVAL; 244 } 245 246 pd_info = &pmu->info->domain_info[id]; 247 if (!pd_info) { 248 dev_err(pmu->dev, "%s: undefined domain id %d\n", 249 node->name, id); 250 return -EINVAL; 251 } 252 253 clk_cnt = of_count_phandle_with_args(node, "clocks", "#clock-cells"); 254 pd = devm_kzalloc(pmu->dev, 255 sizeof(*pd) + clk_cnt * sizeof(pd->clks[0]), 256 GFP_KERNEL); 257 if (!pd) 258 return -ENOMEM; 259 260 pd->info = pd_info; 261 pd->pmu = pmu; 262 263 for (i = 0; i < clk_cnt; i++) { 264 clk = of_clk_get(node, i); 265 if (IS_ERR(clk)) { 266 error = PTR_ERR(clk); 267 dev_err(pmu->dev, 268 "%s: failed to get clk at index %d: %d\n", 269 node->name, i, error); 270 goto err_out; 271 } 272 273 error = clk_prepare(clk); 274 if (error) { 275 dev_err(pmu->dev, 276 "%s: failed to prepare clk %pC (index %d): %d\n", 277 node->name, clk, i, error); 278 clk_put(clk); 279 goto err_out; 280 } 281 282 pd->clks[pd->num_clks++] = clk; 283 284 dev_dbg(pmu->dev, "added clock '%pC' to domain '%s'\n", 285 clk, node->name); 286 } 287 288 error = rockchip_pd_power(pd, true); 289 if (error) { 290 dev_err(pmu->dev, 291 "failed to power on domain '%s': %d\n", 292 node->name, error); 293 goto err_out; 294 } 295 296 pd->genpd.name = node->name; 297 pd->genpd.power_off = rockchip_pd_power_off; 298 pd->genpd.power_on = rockchip_pd_power_on; 299 pd->genpd.attach_dev = rockchip_pd_attach_dev; 300 pd->genpd.detach_dev = rockchip_pd_detach_dev; 301 pd->genpd.flags = GENPD_FLAG_PM_CLK; 302 pm_genpd_init(&pd->genpd, NULL, false); 303 304 pmu->genpd_data.domains[id] = &pd->genpd; 305 return 0; 306 307err_out: 308 while (--i >= 0) { 309 clk_unprepare(pd->clks[i]); 310 clk_put(pd->clks[i]); 311 } 312 return error; 313} 314 315static void rockchip_pm_remove_one_domain(struct rockchip_pm_domain *pd) 316{ 317 int i; 318 319 for (i = 0; i < pd->num_clks; i++) { 320 clk_unprepare(pd->clks[i]); 321 clk_put(pd->clks[i]); 322 } 323 324 /* protect the zeroing of pm->num_clks */ 325 mutex_lock(&pd->pmu->mutex); 326 pd->num_clks = 0; 327 mutex_unlock(&pd->pmu->mutex); 328 329 /* devm will free our memory */ 330} 331 332static void rockchip_pm_domain_cleanup(struct rockchip_pmu *pmu) 333{ 334 struct generic_pm_domain *genpd; 335 struct rockchip_pm_domain *pd; 336 int i; 337 338 for (i = 0; i < pmu->genpd_data.num_domains; i++) { 339 genpd = pmu->genpd_data.domains[i]; 340 if (genpd) { 341 pd = to_rockchip_pd(genpd); 342 rockchip_pm_remove_one_domain(pd); 343 } 344 } 345 346 /* devm will free our memory */ 347} 348 349static void rockchip_configure_pd_cnt(struct rockchip_pmu *pmu, 350 u32 domain_reg_offset, 351 unsigned int count) 352{ 353 /* First configure domain power down transition count ... */ 354 regmap_write(pmu->regmap, domain_reg_offset, count); 355 /* ... and then power up count. */ 356 regmap_write(pmu->regmap, domain_reg_offset + 4, count); 357} 358 359static int rockchip_pm_domain_probe(struct platform_device *pdev) 360{ 361 struct device *dev = &pdev->dev; 362 struct device_node *np = dev->of_node; 363 struct device_node *node; 364 struct device *parent; 365 struct rockchip_pmu *pmu; 366 const struct of_device_id *match; 367 const struct rockchip_pmu_info *pmu_info; 368 int error; 369 370 if (!np) { 371 dev_err(dev, "device tree node not found\n"); 372 return -ENODEV; 373 } 374 375 match = of_match_device(dev->driver->of_match_table, dev); 376 if (!match || !match->data) { 377 dev_err(dev, "missing pmu data\n"); 378 return -EINVAL; 379 } 380 381 pmu_info = match->data; 382 383 pmu = devm_kzalloc(dev, 384 sizeof(*pmu) + 385 pmu_info->num_domains * sizeof(pmu->domains[0]), 386 GFP_KERNEL); 387 if (!pmu) 388 return -ENOMEM; 389 390 pmu->dev = &pdev->dev; 391 mutex_init(&pmu->mutex); 392 393 pmu->info = pmu_info; 394 395 pmu->genpd_data.domains = pmu->domains; 396 pmu->genpd_data.num_domains = pmu_info->num_domains; 397 398 parent = dev->parent; 399 if (!parent) { 400 dev_err(dev, "no parent for syscon devices\n"); 401 return -ENODEV; 402 } 403 404 pmu->regmap = syscon_node_to_regmap(parent->of_node); 405 406 /* 407 * Configure power up and down transition delays for CORE 408 * and GPU domains. 409 */ 410 rockchip_configure_pd_cnt(pmu, pmu_info->core_pwrcnt_offset, 411 pmu_info->core_power_transition_time); 412 rockchip_configure_pd_cnt(pmu, pmu_info->gpu_pwrcnt_offset, 413 pmu_info->gpu_power_transition_time); 414 415 error = -ENODEV; 416 417 for_each_available_child_of_node(np, node) { 418 error = rockchip_pm_add_one_domain(pmu, node); 419 if (error) { 420 dev_err(dev, "failed to handle node %s: %d\n", 421 node->name, error); 422 goto err_out; 423 } 424 } 425 426 if (error) { 427 dev_dbg(dev, "no power domains defined\n"); 428 goto err_out; 429 } 430 431 of_genpd_add_provider_onecell(np, &pmu->genpd_data); 432 433 return 0; 434 435err_out: 436 rockchip_pm_domain_cleanup(pmu); 437 return error; 438} 439 440static const struct rockchip_domain_info rk3288_pm_domains[] = { 441 [RK3288_PD_VIO] = DOMAIN_RK3288(7, 7, 4), 442 [RK3288_PD_HEVC] = DOMAIN_RK3288(14, 10, 9), 443 [RK3288_PD_VIDEO] = DOMAIN_RK3288(8, 8, 3), 444 [RK3288_PD_GPU] = DOMAIN_RK3288(9, 9, 2), 445}; 446 447static const struct rockchip_pmu_info rk3288_pmu = { 448 .pwr_offset = 0x08, 449 .status_offset = 0x0c, 450 .req_offset = 0x10, 451 .idle_offset = 0x14, 452 .ack_offset = 0x14, 453 454 .core_pwrcnt_offset = 0x34, 455 .gpu_pwrcnt_offset = 0x3c, 456 457 .core_power_transition_time = 24, /* 1us */ 458 .gpu_power_transition_time = 24, /* 1us */ 459 460 .num_domains = ARRAY_SIZE(rk3288_pm_domains), 461 .domain_info = rk3288_pm_domains, 462}; 463 464static const struct of_device_id rockchip_pm_domain_dt_match[] = { 465 { 466 .compatible = "rockchip,rk3288-power-controller", 467 .data = (void *)&rk3288_pmu, 468 }, 469 { /* sentinel */ }, 470}; 471 472static struct platform_driver rockchip_pm_domain_driver = { 473 .probe = rockchip_pm_domain_probe, 474 .driver = { 475 .name = "rockchip-pm-domain", 476 .of_match_table = rockchip_pm_domain_dt_match, 477 /* 478 * We can't forcibly eject devices form power domain, 479 * so we can't really remove power domains once they 480 * were added. 481 */ 482 .suppress_bind_attrs = true, 483 }, 484}; 485 486static int __init rockchip_pm_domain_drv_register(void) 487{ 488 return platform_driver_register(&rockchip_pm_domain_driver); 489} 490postcore_initcall(rockchip_pm_domain_drv_register);