diff options
-rw-r--r-- | drivers/bus/ti-sysc.c | 226 | ||||
-rw-r--r-- | include/linux/platform_data/ti-sysc.h | 1 |
2 files changed, 224 insertions, 3 deletions
diff --git a/drivers/bus/ti-sysc.c b/drivers/bus/ti-sysc.c index 50fcb04e8179..5aeab4533b5f 100644 --- a/drivers/bus/ti-sysc.c +++ b/drivers/bus/ti-sysc.c | |||
@@ -14,8 +14,10 @@ | |||
14 | #include <linux/io.h> | 14 | #include <linux/io.h> |
15 | #include <linux/clk.h> | 15 | #include <linux/clk.h> |
16 | #include <linux/clkdev.h> | 16 | #include <linux/clkdev.h> |
17 | #include <linux/delay.h> | ||
17 | #include <linux/module.h> | 18 | #include <linux/module.h> |
18 | #include <linux/platform_device.h> | 19 | #include <linux/platform_device.h> |
20 | #include <linux/pm_domain.h> | ||
19 | #include <linux/pm_runtime.h> | 21 | #include <linux/pm_runtime.h> |
20 | #include <linux/of_address.h> | 22 | #include <linux/of_address.h> |
21 | #include <linux/of_platform.h> | 23 | #include <linux/of_platform.h> |
@@ -68,6 +70,7 @@ struct sysc { | |||
68 | u32 revision; | 70 | u32 revision; |
69 | bool enabled; | 71 | bool enabled; |
70 | bool needs_resume; | 72 | bool needs_resume; |
73 | bool child_needs_resume; | ||
71 | struct delayed_work idle_work; | 74 | struct delayed_work idle_work; |
72 | }; | 75 | }; |
73 | 76 | ||
@@ -474,6 +477,14 @@ static int sysc_show_reg(struct sysc *ddata, | |||
474 | return sprintf(bufp, ":%x", ddata->offsets[reg]); | 477 | return sprintf(bufp, ":%x", ddata->offsets[reg]); |
475 | } | 478 | } |
476 | 479 | ||
480 | static int sysc_show_name(char *bufp, struct sysc *ddata) | ||
481 | { | ||
482 | if (!ddata->name) | ||
483 | return 0; | ||
484 | |||
485 | return sprintf(bufp, ":%s", ddata->name); | ||
486 | } | ||
487 | |||
477 | /** | 488 | /** |
478 | * sysc_show_registers - show information about interconnect target module | 489 | * sysc_show_registers - show information about interconnect target module |
479 | * @ddata: device driver data | 490 | * @ddata: device driver data |
@@ -488,7 +499,7 @@ static void sysc_show_registers(struct sysc *ddata) | |||
488 | bufp += sysc_show_reg(ddata, bufp, i); | 499 | bufp += sysc_show_reg(ddata, bufp, i); |
489 | 500 | ||
490 | bufp += sysc_show_rev(bufp, ddata); | 501 | bufp += sysc_show_rev(bufp, ddata); |
491 | bufp += sysc_show_rev(bufp, ddata); | 502 | bufp += sysc_show_name(bufp, ddata); |
492 | 503 | ||
493 | dev_dbg(ddata->dev, "%llx:%x%s\n", | 504 | dev_dbg(ddata->dev, "%llx:%x%s\n", |
494 | ddata->module_pa, ddata->module_size, | 505 | ddata->module_pa, ddata->module_size, |
@@ -612,11 +623,93 @@ static const struct dev_pm_ops sysc_pm_ops = { | |||
612 | NULL) | 623 | NULL) |
613 | }; | 624 | }; |
614 | 625 | ||
626 | /* Module revision register based quirks */ | ||
627 | struct sysc_revision_quirk { | ||
628 | const char *name; | ||
629 | u32 base; | ||
630 | int rev_offset; | ||
631 | int sysc_offset; | ||
632 | int syss_offset; | ||
633 | u32 revision; | ||
634 | u32 revision_mask; | ||
635 | u32 quirks; | ||
636 | }; | ||
637 | |||
638 | #define SYSC_QUIRK(optname, optbase, optrev, optsysc, optsyss, \ | ||
639 | optrev_val, optrevmask, optquirkmask) \ | ||
640 | { \ | ||
641 | .name = (optname), \ | ||
642 | .base = (optbase), \ | ||
643 | .rev_offset = (optrev), \ | ||
644 | .sysc_offset = (optsysc), \ | ||
645 | .syss_offset = (optsyss), \ | ||
646 | .revision = (optrev_val), \ | ||
647 | .revision_mask = (optrevmask), \ | ||
648 | .quirks = (optquirkmask), \ | ||
649 | } | ||
650 | |||
651 | static const struct sysc_revision_quirk sysc_revision_quirks[] = { | ||
652 | /* These drivers need to be fixed to not use pm_runtime_irq_safe() */ | ||
653 | SYSC_QUIRK("gpio", 0, 0, 0x10, 0x114, 0x50600801, 0xffffffff, | ||
654 | SYSC_QUIRK_LEGACY_IDLE), | ||
655 | SYSC_QUIRK("mmu", 0, 0, 0x10, 0x14, 0x00000020, 0xffffffff, | ||
656 | SYSC_QUIRK_LEGACY_IDLE), | ||
657 | SYSC_QUIRK("mmu", 0, 0, 0x10, 0x14, 0x00000030, 0xffffffff, | ||
658 | SYSC_QUIRK_LEGACY_IDLE), | ||
659 | SYSC_QUIRK("sham", 0, 0x100, 0x110, 0x114, 0x40000c03, 0xffffffff, | ||
660 | SYSC_QUIRK_LEGACY_IDLE), | ||
661 | SYSC_QUIRK("smartreflex", 0, -1, 0x24, -1, 0x00000000, 0xffffffff, | ||
662 | SYSC_QUIRK_LEGACY_IDLE), | ||
663 | SYSC_QUIRK("smartreflex", 0, -1, 0x38, -1, 0x00000000, 0xffffffff, | ||
664 | SYSC_QUIRK_LEGACY_IDLE), | ||
665 | SYSC_QUIRK("timer", 0, 0, 0x10, 0x14, 0x00000015, 0xffffffff, | ||
666 | SYSC_QUIRK_LEGACY_IDLE), | ||
667 | SYSC_QUIRK("uart", 0, 0x50, 0x54, 0x58, 0x00000052, 0xffffffff, | ||
668 | SYSC_QUIRK_LEGACY_IDLE), | ||
669 | }; | ||
670 | |||
671 | static void sysc_init_revision_quirks(struct sysc *ddata) | ||
672 | { | ||
673 | const struct sysc_revision_quirk *q; | ||
674 | int i; | ||
675 | |||
676 | for (i = 0; i < ARRAY_SIZE(sysc_revision_quirks); i++) { | ||
677 | q = &sysc_revision_quirks[i]; | ||
678 | |||
679 | if (q->base && q->base != ddata->module_pa) | ||
680 | continue; | ||
681 | |||
682 | if (q->rev_offset >= 0 && | ||
683 | q->rev_offset != ddata->offsets[SYSC_REVISION]) | ||
684 | continue; | ||
685 | |||
686 | if (q->sysc_offset >= 0 && | ||
687 | q->sysc_offset != ddata->offsets[SYSC_SYSCONFIG]) | ||
688 | continue; | ||
689 | |||
690 | if (q->syss_offset >= 0 && | ||
691 | q->syss_offset != ddata->offsets[SYSC_SYSSTATUS]) | ||
692 | continue; | ||
693 | |||
694 | if (q->revision == ddata->revision || | ||
695 | (q->revision & q->revision_mask) == | ||
696 | (ddata->revision & q->revision_mask)) { | ||
697 | ddata->name = q->name; | ||
698 | ddata->cfg.quirks |= q->quirks; | ||
699 | } | ||
700 | } | ||
701 | } | ||
702 | |||
615 | /* At this point the module is configured enough to read the revision */ | 703 | /* At this point the module is configured enough to read the revision */ |
616 | static int sysc_init_module(struct sysc *ddata) | 704 | static int sysc_init_module(struct sysc *ddata) |
617 | { | 705 | { |
618 | int error; | 706 | int error; |
619 | 707 | ||
708 | if (ddata->cfg.quirks & SYSC_QUIRK_NO_IDLE_ON_INIT) { | ||
709 | ddata->revision = sysc_read_revision(ddata); | ||
710 | goto rev_quirks; | ||
711 | } | ||
712 | |||
620 | error = pm_runtime_get_sync(ddata->dev); | 713 | error = pm_runtime_get_sync(ddata->dev); |
621 | if (error < 0) { | 714 | if (error < 0) { |
622 | pm_runtime_put_noidle(ddata->dev); | 715 | pm_runtime_put_noidle(ddata->dev); |
@@ -626,6 +719,9 @@ static int sysc_init_module(struct sysc *ddata) | |||
626 | ddata->revision = sysc_read_revision(ddata); | 719 | ddata->revision = sysc_read_revision(ddata); |
627 | pm_runtime_put_sync(ddata->dev); | 720 | pm_runtime_put_sync(ddata->dev); |
628 | 721 | ||
722 | rev_quirks: | ||
723 | sysc_init_revision_quirks(ddata); | ||
724 | |||
629 | return 0; | 725 | return 0; |
630 | } | 726 | } |
631 | 727 | ||
@@ -753,6 +849,127 @@ static struct sysc *sysc_child_to_parent(struct device *dev) | |||
753 | return dev_get_drvdata(parent); | 849 | return dev_get_drvdata(parent); |
754 | } | 850 | } |
755 | 851 | ||
852 | static int __maybe_unused sysc_child_runtime_suspend(struct device *dev) | ||
853 | { | ||
854 | struct sysc *ddata; | ||
855 | int error; | ||
856 | |||
857 | ddata = sysc_child_to_parent(dev); | ||
858 | |||
859 | error = pm_generic_runtime_suspend(dev); | ||
860 | if (error) | ||
861 | return error; | ||
862 | |||
863 | if (!ddata->enabled) | ||
864 | return 0; | ||
865 | |||
866 | return sysc_runtime_suspend(ddata->dev); | ||
867 | } | ||
868 | |||
869 | static int __maybe_unused sysc_child_runtime_resume(struct device *dev) | ||
870 | { | ||
871 | struct sysc *ddata; | ||
872 | int error; | ||
873 | |||
874 | ddata = sysc_child_to_parent(dev); | ||
875 | |||
876 | if (!ddata->enabled) { | ||
877 | error = sysc_runtime_resume(ddata->dev); | ||
878 | if (error < 0) | ||
879 | dev_err(ddata->dev, | ||
880 | "%s error: %i\n", __func__, error); | ||
881 | } | ||
882 | |||
883 | return pm_generic_runtime_resume(dev); | ||
884 | } | ||
885 | |||
886 | #ifdef CONFIG_PM_SLEEP | ||
887 | static int sysc_child_suspend_noirq(struct device *dev) | ||
888 | { | ||
889 | struct sysc *ddata; | ||
890 | int error; | ||
891 | |||
892 | ddata = sysc_child_to_parent(dev); | ||
893 | |||
894 | error = pm_generic_suspend_noirq(dev); | ||
895 | if (error) | ||
896 | return error; | ||
897 | |||
898 | if (!pm_runtime_status_suspended(dev)) { | ||
899 | error = pm_generic_runtime_suspend(dev); | ||
900 | if (error) | ||
901 | return error; | ||
902 | |||
903 | error = sysc_runtime_suspend(ddata->dev); | ||
904 | if (error) | ||
905 | return error; | ||
906 | |||
907 | ddata->child_needs_resume = true; | ||
908 | } | ||
909 | |||
910 | return 0; | ||
911 | } | ||
912 | |||
913 | static int sysc_child_resume_noirq(struct device *dev) | ||
914 | { | ||
915 | struct sysc *ddata; | ||
916 | int error; | ||
917 | |||
918 | ddata = sysc_child_to_parent(dev); | ||
919 | |||
920 | if (ddata->child_needs_resume) { | ||
921 | ddata->child_needs_resume = false; | ||
922 | |||
923 | error = sysc_runtime_resume(ddata->dev); | ||
924 | if (error) | ||
925 | dev_err(ddata->dev, | ||
926 | "%s runtime resume error: %i\n", | ||
927 | __func__, error); | ||
928 | |||
929 | error = pm_generic_runtime_resume(dev); | ||
930 | if (error) | ||
931 | dev_err(ddata->dev, | ||
932 | "%s generic runtime resume: %i\n", | ||
933 | __func__, error); | ||
934 | } | ||
935 | |||
936 | return pm_generic_resume_noirq(dev); | ||
937 | } | ||
938 | #endif | ||
939 | |||
940 | struct dev_pm_domain sysc_child_pm_domain = { | ||
941 | .ops = { | ||
942 | SET_RUNTIME_PM_OPS(sysc_child_runtime_suspend, | ||
943 | sysc_child_runtime_resume, | ||
944 | NULL) | ||
945 | USE_PLATFORM_PM_SLEEP_OPS | ||
946 | SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(sysc_child_suspend_noirq, | ||
947 | sysc_child_resume_noirq) | ||
948 | } | ||
949 | }; | ||
950 | |||
951 | /** | ||
952 | * sysc_legacy_idle_quirk - handle children in omap_device compatible way | ||
953 | * @ddata: device driver data | ||
954 | * @child: child device driver | ||
955 | * | ||
956 | * Allow idle for child devices as done with _od_runtime_suspend(). | ||
957 | * Otherwise many child devices will not idle because of the permanent | ||
958 | * parent usecount set in pm_runtime_irq_safe(). | ||
959 | * | ||
960 | * Note that the long term solution is to just modify the child device | ||
961 | * drivers to not set pm_runtime_irq_safe() and then this can be just | ||
962 | * dropped. | ||
963 | */ | ||
964 | static void sysc_legacy_idle_quirk(struct sysc *ddata, struct device *child) | ||
965 | { | ||
966 | if (!ddata->legacy_mode) | ||
967 | return; | ||
968 | |||
969 | if (ddata->cfg.quirks & SYSC_QUIRK_LEGACY_IDLE) | ||
970 | dev_pm_domain_set(child, &sysc_child_pm_domain); | ||
971 | } | ||
972 | |||
756 | static int sysc_notifier_call(struct notifier_block *nb, | 973 | static int sysc_notifier_call(struct notifier_block *nb, |
757 | unsigned long event, void *device) | 974 | unsigned long event, void *device) |
758 | { | 975 | { |
@@ -770,6 +987,7 @@ static int sysc_notifier_call(struct notifier_block *nb, | |||
770 | if (error && error != -EEXIST) | 987 | if (error && error != -EEXIST) |
771 | dev_warn(ddata->dev, "could not add %s fck: %i\n", | 988 | dev_warn(ddata->dev, "could not add %s fck: %i\n", |
772 | dev_name(dev), error); | 989 | dev_name(dev), error); |
990 | sysc_legacy_idle_quirk(ddata, dev); | ||
773 | break; | 991 | break; |
774 | default: | 992 | default: |
775 | break; | 993 | break; |
@@ -974,7 +1192,8 @@ static const struct sysc_capabilities sysc_34xx_sr = { | |||
974 | .type = TI_SYSC_OMAP34XX_SR, | 1192 | .type = TI_SYSC_OMAP34XX_SR, |
975 | .sysc_mask = SYSC_OMAP2_CLOCKACTIVITY, | 1193 | .sysc_mask = SYSC_OMAP2_CLOCKACTIVITY, |
976 | .regbits = &sysc_regbits_omap34xx_sr, | 1194 | .regbits = &sysc_regbits_omap34xx_sr, |
977 | .mod_quirks = SYSC_QUIRK_USE_CLOCKACT | SYSC_QUIRK_UNCACHED, | 1195 | .mod_quirks = SYSC_QUIRK_USE_CLOCKACT | SYSC_QUIRK_UNCACHED | |
1196 | SYSC_QUIRK_LEGACY_IDLE, | ||
978 | }; | 1197 | }; |
979 | 1198 | ||
980 | /* | 1199 | /* |
@@ -995,12 +1214,13 @@ static const struct sysc_capabilities sysc_36xx_sr = { | |||
995 | .type = TI_SYSC_OMAP36XX_SR, | 1214 | .type = TI_SYSC_OMAP36XX_SR, |
996 | .sysc_mask = SYSC_OMAP3_SR_ENAWAKEUP, | 1215 | .sysc_mask = SYSC_OMAP3_SR_ENAWAKEUP, |
997 | .regbits = &sysc_regbits_omap36xx_sr, | 1216 | .regbits = &sysc_regbits_omap36xx_sr, |
998 | .mod_quirks = SYSC_QUIRK_UNCACHED, | 1217 | .mod_quirks = SYSC_QUIRK_UNCACHED | SYSC_QUIRK_LEGACY_IDLE, |
999 | }; | 1218 | }; |
1000 | 1219 | ||
1001 | static const struct sysc_capabilities sysc_omap4_sr = { | 1220 | static const struct sysc_capabilities sysc_omap4_sr = { |
1002 | .type = TI_SYSC_OMAP4_SR, | 1221 | .type = TI_SYSC_OMAP4_SR, |
1003 | .regbits = &sysc_regbits_omap36xx_sr, | 1222 | .regbits = &sysc_regbits_omap36xx_sr, |
1223 | .mod_quirks = SYSC_QUIRK_LEGACY_IDLE, | ||
1004 | }; | 1224 | }; |
1005 | 1225 | ||
1006 | /* | 1226 | /* |
diff --git a/include/linux/platform_data/ti-sysc.h b/include/linux/platform_data/ti-sysc.h index 4176cb90e195..80ce28d40832 100644 --- a/include/linux/platform_data/ti-sysc.h +++ b/include/linux/platform_data/ti-sysc.h | |||
@@ -45,6 +45,7 @@ struct sysc_regbits { | |||
45 | s8 emufree_shift; | 45 | s8 emufree_shift; |
46 | }; | 46 | }; |
47 | 47 | ||
48 | #define SYSC_QUIRK_LEGACY_IDLE BIT(8) | ||
48 | #define SYSC_QUIRK_RESET_STATUS BIT(7) | 49 | #define SYSC_QUIRK_RESET_STATUS BIT(7) |
49 | #define SYSC_QUIRK_NO_IDLE_ON_INIT BIT(6) | 50 | #define SYSC_QUIRK_NO_IDLE_ON_INIT BIT(6) |
50 | #define SYSC_QUIRK_NO_RESET_ON_INIT BIT(5) | 51 | #define SYSC_QUIRK_NO_RESET_ON_INIT BIT(5) |