1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright 2019 NXP. |
4 | */ |
5 | |
6 | #include <linux/init.h> |
7 | #include <linux/io.h> |
8 | #include <linux/of_address.h> |
9 | #include <linux/slab.h> |
10 | #include <linux/sys_soc.h> |
11 | #include <linux/platform_device.h> |
12 | #include <linux/arm-smccc.h> |
13 | #include <linux/of.h> |
14 | #include <linux/clk.h> |
15 | |
16 | #define REV_B1 0x21 |
17 | |
18 | #define IMX8MQ_SW_INFO_B1 0x40 |
19 | #define IMX8MQ_SW_MAGIC_B1 0xff0055aa |
20 | |
21 | #define IMX_SIP_GET_SOC_INFO 0xc2000006 |
22 | |
23 | #define OCOTP_UID_LOW 0x410 |
24 | #define OCOTP_UID_HIGH 0x420 |
25 | |
26 | #define IMX8MP_OCOTP_UID_OFFSET 0x10 |
27 | |
28 | /* Same as ANADIG_DIGPROG_IMX7D */ |
29 | #define ANADIG_DIGPROG_IMX8MM 0x800 |
30 | |
31 | struct imx8_soc_data { |
32 | char *name; |
33 | u32 (*soc_revision)(void); |
34 | }; |
35 | |
36 | static u64 soc_uid; |
37 | |
38 | #ifdef CONFIG_HAVE_ARM_SMCCC |
39 | static u32 imx8mq_soc_revision_from_atf(void) |
40 | { |
41 | struct arm_smccc_res res; |
42 | |
43 | arm_smccc_smc(IMX_SIP_GET_SOC_INFO, 0, 0, 0, 0, 0, 0, 0, &res); |
44 | |
45 | if (res.a0 == SMCCC_RET_NOT_SUPPORTED) |
46 | return 0; |
47 | else |
48 | return res.a0 & 0xff; |
49 | } |
50 | #else |
51 | static inline u32 imx8mq_soc_revision_from_atf(void) { return 0; }; |
52 | #endif |
53 | |
54 | static u32 __init imx8mq_soc_revision(void) |
55 | { |
56 | struct device_node *np; |
57 | void __iomem *ocotp_base; |
58 | u32 magic; |
59 | u32 rev; |
60 | struct clk *clk; |
61 | |
62 | np = of_find_compatible_node(NULL, NULL, compat: "fsl,imx8mq-ocotp" ); |
63 | if (!np) |
64 | return 0; |
65 | |
66 | ocotp_base = of_iomap(node: np, index: 0); |
67 | WARN_ON(!ocotp_base); |
68 | clk = of_clk_get_by_name(np, NULL); |
69 | if (IS_ERR(ptr: clk)) { |
70 | WARN_ON(IS_ERR(clk)); |
71 | return 0; |
72 | } |
73 | |
74 | clk_prepare_enable(clk); |
75 | |
76 | /* |
77 | * SOC revision on older imx8mq is not available in fuses so query |
78 | * the value from ATF instead. |
79 | */ |
80 | rev = imx8mq_soc_revision_from_atf(); |
81 | if (!rev) { |
82 | magic = readl_relaxed(ocotp_base + IMX8MQ_SW_INFO_B1); |
83 | if (magic == IMX8MQ_SW_MAGIC_B1) |
84 | rev = REV_B1; |
85 | } |
86 | |
87 | soc_uid = readl_relaxed(ocotp_base + OCOTP_UID_HIGH); |
88 | soc_uid <<= 32; |
89 | soc_uid |= readl_relaxed(ocotp_base + OCOTP_UID_LOW); |
90 | |
91 | clk_disable_unprepare(clk); |
92 | clk_put(clk); |
93 | iounmap(addr: ocotp_base); |
94 | of_node_put(node: np); |
95 | |
96 | return rev; |
97 | } |
98 | |
99 | static void __init imx8mm_soc_uid(void) |
100 | { |
101 | void __iomem *ocotp_base; |
102 | struct device_node *np; |
103 | struct clk *clk; |
104 | u32 offset = of_machine_is_compatible(compat: "fsl,imx8mp" ) ? |
105 | IMX8MP_OCOTP_UID_OFFSET : 0; |
106 | |
107 | np = of_find_compatible_node(NULL, NULL, compat: "fsl,imx8mm-ocotp" ); |
108 | if (!np) |
109 | return; |
110 | |
111 | ocotp_base = of_iomap(node: np, index: 0); |
112 | WARN_ON(!ocotp_base); |
113 | clk = of_clk_get_by_name(np, NULL); |
114 | if (IS_ERR(ptr: clk)) { |
115 | WARN_ON(IS_ERR(clk)); |
116 | return; |
117 | } |
118 | |
119 | clk_prepare_enable(clk); |
120 | |
121 | soc_uid = readl_relaxed(ocotp_base + OCOTP_UID_HIGH + offset); |
122 | soc_uid <<= 32; |
123 | soc_uid |= readl_relaxed(ocotp_base + OCOTP_UID_LOW + offset); |
124 | |
125 | clk_disable_unprepare(clk); |
126 | clk_put(clk); |
127 | iounmap(addr: ocotp_base); |
128 | of_node_put(node: np); |
129 | } |
130 | |
131 | static u32 __init imx8mm_soc_revision(void) |
132 | { |
133 | struct device_node *np; |
134 | void __iomem *anatop_base; |
135 | u32 rev; |
136 | |
137 | np = of_find_compatible_node(NULL, NULL, compat: "fsl,imx8mm-anatop" ); |
138 | if (!np) |
139 | return 0; |
140 | |
141 | anatop_base = of_iomap(node: np, index: 0); |
142 | WARN_ON(!anatop_base); |
143 | |
144 | rev = readl_relaxed(anatop_base + ANADIG_DIGPROG_IMX8MM); |
145 | |
146 | iounmap(addr: anatop_base); |
147 | of_node_put(node: np); |
148 | |
149 | imx8mm_soc_uid(); |
150 | |
151 | return rev; |
152 | } |
153 | |
154 | static const struct imx8_soc_data imx8mq_soc_data = { |
155 | .name = "i.MX8MQ" , |
156 | .soc_revision = imx8mq_soc_revision, |
157 | }; |
158 | |
159 | static const struct imx8_soc_data imx8mm_soc_data = { |
160 | .name = "i.MX8MM" , |
161 | .soc_revision = imx8mm_soc_revision, |
162 | }; |
163 | |
164 | static const struct imx8_soc_data imx8mn_soc_data = { |
165 | .name = "i.MX8MN" , |
166 | .soc_revision = imx8mm_soc_revision, |
167 | }; |
168 | |
169 | static const struct imx8_soc_data imx8mp_soc_data = { |
170 | .name = "i.MX8MP" , |
171 | .soc_revision = imx8mm_soc_revision, |
172 | }; |
173 | |
174 | static __maybe_unused const struct of_device_id imx8_soc_match[] = { |
175 | { .compatible = "fsl,imx8mq" , .data = &imx8mq_soc_data, }, |
176 | { .compatible = "fsl,imx8mm" , .data = &imx8mm_soc_data, }, |
177 | { .compatible = "fsl,imx8mn" , .data = &imx8mn_soc_data, }, |
178 | { .compatible = "fsl,imx8mp" , .data = &imx8mp_soc_data, }, |
179 | { } |
180 | }; |
181 | |
182 | #define imx8_revision(soc_rev) \ |
183 | soc_rev ? \ |
184 | kasprintf(GFP_KERNEL, "%d.%d", (soc_rev >> 4) & 0xf, soc_rev & 0xf) : \ |
185 | "unknown" |
186 | |
187 | static int __init imx8_soc_init(void) |
188 | { |
189 | struct soc_device_attribute *soc_dev_attr; |
190 | struct soc_device *soc_dev; |
191 | const struct of_device_id *id; |
192 | u32 soc_rev = 0; |
193 | const struct imx8_soc_data *data; |
194 | int ret; |
195 | |
196 | soc_dev_attr = kzalloc(size: sizeof(*soc_dev_attr), GFP_KERNEL); |
197 | if (!soc_dev_attr) |
198 | return -ENOMEM; |
199 | |
200 | soc_dev_attr->family = "Freescale i.MX" ; |
201 | |
202 | ret = of_property_read_string(np: of_root, propname: "model" , out_string: &soc_dev_attr->machine); |
203 | if (ret) |
204 | goto free_soc; |
205 | |
206 | id = of_match_node(matches: imx8_soc_match, node: of_root); |
207 | if (!id) { |
208 | ret = -ENODEV; |
209 | goto free_soc; |
210 | } |
211 | |
212 | data = id->data; |
213 | if (data) { |
214 | soc_dev_attr->soc_id = data->name; |
215 | if (data->soc_revision) |
216 | soc_rev = data->soc_revision(); |
217 | } |
218 | |
219 | soc_dev_attr->revision = imx8_revision(soc_rev); |
220 | if (!soc_dev_attr->revision) { |
221 | ret = -ENOMEM; |
222 | goto free_soc; |
223 | } |
224 | |
225 | soc_dev_attr->serial_number = kasprintf(GFP_KERNEL, fmt: "%016llX" , soc_uid); |
226 | if (!soc_dev_attr->serial_number) { |
227 | ret = -ENOMEM; |
228 | goto free_rev; |
229 | } |
230 | |
231 | soc_dev = soc_device_register(soc_plat_dev_attr: soc_dev_attr); |
232 | if (IS_ERR(ptr: soc_dev)) { |
233 | ret = PTR_ERR(ptr: soc_dev); |
234 | goto free_serial_number; |
235 | } |
236 | |
237 | pr_info("SoC: %s revision %s\n" , soc_dev_attr->soc_id, |
238 | soc_dev_attr->revision); |
239 | |
240 | if (IS_ENABLED(CONFIG_ARM_IMX_CPUFREQ_DT)) |
241 | platform_device_register_simple(name: "imx-cpufreq-dt" , id: -1, NULL, num: 0); |
242 | |
243 | return 0; |
244 | |
245 | free_serial_number: |
246 | kfree(objp: soc_dev_attr->serial_number); |
247 | free_rev: |
248 | if (strcmp(soc_dev_attr->revision, "unknown" )) |
249 | kfree(objp: soc_dev_attr->revision); |
250 | free_soc: |
251 | kfree(objp: soc_dev_attr); |
252 | return ret; |
253 | } |
254 | device_initcall(imx8_soc_init); |
255 | MODULE_LICENSE("GPL" ); |
256 | |