1// SPDX-License-Identifier: GPL-2.0 2/* Copyright (c) 2018, The Linux Foundation. All rights reserved.*/ 3 4#include <linux/err.h> 5#include <linux/init.h> 6#include <linux/kernel.h> 7#include <linux/module.h> 8#include <linux/mutex.h> 9#include <linux/pm_domain.h> 10#include <linux/slab.h> 11#include <linux/of.h> 12#include <linux/of_device.h> 13#include <linux/platform_device.h> 14#include <linux/pm_opp.h> 15#include <soc/qcom/cmd-db.h> 16#include <soc/qcom/rpmh.h> 17#include <dt-bindings/power/qcom-rpmpd.h> 18 19#define domain_to_rpmhpd(domain) container_of(domain, struct rpmhpd, pd) 20 21#define RPMH_ARC_MAX_LEVELS 16 22 23/** 24 * struct rpmhpd - top level RPMh power domain resource data structure 25 * @dev: rpmh power domain controller device 26 * @pd: generic_pm_domain corrresponding to the power domain 27 * @parent: generic_pm_domain corrresponding to the parent's power domain 28 * @peer: A peer power domain in case Active only Voting is 29 * supported 30 * @active_only: True if it represents an Active only peer 31 * @corner: current corner 32 * @active_corner: current active corner 33 * @enable_corner: lowest non-zero corner 34 * @level: An array of level (vlvl) to corner (hlvl) mappings 35 * derived from cmd-db 36 * @level_count: Number of levels supported by the power domain. max 37 * being 16 (0 - 15) 38 * @enabled: true if the power domain is enabled 39 * @res_name: Resource name used for cmd-db lookup 40 * @addr: Resource address as looped up using resource name from 41 * cmd-db 42 */ 43struct rpmhpd { 44 struct device *dev; 45 struct generic_pm_domain pd; 46 struct generic_pm_domain *parent; 47 struct rpmhpd *peer; 48 const bool active_only; 49 unsigned int corner; 50 unsigned int active_corner; 51 unsigned int enable_corner; 52 u32 level[RPMH_ARC_MAX_LEVELS]; 53 size_t level_count; 54 bool enabled; 55 const char *res_name; 56 u32 addr; 57}; 58 59struct rpmhpd_desc { 60 struct rpmhpd **rpmhpds; 61 size_t num_pds; 62}; 63 64static DEFINE_MUTEX(rpmhpd_lock); 65 66/* SDM845 RPMH powerdomains */ 67 68static struct rpmhpd sdm845_ebi = { 69 .pd = { .name = "ebi", }, 70 .res_name = "ebi.lvl", 71}; 72 73static struct rpmhpd sdm845_lmx = { 74 .pd = { .name = "lmx", }, 75 .res_name = "lmx.lvl", 76}; 77 78static struct rpmhpd sdm845_lcx = { 79 .pd = { .name = "lcx", }, 80 .res_name = "lcx.lvl", 81}; 82 83static struct rpmhpd sdm845_gfx = { 84 .pd = { .name = "gfx", }, 85 .res_name = "gfx.lvl", 86}; 87 88static struct rpmhpd sdm845_mss = { 89 .pd = { .name = "mss", }, 90 .res_name = "mss.lvl", 91}; 92 93static struct rpmhpd sdm845_mx_ao; 94static struct rpmhpd sdm845_mx = { 95 .pd = { .name = "mx", }, 96 .peer = &sdm845_mx_ao, 97 .res_name = "mx.lvl", 98}; 99 100static struct rpmhpd sdm845_mx_ao = { 101 .pd = { .name = "mx_ao", }, 102 .active_only = true, 103 .peer = &sdm845_mx, 104 .res_name = "mx.lvl", 105}; 106 107static struct rpmhpd sdm845_cx_ao; 108static struct rpmhpd sdm845_cx = { 109 .pd = { .name = "cx", }, 110 .peer = &sdm845_cx_ao, 111 .parent = &sdm845_mx.pd, 112 .res_name = "cx.lvl", 113}; 114 115static struct rpmhpd sdm845_cx_ao = { 116 .pd = { .name = "cx_ao", }, 117 .active_only = true, 118 .peer = &sdm845_cx, 119 .parent = &sdm845_mx_ao.pd, 120 .res_name = "cx.lvl", 121}; 122 123static struct rpmhpd *sdm845_rpmhpds[] = { 124 [SDM845_EBI] = &sdm845_ebi, 125 [SDM845_MX] = &sdm845_mx, 126 [SDM845_MX_AO] = &sdm845_mx_ao, 127 [SDM845_CX] = &sdm845_cx, 128 [SDM845_CX_AO] = &sdm845_cx_ao, 129 [SDM845_LMX] = &sdm845_lmx, 130 [SDM845_LCX] = &sdm845_lcx, 131 [SDM845_GFX] = &sdm845_gfx, 132 [SDM845_MSS] = &sdm845_mss, 133}; 134 135static const struct rpmhpd_desc sdm845_desc = { 136 .rpmhpds = sdm845_rpmhpds, 137 .num_pds = ARRAY_SIZE(sdm845_rpmhpds), 138}; 139 140/* SM8150 RPMH powerdomains */ 141 142static struct rpmhpd sm8150_mmcx_ao; 143static struct rpmhpd sm8150_mmcx = { 144 .pd = { .name = "mmcx", }, 145 .peer = &sm8150_mmcx_ao, 146 .res_name = "mmcx.lvl", 147}; 148 149static struct rpmhpd sm8150_mmcx_ao = { 150 .pd = { .name = "mmcx_ao", }, 151 .active_only = true, 152 .peer = &sm8150_mmcx, 153 .res_name = "mmcx.lvl", 154}; 155 156static struct rpmhpd *sm8150_rpmhpds[] = { 157 [SM8150_MSS] = &sdm845_mss, 158 [SM8150_EBI] = &sdm845_ebi, 159 [SM8150_LMX] = &sdm845_lmx, 160 [SM8150_LCX] = &sdm845_lcx, 161 [SM8150_GFX] = &sdm845_gfx, 162 [SM8150_MX] = &sdm845_mx, 163 [SM8150_MX_AO] = &sdm845_mx_ao, 164 [SM8150_CX] = &sdm845_cx, 165 [SM8150_CX_AO] = &sdm845_cx_ao, 166 [SM8150_MMCX] = &sm8150_mmcx, 167 [SM8150_MMCX_AO] = &sm8150_mmcx_ao, 168}; 169 170static const struct rpmhpd_desc sm8150_desc = { 171 .rpmhpds = sm8150_rpmhpds, 172 .num_pds = ARRAY_SIZE(sm8150_rpmhpds), 173}; 174 175static struct rpmhpd *sm8250_rpmhpds[] = { 176 [SM8250_CX] = &sdm845_cx, 177 [SM8250_CX_AO] = &sdm845_cx_ao, 178 [SM8250_EBI] = &sdm845_ebi, 179 [SM8250_GFX] = &sdm845_gfx, 180 [SM8250_LCX] = &sdm845_lcx, 181 [SM8250_LMX] = &sdm845_lmx, 182 [SM8250_MMCX] = &sm8150_mmcx, 183 [SM8250_MMCX_AO] = &sm8150_mmcx_ao, 184 [SM8250_MX] = &sdm845_mx, 185 [SM8250_MX_AO] = &sdm845_mx_ao, 186}; 187 188static const struct rpmhpd_desc sm8250_desc = { 189 .rpmhpds = sm8250_rpmhpds, 190 .num_pds = ARRAY_SIZE(sm8250_rpmhpds), 191}; 192 193/* SC7180 RPMH powerdomains */ 194static struct rpmhpd *sc7180_rpmhpds[] = { 195 [SC7180_CX] = &sdm845_cx, 196 [SC7180_CX_AO] = &sdm845_cx_ao, 197 [SC7180_GFX] = &sdm845_gfx, 198 [SC7180_MX] = &sdm845_mx, 199 [SC7180_MX_AO] = &sdm845_mx_ao, 200 [SC7180_LMX] = &sdm845_lmx, 201 [SC7180_LCX] = &sdm845_lcx, 202 [SC7180_MSS] = &sdm845_mss, 203}; 204 205static const struct rpmhpd_desc sc7180_desc = { 206 .rpmhpds = sc7180_rpmhpds, 207 .num_pds = ARRAY_SIZE(sc7180_rpmhpds), 208}; 209 210static const struct of_device_id rpmhpd_match_table[] = { 211 { .compatible = "qcom,sc7180-rpmhpd", .data = &sc7180_desc }, 212 { .compatible = "qcom,sdm845-rpmhpd", .data = &sdm845_desc }, 213 { .compatible = "qcom,sm8150-rpmhpd", .data = &sm8150_desc }, 214 { .compatible = "qcom,sm8250-rpmhpd", .data = &sm8250_desc }, 215 { } 216}; 217MODULE_DEVICE_TABLE(of, rpmhpd_match_table); 218 219static int rpmhpd_send_corner(struct rpmhpd *pd, int state, 220 unsigned int corner, bool sync) 221{ 222 struct tcs_cmd cmd = { 223 .addr = pd->addr, 224 .data = corner, 225 }; 226 227 /* 228 * Wait for an ack only when we are increasing the 229 * perf state of the power domain 230 */ 231 if (sync) 232 return rpmh_write(pd->dev, state, &cmd, 1); 233 else 234 return rpmh_write_async(pd->dev, state, &cmd, 1); 235} 236 237static void to_active_sleep(struct rpmhpd *pd, unsigned int corner, 238 unsigned int *active, unsigned int *sleep) 239{ 240 *active = corner; 241 242 if (pd->active_only) 243 *sleep = 0; 244 else 245 *sleep = *active; 246} 247 248/* 249 * This function is used to aggregate the votes across the active only 250 * resources and its peers. The aggregated votes are sent to RPMh as 251 * ACTIVE_ONLY votes (which take effect immediately), as WAKE_ONLY votes 252 * (applied by RPMh on system wakeup) and as SLEEP votes (applied by RPMh 253 * on system sleep). 254 * We send ACTIVE_ONLY votes for resources without any peers. For others, 255 * which have an active only peer, all 3 votes are sent. 256 */ 257static int rpmhpd_aggregate_corner(struct rpmhpd *pd, unsigned int corner) 258{ 259 int ret; 260 struct rpmhpd *peer = pd->peer; 261 unsigned int active_corner, sleep_corner; 262 unsigned int this_active_corner = 0, this_sleep_corner = 0; 263 unsigned int peer_active_corner = 0, peer_sleep_corner = 0; 264 265 to_active_sleep(pd, corner, &this_active_corner, &this_sleep_corner); 266 267 if (peer && peer->enabled) 268 to_active_sleep(peer, peer->corner, &peer_active_corner, 269 &peer_sleep_corner); 270 271 active_corner = max(this_active_corner, peer_active_corner); 272 273 ret = rpmhpd_send_corner(pd, RPMH_ACTIVE_ONLY_STATE, active_corner, 274 active_corner > pd->active_corner); 275 if (ret) 276 return ret; 277 278 pd->active_corner = active_corner; 279 280 if (peer) { 281 peer->active_corner = active_corner; 282 283 ret = rpmhpd_send_corner(pd, RPMH_WAKE_ONLY_STATE, 284 active_corner, false); 285 if (ret) 286 return ret; 287 288 sleep_corner = max(this_sleep_corner, peer_sleep_corner); 289 290 return rpmhpd_send_corner(pd, RPMH_SLEEP_STATE, sleep_corner, 291 false); 292 } 293 294 return ret; 295} 296 297static int rpmhpd_power_on(struct generic_pm_domain *domain) 298{ 299 struct rpmhpd *pd = domain_to_rpmhpd(domain); 300 unsigned int corner; 301 int ret; 302 303 mutex_lock(&rpmhpd_lock); 304 305 corner = max(pd->corner, pd->enable_corner); 306 ret = rpmhpd_aggregate_corner(pd, corner); 307 if (!ret) 308 pd->enabled = true; 309 310 mutex_unlock(&rpmhpd_lock); 311 312 return ret; 313} 314 315static int rpmhpd_power_off(struct generic_pm_domain *domain) 316{ 317 struct rpmhpd *pd = domain_to_rpmhpd(domain); 318 int ret; 319 320 mutex_lock(&rpmhpd_lock); 321 322 ret = rpmhpd_aggregate_corner(pd, 0); 323 if (!ret) 324 pd->enabled = false; 325 326 mutex_unlock(&rpmhpd_lock); 327 328 return ret; 329} 330 331static int rpmhpd_set_performance_state(struct generic_pm_domain *domain, 332 unsigned int level) 333{ 334 struct rpmhpd *pd = domain_to_rpmhpd(domain); 335 int ret = 0, i; 336 337 mutex_lock(&rpmhpd_lock); 338 339 for (i = 0; i < pd->level_count; i++) 340 if (level <= pd->level[i]) 341 break; 342 343 /* 344 * If the level requested is more than that supported by the 345 * max corner, just set it to max anyway. 346 */ 347 if (i == pd->level_count) 348 i--; 349 350 if (pd->enabled) { 351 /* Ensure that the domain isn't turn off */ 352 if (i < pd->enable_corner) 353 i = pd->enable_corner; 354 355 ret = rpmhpd_aggregate_corner(pd, i); 356 if (ret) 357 goto out; 358 } 359 360 pd->corner = i; 361out: 362 mutex_unlock(&rpmhpd_lock); 363 364 return ret; 365} 366 367static unsigned int rpmhpd_get_performance_state(struct generic_pm_domain *genpd, 368 struct dev_pm_opp *opp) 369{ 370 return dev_pm_opp_get_level(opp); 371} 372 373static int rpmhpd_update_level_mapping(struct rpmhpd *rpmhpd) 374{ 375 int i; 376 const u16 *buf; 377 378 buf = cmd_db_read_aux_data(rpmhpd->res_name, &rpmhpd->level_count); 379 if (IS_ERR(buf)) 380 return PTR_ERR(buf); 381 382 /* 2 bytes used for each command DB aux data entry */ 383 rpmhpd->level_count >>= 1; 384 385 if (rpmhpd->level_count > RPMH_ARC_MAX_LEVELS) 386 return -EINVAL; 387 388 for (i = 0; i < rpmhpd->level_count; i++) { 389 rpmhpd->level[i] = buf[i]; 390 391 /* Remember the first corner with non-zero level */ 392 if (!rpmhpd->level[rpmhpd->enable_corner] && rpmhpd->level[i]) 393 rpmhpd->enable_corner = i; 394 395 /* 396 * The AUX data may be zero padded. These 0 valued entries at 397 * the end of the map must be ignored. 398 */ 399 if (i > 0 && rpmhpd->level[i] == 0) { 400 rpmhpd->level_count = i; 401 break; 402 } 403 pr_debug("%s: ARC hlvl=%2d --> vlvl=%4u\n", rpmhpd->res_name, i, 404 rpmhpd->level[i]); 405 } 406 407 return 0; 408} 409 410static int rpmhpd_probe(struct platform_device *pdev) 411{ 412 int i, ret; 413 size_t num_pds; 414 struct device *dev = &pdev->dev; 415 struct genpd_onecell_data *data; 416 struct rpmhpd **rpmhpds; 417 const struct rpmhpd_desc *desc; 418 419 desc = of_device_get_match_data(dev); 420 if (!desc) 421 return -EINVAL; 422 423 rpmhpds = desc->rpmhpds; 424 num_pds = desc->num_pds; 425 426 data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); 427 if (!data) 428 return -ENOMEM; 429 430 data->domains = devm_kcalloc(dev, num_pds, sizeof(*data->domains), 431 GFP_KERNEL); 432 if (!data->domains) 433 return -ENOMEM; 434 435 data->num_domains = num_pds; 436 437 for (i = 0; i < num_pds; i++) { 438 if (!rpmhpds[i]) { 439 dev_warn(dev, "rpmhpds[%d] is empty\n", i); 440 continue; 441 } 442 443 rpmhpds[i]->dev = dev; 444 rpmhpds[i]->addr = cmd_db_read_addr(rpmhpds[i]->res_name); 445 if (!rpmhpds[i]->addr) { 446 dev_err(dev, "Could not find RPMh address for resource %s\n", 447 rpmhpds[i]->res_name); 448 return -ENODEV; 449 } 450 451 ret = cmd_db_read_slave_id(rpmhpds[i]->res_name); 452 if (ret != CMD_DB_HW_ARC) { 453 dev_err(dev, "RPMh slave ID mismatch\n"); 454 return -EINVAL; 455 } 456 457 ret = rpmhpd_update_level_mapping(rpmhpds[i]); 458 if (ret) 459 return ret; 460 461 rpmhpds[i]->pd.power_off = rpmhpd_power_off; 462 rpmhpds[i]->pd.power_on = rpmhpd_power_on; 463 rpmhpds[i]->pd.set_performance_state = rpmhpd_set_performance_state; 464 rpmhpds[i]->pd.opp_to_performance_state = rpmhpd_get_performance_state; 465 pm_genpd_init(&rpmhpds[i]->pd, NULL, true); 466 467 data->domains[i] = &rpmhpds[i]->pd; 468 } 469 470 /* Add subdomains */ 471 for (i = 0; i < num_pds; i++) { 472 if (!rpmhpds[i]) 473 continue; 474 if (rpmhpds[i]->parent) 475 pm_genpd_add_subdomain(rpmhpds[i]->parent, 476 &rpmhpds[i]->pd); 477 } 478 479 return of_genpd_add_provider_onecell(pdev->dev.of_node, data); 480} 481 482static struct platform_driver rpmhpd_driver = { 483 .driver = { 484 .name = "qcom-rpmhpd", 485 .of_match_table = rpmhpd_match_table, 486 .suppress_bind_attrs = true, 487 }, 488 .probe = rpmhpd_probe, 489}; 490 491static int __init rpmhpd_init(void) 492{ 493 return platform_driver_register(&rpmhpd_driver); 494} 495core_initcall(rpmhpd_init); 496 497MODULE_DESCRIPTION("Qualcomm Technologies, Inc. RPMh Power Domain Driver"); 498MODULE_LICENSE("GPL v2"); 499