1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com> |
4 | */ |
5 | |
6 | #include <linux/clk.h> |
7 | #include <linux/clk-provider.h> |
8 | #include <linux/clkdev.h> |
9 | #include <linux/clk/at91_pmc.h> |
10 | #include <linux/of.h> |
11 | #include <linux/of_address.h> |
12 | #include <linux/mfd/syscon.h> |
13 | #include <linux/platform_device.h> |
14 | #include <linux/regmap.h> |
15 | #include <linux/syscore_ops.h> |
16 | |
17 | #include <asm/proc-fns.h> |
18 | |
19 | #include "pmc.h" |
20 | |
21 | #define PMC_MAX_IDS 128 |
22 | #define PMC_MAX_PCKS 8 |
23 | |
24 | int of_at91_get_clk_range(struct device_node *np, const char *propname, |
25 | struct clk_range *range) |
26 | { |
27 | u32 min, max; |
28 | int ret; |
29 | |
30 | ret = of_property_read_u32_index(np, propname, index: 0, out_value: &min); |
31 | if (ret) |
32 | return ret; |
33 | |
34 | ret = of_property_read_u32_index(np, propname, index: 1, out_value: &max); |
35 | if (ret) |
36 | return ret; |
37 | |
38 | if (range) { |
39 | range->min = min; |
40 | range->max = max; |
41 | } |
42 | |
43 | return 0; |
44 | } |
45 | EXPORT_SYMBOL_GPL(of_at91_get_clk_range); |
46 | |
47 | struct clk_hw *of_clk_hw_pmc_get(struct of_phandle_args *clkspec, void *data) |
48 | { |
49 | unsigned int type = clkspec->args[0]; |
50 | unsigned int idx = clkspec->args[1]; |
51 | struct pmc_data *pmc_data = data; |
52 | |
53 | switch (type) { |
54 | case PMC_TYPE_CORE: |
55 | if (idx < pmc_data->ncore) |
56 | return pmc_data->chws[idx]; |
57 | break; |
58 | case PMC_TYPE_SYSTEM: |
59 | if (idx < pmc_data->nsystem) |
60 | return pmc_data->shws[idx]; |
61 | break; |
62 | case PMC_TYPE_PERIPHERAL: |
63 | if (idx < pmc_data->nperiph) |
64 | return pmc_data->phws[idx]; |
65 | break; |
66 | case PMC_TYPE_GCK: |
67 | if (idx < pmc_data->ngck) |
68 | return pmc_data->ghws[idx]; |
69 | break; |
70 | case PMC_TYPE_PROGRAMMABLE: |
71 | if (idx < pmc_data->npck) |
72 | return pmc_data->pchws[idx]; |
73 | break; |
74 | default: |
75 | break; |
76 | } |
77 | |
78 | pr_err("%s: invalid type (%u) or index (%u)\n" , __func__, type, idx); |
79 | |
80 | return ERR_PTR(error: -EINVAL); |
81 | } |
82 | |
83 | struct pmc_data *pmc_data_allocate(unsigned int ncore, unsigned int nsystem, |
84 | unsigned int nperiph, unsigned int ngck, |
85 | unsigned int npck) |
86 | { |
87 | unsigned int num_clks = ncore + nsystem + nperiph + ngck + npck; |
88 | struct pmc_data *pmc_data; |
89 | |
90 | pmc_data = kzalloc(struct_size(pmc_data, hwtable, num_clks), |
91 | GFP_KERNEL); |
92 | if (!pmc_data) |
93 | return NULL; |
94 | |
95 | pmc_data->ncore = ncore; |
96 | pmc_data->chws = pmc_data->hwtable; |
97 | |
98 | pmc_data->nsystem = nsystem; |
99 | pmc_data->shws = pmc_data->chws + ncore; |
100 | |
101 | pmc_data->nperiph = nperiph; |
102 | pmc_data->phws = pmc_data->shws + nsystem; |
103 | |
104 | pmc_data->ngck = ngck; |
105 | pmc_data->ghws = pmc_data->phws + nperiph; |
106 | |
107 | pmc_data->npck = npck; |
108 | pmc_data->pchws = pmc_data->ghws + ngck; |
109 | |
110 | return pmc_data; |
111 | } |
112 | |
113 | #ifdef CONFIG_PM |
114 | |
115 | /* Address in SECURAM that say if we suspend to backup mode. */ |
116 | static void __iomem *at91_pmc_backup_suspend; |
117 | |
118 | static int at91_pmc_suspend(void) |
119 | { |
120 | unsigned int backup; |
121 | |
122 | if (!at91_pmc_backup_suspend) |
123 | return 0; |
124 | |
125 | backup = readl_relaxed(at91_pmc_backup_suspend); |
126 | if (!backup) |
127 | return 0; |
128 | |
129 | return clk_save_context(); |
130 | } |
131 | |
132 | static void at91_pmc_resume(void) |
133 | { |
134 | unsigned int backup; |
135 | |
136 | if (!at91_pmc_backup_suspend) |
137 | return; |
138 | |
139 | backup = readl_relaxed(at91_pmc_backup_suspend); |
140 | if (!backup) |
141 | return; |
142 | |
143 | clk_restore_context(); |
144 | } |
145 | |
146 | static struct syscore_ops pmc_syscore_ops = { |
147 | .suspend = at91_pmc_suspend, |
148 | .resume = at91_pmc_resume, |
149 | }; |
150 | |
151 | static const struct of_device_id pmc_dt_ids[] = { |
152 | { .compatible = "atmel,sama5d2-pmc" }, |
153 | { .compatible = "microchip,sama7g5-pmc" , }, |
154 | { /* sentinel */ } |
155 | }; |
156 | |
157 | static int __init pmc_register_ops(void) |
158 | { |
159 | struct device_node *np; |
160 | |
161 | np = of_find_matching_node(NULL, matches: pmc_dt_ids); |
162 | if (!np) |
163 | return -ENODEV; |
164 | |
165 | if (!of_device_is_available(device: np)) { |
166 | of_node_put(node: np); |
167 | return -ENODEV; |
168 | } |
169 | of_node_put(node: np); |
170 | |
171 | np = of_find_compatible_node(NULL, NULL, compat: "atmel,sama5d2-securam" ); |
172 | if (!np) |
173 | return -ENODEV; |
174 | |
175 | if (!of_device_is_available(device: np)) { |
176 | of_node_put(node: np); |
177 | return -ENODEV; |
178 | } |
179 | of_node_put(node: np); |
180 | |
181 | at91_pmc_backup_suspend = of_iomap(node: np, index: 0); |
182 | if (!at91_pmc_backup_suspend) { |
183 | pr_warn("%s(): unable to map securam\n" , __func__); |
184 | return -ENOMEM; |
185 | } |
186 | |
187 | register_syscore_ops(ops: &pmc_syscore_ops); |
188 | |
189 | return 0; |
190 | } |
191 | /* This has to happen before arch_initcall because of the tcb_clksrc driver */ |
192 | postcore_initcall(pmc_register_ops); |
193 | #endif |
194 | |