Commit | Line | Data |
---|---|---|
d2912cb1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
1e832e51 TF |
2 | /* |
3 | * Copyright (c) 2014 Samsung Electronics Co., Ltd. | |
4 | * Author: Tomasz Figa <t.figa@samsung.com> | |
5 | * | |
1e832e51 TF |
6 | * Clock driver for Exynos clock output |
7 | */ | |
8 | ||
6f1ed07a | 9 | #include <linux/slab.h> |
1e832e51 | 10 | #include <linux/clk.h> |
1e832e51 | 11 | #include <linux/clk-provider.h> |
62e59c4e | 12 | #include <linux/io.h> |
1e832e51 TF |
13 | #include <linux/of.h> |
14 | #include <linux/of_address.h> | |
15 | #include <linux/syscore_ops.h> | |
16 | ||
17 | #define EXYNOS_CLKOUT_NR_CLKS 1 | |
18 | #define EXYNOS_CLKOUT_PARENTS 32 | |
19 | ||
20 | #define EXYNOS_PMU_DEBUG_REG 0xa00 | |
21 | #define EXYNOS_CLKOUT_DISABLE_SHIFT 0 | |
22 | #define EXYNOS_CLKOUT_MUX_SHIFT 8 | |
23 | #define EXYNOS4_CLKOUT_MUX_MASK 0xf | |
24 | #define EXYNOS5_CLKOUT_MUX_MASK 0x1f | |
25 | ||
26 | struct exynos_clkout { | |
27 | struct clk_gate gate; | |
28 | struct clk_mux mux; | |
29 | spinlock_t slock; | |
1e832e51 TF |
30 | void __iomem *reg; |
31 | u32 pmu_debug_save; | |
cf139514 | 32 | struct clk_hw_onecell_data data; |
1e832e51 TF |
33 | }; |
34 | ||
35 | static struct exynos_clkout *clkout; | |
36 | ||
37 | static int exynos_clkout_suspend(void) | |
38 | { | |
39 | clkout->pmu_debug_save = readl(clkout->reg + EXYNOS_PMU_DEBUG_REG); | |
40 | ||
41 | return 0; | |
42 | } | |
43 | ||
44 | static void exynos_clkout_resume(void) | |
45 | { | |
46 | writel(clkout->pmu_debug_save, clkout->reg + EXYNOS_PMU_DEBUG_REG); | |
47 | } | |
48 | ||
49 | static struct syscore_ops exynos_clkout_syscore_ops = { | |
50 | .suspend = exynos_clkout_suspend, | |
51 | .resume = exynos_clkout_resume, | |
52 | }; | |
53 | ||
54 | static void __init exynos_clkout_init(struct device_node *node, u32 mux_mask) | |
55 | { | |
56 | const char *parent_names[EXYNOS_CLKOUT_PARENTS]; | |
57 | struct clk *parents[EXYNOS_CLKOUT_PARENTS]; | |
58 | int parent_count; | |
59 | int ret; | |
60 | int i; | |
61 | ||
acafe7e3 | 62 | clkout = kzalloc(struct_size(clkout, data.hws, EXYNOS_CLKOUT_NR_CLKS), |
cf139514 | 63 | GFP_KERNEL); |
1e832e51 TF |
64 | if (!clkout) |
65 | return; | |
66 | ||
67 | spin_lock_init(&clkout->slock); | |
68 | ||
69 | parent_count = 0; | |
70 | for (i = 0; i < EXYNOS_CLKOUT_PARENTS; ++i) { | |
71 | char name[] = "clkoutXX"; | |
72 | ||
73 | snprintf(name, sizeof(name), "clkout%d", i); | |
74 | parents[i] = of_clk_get_by_name(node, name); | |
75 | if (IS_ERR(parents[i])) { | |
76 | parent_names[i] = "none"; | |
77 | continue; | |
78 | } | |
79 | ||
80 | parent_names[i] = __clk_get_name(parents[i]); | |
81 | parent_count = i + 1; | |
82 | } | |
83 | ||
84 | if (!parent_count) | |
85 | goto free_clkout; | |
86 | ||
87 | clkout->reg = of_iomap(node, 0); | |
88 | if (!clkout->reg) | |
89 | goto clks_put; | |
90 | ||
91 | clkout->gate.reg = clkout->reg + EXYNOS_PMU_DEBUG_REG; | |
92 | clkout->gate.bit_idx = EXYNOS_CLKOUT_DISABLE_SHIFT; | |
93 | clkout->gate.flags = CLK_GATE_SET_TO_DISABLE; | |
94 | clkout->gate.lock = &clkout->slock; | |
95 | ||
96 | clkout->mux.reg = clkout->reg + EXYNOS_PMU_DEBUG_REG; | |
97 | clkout->mux.mask = mux_mask; | |
98 | clkout->mux.shift = EXYNOS_CLKOUT_MUX_SHIFT; | |
99 | clkout->mux.lock = &clkout->slock; | |
100 | ||
cf139514 | 101 | clkout->data.hws[0] = clk_hw_register_composite(NULL, "clkout", |
1e832e51 TF |
102 | parent_names, parent_count, &clkout->mux.hw, |
103 | &clk_mux_ops, NULL, NULL, &clkout->gate.hw, | |
104 | &clk_gate_ops, CLK_SET_RATE_PARENT | |
105 | | CLK_SET_RATE_NO_REPARENT); | |
cf139514 | 106 | if (IS_ERR(clkout->data.hws[0])) |
1e832e51 TF |
107 | goto err_unmap; |
108 | ||
cf139514 MS |
109 | clkout->data.num = EXYNOS_CLKOUT_NR_CLKS; |
110 | ret = of_clk_add_hw_provider(node, of_clk_hw_onecell_get, &clkout->data); | |
1e832e51 TF |
111 | if (ret) |
112 | goto err_clk_unreg; | |
113 | ||
114 | register_syscore_ops(&exynos_clkout_syscore_ops); | |
115 | ||
116 | return; | |
117 | ||
118 | err_clk_unreg: | |
cf139514 | 119 | clk_hw_unregister(clkout->data.hws[0]); |
1e832e51 TF |
120 | err_unmap: |
121 | iounmap(clkout->reg); | |
122 | clks_put: | |
123 | for (i = 0; i < EXYNOS_CLKOUT_PARENTS; ++i) | |
124 | if (!IS_ERR(parents[i])) | |
125 | clk_put(parents[i]); | |
126 | free_clkout: | |
127 | kfree(clkout); | |
128 | ||
129 | pr_err("%s: failed to register clkout clock\n", __func__); | |
130 | } | |
131 | ||
5c4a9129 MS |
132 | /* |
133 | * We use CLK_OF_DECLARE_DRIVER initialization method to avoid setting | |
134 | * the OF_POPULATED flag on the pmu device tree node, so later the | |
135 | * Exynos PMU platform device can be properly probed with PMU driver. | |
136 | */ | |
137 | ||
1e832e51 TF |
138 | static void __init exynos4_clkout_init(struct device_node *node) |
139 | { | |
140 | exynos_clkout_init(node, EXYNOS4_CLKOUT_MUX_MASK); | |
141 | } | |
5c4a9129 | 142 | CLK_OF_DECLARE_DRIVER(exynos4210_clkout, "samsung,exynos4210-pmu", |
1e832e51 | 143 | exynos4_clkout_init); |
5c4a9129 | 144 | CLK_OF_DECLARE_DRIVER(exynos4412_clkout, "samsung,exynos4412-pmu", |
1e832e51 | 145 | exynos4_clkout_init); |
5c4a9129 | 146 | CLK_OF_DECLARE_DRIVER(exynos3250_clkout, "samsung,exynos3250-pmu", |
abec147f | 147 | exynos4_clkout_init); |
1e832e51 TF |
148 | |
149 | static void __init exynos5_clkout_init(struct device_node *node) | |
150 | { | |
151 | exynos_clkout_init(node, EXYNOS5_CLKOUT_MUX_MASK); | |
152 | } | |
5c4a9129 | 153 | CLK_OF_DECLARE_DRIVER(exynos5250_clkout, "samsung,exynos5250-pmu", |
1e832e51 | 154 | exynos5_clkout_init); |
5c4a9129 | 155 | CLK_OF_DECLARE_DRIVER(exynos5410_clkout, "samsung,exynos5410-pmu", |
d8137e03 | 156 | exynos5_clkout_init); |
5c4a9129 | 157 | CLK_OF_DECLARE_DRIVER(exynos5420_clkout, "samsung,exynos5420-pmu", |
1e832e51 | 158 | exynos5_clkout_init); |
5c4a9129 | 159 | CLK_OF_DECLARE_DRIVER(exynos5433_clkout, "samsung,exynos5433-pmu", |
6166c01c | 160 | exynos5_clkout_init); |