diff options
-rw-r--r-- | arch/arm/mach-exynos/Kconfig | 1 | ||||
-rw-r--r-- | arch/arm/mach-exynos/pmu.c | 171 |
2 files changed, 130 insertions, 42 deletions
diff --git a/arch/arm/mach-exynos/Kconfig b/arch/arm/mach-exynos/Kconfig index 349c86708298..b9e3f1c61baf 100644 --- a/arch/arm/mach-exynos/Kconfig +++ b/arch/arm/mach-exynos/Kconfig | |||
@@ -24,6 +24,7 @@ menuconfig ARCH_EXYNOS | |||
24 | select PM_GENERIC_DOMAINS if PM_RUNTIME | 24 | select PM_GENERIC_DOMAINS if PM_RUNTIME |
25 | select S5P_DEV_MFC | 25 | select S5P_DEV_MFC |
26 | select SRAM | 26 | select SRAM |
27 | select MFD_SYSCON | ||
27 | help | 28 | help |
28 | Support for SAMSUNG EXYNOS SoCs (EXYNOS4/5) | 29 | Support for SAMSUNG EXYNOS SoCs (EXYNOS4/5) |
29 | 30 | ||
diff --git a/arch/arm/mach-exynos/pmu.c b/arch/arm/mach-exynos/pmu.c index e5e1846f10c9..24cd4c471c47 100644 --- a/arch/arm/mach-exynos/pmu.c +++ b/arch/arm/mach-exynos/pmu.c | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * Copyright (c) 2011-2012 Samsung Electronics Co., Ltd. | 2 | * Copyright (c) 2011-2014 Samsung Electronics Co., Ltd. |
3 | * http://www.samsung.com/ | 3 | * http://www.samsung.com/ |
4 | * | 4 | * |
5 | * EXYNOS - CPU PMU(Power Management Unit) support | 5 | * EXYNOS - CPU PMU(Power Management Unit) support |
@@ -10,12 +10,26 @@ | |||
10 | */ | 10 | */ |
11 | 11 | ||
12 | #include <linux/io.h> | 12 | #include <linux/io.h> |
13 | #include <linux/kernel.h> | 13 | #include <linux/of.h> |
14 | #include <linux/platform_device.h> | ||
14 | 15 | ||
15 | #include "common.h" | 16 | #include "common.h" |
16 | #include "regs-pmu.h" | 17 | #include "regs-pmu.h" |
17 | 18 | ||
18 | static const struct exynos_pmu_conf *exynos_pmu_config; | 19 | struct exynos_pmu_data { |
20 | const struct exynos_pmu_conf *pmu_config; | ||
21 | const struct exynos_pmu_conf *pmu_config_extra; | ||
22 | |||
23 | void (*pmu_init)(void); | ||
24 | void (*powerdown_conf)(enum sys_powerdown); | ||
25 | }; | ||
26 | |||
27 | struct exynos_pmu_context { | ||
28 | struct device *dev; | ||
29 | const struct exynos_pmu_data *pmu_data; | ||
30 | }; | ||
31 | |||
32 | static struct exynos_pmu_context *pmu_context; | ||
19 | 33 | ||
20 | static const struct exynos_pmu_conf exynos4210_pmu_config[] = { | 34 | static const struct exynos_pmu_conf exynos4210_pmu_config[] = { |
21 | /* { .offset = offset, .val = { AFTR, LPA, SLEEP } */ | 35 | /* { .offset = offset, .val = { AFTR, LPA, SLEEP } */ |
@@ -336,7 +350,7 @@ static unsigned int const exynos5_list_disable_wfi_wfe[] = { | |||
336 | EXYNOS5_ISP_ARM_OPTION, | 350 | EXYNOS5_ISP_ARM_OPTION, |
337 | }; | 351 | }; |
338 | 352 | ||
339 | static void exynos5_init_pmu(void) | 353 | static void exynos5_powerdown_conf(enum sys_powerdown mode) |
340 | { | 354 | { |
341 | unsigned int i; | 355 | unsigned int i; |
342 | unsigned int tmp; | 356 | unsigned int tmp; |
@@ -344,7 +358,7 @@ static void exynos5_init_pmu(void) | |||
344 | /* | 358 | /* |
345 | * Enable both SC_FEEDBACK and SC_COUNTER | 359 | * Enable both SC_FEEDBACK and SC_COUNTER |
346 | */ | 360 | */ |
347 | for (i = 0 ; i < ARRAY_SIZE(exynos5_list_both_cnt_feed) ; i++) { | 361 | for (i = 0; i < ARRAY_SIZE(exynos5_list_both_cnt_feed); i++) { |
348 | tmp = pmu_raw_readl(exynos5_list_both_cnt_feed[i]); | 362 | tmp = pmu_raw_readl(exynos5_list_both_cnt_feed[i]); |
349 | tmp |= (EXYNOS5_USE_SC_FEEDBACK | | 363 | tmp |= (EXYNOS5_USE_SC_FEEDBACK | |
350 | EXYNOS5_USE_SC_COUNTER); | 364 | EXYNOS5_USE_SC_COUNTER); |
@@ -361,7 +375,7 @@ static void exynos5_init_pmu(void) | |||
361 | /* | 375 | /* |
362 | * Disable WFI/WFE on XXX_OPTION | 376 | * Disable WFI/WFE on XXX_OPTION |
363 | */ | 377 | */ |
364 | for (i = 0 ; i < ARRAY_SIZE(exynos5_list_disable_wfi_wfe) ; i++) { | 378 | for (i = 0; i < ARRAY_SIZE(exynos5_list_disable_wfi_wfe); i++) { |
365 | tmp = pmu_raw_readl(exynos5_list_disable_wfi_wfe[i]); | 379 | tmp = pmu_raw_readl(exynos5_list_disable_wfi_wfe[i]); |
366 | tmp &= ~(EXYNOS5_OPTION_USE_STANDBYWFE | | 380 | tmp &= ~(EXYNOS5_OPTION_USE_STANDBYWFE | |
367 | EXYNOS5_OPTION_USE_STANDBYWFI); | 381 | EXYNOS5_OPTION_USE_STANDBYWFI); |
@@ -373,51 +387,124 @@ void exynos_sys_powerdown_conf(enum sys_powerdown mode) | |||
373 | { | 387 | { |
374 | unsigned int i; | 388 | unsigned int i; |
375 | 389 | ||
376 | if (soc_is_exynos5250()) | 390 | const struct exynos_pmu_data *pmu_data = pmu_context->pmu_data; |
377 | exynos5_init_pmu(); | ||
378 | 391 | ||
379 | for (i = 0; (exynos_pmu_config[i].offset != PMU_TABLE_END) ; i++) | 392 | if (pmu_data->powerdown_conf) |
380 | pmu_raw_writel(exynos_pmu_config[i].val[mode], | 393 | pmu_data->powerdown_conf(mode); |
381 | exynos_pmu_config[i].offset); | ||
382 | 394 | ||
383 | if (soc_is_exynos4412()) { | 395 | if (pmu_data->pmu_config) { |
384 | for (i = 0; exynos4412_pmu_config[i].offset != PMU_TABLE_END ; i++) | 396 | for (i = 0; (pmu_data->pmu_config[i].offset != PMU_TABLE_END); i++) |
385 | pmu_raw_writel(exynos4412_pmu_config[i].val[mode], | 397 | pmu_raw_writel(pmu_data->pmu_config[i].val[mode], |
386 | exynos4412_pmu_config[i].offset); | 398 | pmu_data->pmu_config[i].offset); |
399 | } | ||
400 | |||
401 | if (pmu_data->pmu_config_extra) { | ||
402 | for (i = 0; pmu_data->pmu_config_extra[i].offset != PMU_TABLE_END; i++) | ||
403 | pmu_raw_writel(pmu_data->pmu_config_extra[i].val[mode], | ||
404 | pmu_data->pmu_config_extra[i].offset); | ||
387 | } | 405 | } |
388 | } | 406 | } |
389 | 407 | ||
390 | static int __init exynos_pmu_init(void) | 408 | static void exynos5250_pmu_init(void) |
391 | { | 409 | { |
392 | unsigned int value; | 410 | unsigned int value; |
411 | /* | ||
412 | * When SYS_WDTRESET is set, watchdog timer reset request | ||
413 | * is ignored by power management unit. | ||
414 | */ | ||
415 | value = pmu_raw_readl(EXYNOS5_AUTO_WDTRESET_DISABLE); | ||
416 | value &= ~EXYNOS5_SYS_WDTRESET; | ||
417 | pmu_raw_writel(value, EXYNOS5_AUTO_WDTRESET_DISABLE); | ||
418 | |||
419 | value = pmu_raw_readl(EXYNOS5_MASK_WDTRESET_REQUEST); | ||
420 | value &= ~EXYNOS5_SYS_WDTRESET; | ||
421 | pmu_raw_writel(value, EXYNOS5_MASK_WDTRESET_REQUEST); | ||
422 | } | ||
423 | |||
424 | static const struct exynos_pmu_data exynos4210_pmu_data = { | ||
425 | .pmu_config = exynos4210_pmu_config, | ||
426 | }; | ||
427 | |||
428 | static const struct exynos_pmu_data exynos4212_pmu_data = { | ||
429 | .pmu_config = exynos4x12_pmu_config, | ||
430 | }; | ||
431 | |||
432 | static const struct exynos_pmu_data exynos4412_pmu_data = { | ||
433 | .pmu_config = exynos4x12_pmu_config, | ||
434 | .pmu_config_extra = exynos4412_pmu_config, | ||
435 | }; | ||
436 | |||
437 | static const struct exynos_pmu_data exynos5250_pmu_data = { | ||
438 | .pmu_config = exynos5250_pmu_config, | ||
439 | .pmu_init = exynos5250_pmu_init, | ||
440 | .powerdown_conf = exynos5_powerdown_conf, | ||
441 | }; | ||
442 | |||
443 | /* | ||
444 | * PMU platform driver and devicetree bindings. | ||
445 | */ | ||
446 | static const struct of_device_id exynos_pmu_of_device_ids[] = { | ||
447 | { | ||
448 | .compatible = "samsung,exynos4210-pmu", | ||
449 | .data = &exynos4210_pmu_data, | ||
450 | }, { | ||
451 | .compatible = "samsung,exynos4212-pmu", | ||
452 | .data = &exynos4212_pmu_data, | ||
453 | }, { | ||
454 | .compatible = "samsung,exynos4412-pmu", | ||
455 | .data = &exynos4412_pmu_data, | ||
456 | }, { | ||
457 | .compatible = "samsung,exynos5250-pmu", | ||
458 | .data = &exynos5250_pmu_data, | ||
459 | }, | ||
460 | { /*sentinel*/ }, | ||
461 | }; | ||
393 | 462 | ||
394 | exynos_pmu_config = exynos4210_pmu_config; | 463 | static int exynos_pmu_probe(struct platform_device *pdev) |
395 | 464 | { | |
396 | if (soc_is_exynos4210()) { | 465 | const struct of_device_id *match; |
397 | exynos_pmu_config = exynos4210_pmu_config; | 466 | struct device *dev = &pdev->dev; |
398 | pr_info("EXYNOS4210 PMU Initialize\n"); | 467 | struct resource *res; |
399 | } else if (soc_is_exynos4212() || soc_is_exynos4412()) { | 468 | |
400 | exynos_pmu_config = exynos4x12_pmu_config; | 469 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
401 | pr_info("EXYNOS4x12 PMU Initialize\n"); | 470 | pmu_base_addr = devm_ioremap_resource(dev, res); |
402 | } else if (soc_is_exynos5250()) { | 471 | if (IS_ERR(pmu_base_addr)) |
403 | /* | 472 | return PTR_ERR(pmu_base_addr); |
404 | * When SYS_WDTRESET is set, watchdog timer reset request | 473 | |
405 | * is ignored by power management unit. | 474 | pmu_context = devm_kzalloc(&pdev->dev, |
406 | */ | 475 | sizeof(struct exynos_pmu_context), |
407 | value = pmu_raw_readl(EXYNOS5_AUTO_WDTRESET_DISABLE); | 476 | GFP_KERNEL); |
408 | value &= ~EXYNOS5_SYS_WDTRESET; | 477 | if (!pmu_context) { |
409 | pmu_raw_writel(value, EXYNOS5_AUTO_WDTRESET_DISABLE); | 478 | dev_err(dev, "Cannot allocate memory.\n"); |
410 | 479 | return -ENOMEM; | |
411 | value = pmu_raw_readl(EXYNOS5_MASK_WDTRESET_REQUEST); | ||
412 | value &= ~EXYNOS5_SYS_WDTRESET; | ||
413 | pmu_raw_writel(value, EXYNOS5_MASK_WDTRESET_REQUEST); | ||
414 | |||
415 | exynos_pmu_config = exynos5250_pmu_config; | ||
416 | pr_info("EXYNOS5250 PMU Initialize\n"); | ||
417 | } else { | ||
418 | pr_info("EXYNOS: PMU not supported\n"); | ||
419 | } | 480 | } |
481 | pmu_context->dev = dev; | ||
482 | |||
483 | match = of_match_node(exynos_pmu_of_device_ids, dev->of_node); | ||
484 | |||
485 | pmu_context->pmu_data = match->data; | ||
420 | 486 | ||
487 | if (pmu_context->pmu_data->pmu_init) | ||
488 | pmu_context->pmu_data->pmu_init(); | ||
489 | |||
490 | platform_set_drvdata(pdev, pmu_context); | ||
491 | |||
492 | dev_dbg(dev, "Exynos PMU Driver probe done\n"); | ||
421 | return 0; | 493 | return 0; |
422 | } | 494 | } |
423 | arch_initcall(exynos_pmu_init); | 495 | |
496 | static struct platform_driver exynos_pmu_driver = { | ||
497 | .driver = { | ||
498 | .name = "exynos-pmu", | ||
499 | .owner = THIS_MODULE, | ||
500 | .of_match_table = exynos_pmu_of_device_ids, | ||
501 | }, | ||
502 | .probe = exynos_pmu_probe, | ||
503 | }; | ||
504 | |||
505 | static int __init exynos_pmu_init(void) | ||
506 | { | ||
507 | return platform_driver_register(&exynos_pmu_driver); | ||
508 | |||
509 | } | ||
510 | postcore_initcall(exynos_pmu_init); | ||