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/module.h> 9 #include <linux/nvmem-consumer.h> 10 #include <linux/of_address.h> 11 #include <linux/slab.h> 12 #include <linux/sys_soc.h> 13 #include <linux/platform_device.h> 14 #include <linux/arm-smccc.h> 15 #include <linux/of.h> 16 17 #define REV_B1 0x21 18 19 #define IMX8MQ_SW_INFO_B1 0x40 20 #define IMX8MQ_SW_MAGIC_B1 0xff0055aa 21 22 #define IMX_SIP_GET_SOC_INFO 0xc2000006 23 24 #define OCOTP_UID_LOW 0x410 25 #define OCOTP_UID_HIGH 0x420 26 27 #define IMX8MP_OCOTP_UID_OFFSET 0x10 28 29 /* Same as ANADIG_DIGPROG_IMX7D */ 30 #define ANADIG_DIGPROG_IMX8MM 0x800 31 32 struct imx8_soc_data { 33 char *name; 34 u32 (*soc_revision)(struct device *dev); 35 }; 36 37 static u64 soc_uid; 38 39 #ifdef CONFIG_HAVE_ARM_SMCCC 40 static u32 imx8mq_soc_revision_from_atf(void) 41 { 42 struct arm_smccc_res res; 43 44 arm_smccc_smc(IMX_SIP_GET_SOC_INFO, 0, 0, 0, 0, 0, 0, 0, &res); 45 46 if (res.a0 == SMCCC_RET_NOT_SUPPORTED) 47 return 0; 48 else 49 return res.a0 & 0xff; 50 } 51 #else 52 static inline u32 imx8mq_soc_revision_from_atf(void) { return 0; }; 53 #endif 54 55 static u32 __init imx8mq_soc_revision(struct device *dev) 56 { 57 struct device_node *np; 58 void __iomem *ocotp_base; 59 u32 magic; 60 u32 rev; 61 62 np = of_find_compatible_node(NULL, NULL, "fsl,imx8mq-ocotp"); 63 if (!np) 64 return 0; 65 66 ocotp_base = of_iomap(np, 0); 67 WARN_ON(!ocotp_base); 68 69 /* 70 * SOC revision on older imx8mq is not available in fuses so query 71 * the value from ATF instead. 72 */ 73 rev = imx8mq_soc_revision_from_atf(); 74 if (!rev) { 75 magic = readl_relaxed(ocotp_base + IMX8MQ_SW_INFO_B1); 76 if (magic == IMX8MQ_SW_MAGIC_B1) 77 rev = REV_B1; 78 } 79 80 if (dev) { 81 int ret; 82 83 ret = nvmem_cell_read_u64(dev, "soc_unique_id", &soc_uid); 84 if (ret) { 85 iounmap(ocotp_base); 86 of_node_put(np); 87 return ret; 88 } 89 } else { 90 soc_uid = readl_relaxed(ocotp_base + OCOTP_UID_HIGH); 91 soc_uid <<= 32; 92 soc_uid |= readl_relaxed(ocotp_base + OCOTP_UID_LOW); 93 } 94 95 iounmap(ocotp_base); 96 of_node_put(np); 97 98 return rev; 99 } 100 101 static void __init imx8mm_soc_uid(void) 102 { 103 void __iomem *ocotp_base; 104 struct device_node *np; 105 u32 offset = of_machine_is_compatible("fsl,imx8mp") ? 106 IMX8MP_OCOTP_UID_OFFSET : 0; 107 108 np = of_find_compatible_node(NULL, NULL, "fsl,imx8mm-ocotp"); 109 if (!np) 110 return; 111 112 ocotp_base = of_iomap(np, 0); 113 WARN_ON(!ocotp_base); 114 115 soc_uid = readl_relaxed(ocotp_base + OCOTP_UID_HIGH + offset); 116 soc_uid <<= 32; 117 soc_uid |= readl_relaxed(ocotp_base + OCOTP_UID_LOW + offset); 118 119 iounmap(ocotp_base); 120 of_node_put(np); 121 } 122 123 static u32 __init imx8mm_soc_revision(struct device *dev) 124 { 125 struct device_node *np; 126 void __iomem *anatop_base; 127 u32 rev; 128 129 np = of_find_compatible_node(NULL, NULL, "fsl,imx8mm-anatop"); 130 if (!np) 131 return 0; 132 133 anatop_base = of_iomap(np, 0); 134 WARN_ON(!anatop_base); 135 136 rev = readl_relaxed(anatop_base + ANADIG_DIGPROG_IMX8MM); 137 138 iounmap(anatop_base); 139 of_node_put(np); 140 141 if (dev) { 142 int ret; 143 144 ret = nvmem_cell_read_u64(dev, "soc_unique_id", &soc_uid); 145 if (ret) 146 return ret; 147 } else { 148 imx8mm_soc_uid(); 149 } 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_machine_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 static __maybe_unused const struct of_device_id imx8_soc_match[] = { 183 { .compatible = "fsl,imx8mq-soc", .data = &imx8mq_soc_data, }, 184 { .compatible = "fsl,imx8mm-soc", .data = &imx8mm_soc_data, }, 185 { .compatible = "fsl,imx8mn-soc", .data = &imx8mn_soc_data, }, 186 { .compatible = "fsl,imx8mp-soc", .data = &imx8mp_soc_data, }, 187 { } 188 }; 189 190 #define imx8_revision(soc_rev) \ 191 soc_rev ? \ 192 kasprintf(GFP_KERNEL, "%d.%d", (soc_rev >> 4) & 0xf, soc_rev & 0xf) : \ 193 "unknown" 194 195 static int imx8_soc_info(struct platform_device *pdev) 196 { 197 struct soc_device_attribute *soc_dev_attr; 198 struct soc_device *soc_dev; 199 const struct of_device_id *id; 200 u32 soc_rev = 0; 201 const struct imx8_soc_data *data; 202 int ret; 203 204 soc_dev_attr = kzalloc(sizeof(*soc_dev_attr), GFP_KERNEL); 205 if (!soc_dev_attr) 206 return -ENOMEM; 207 208 soc_dev_attr->family = "Freescale i.MX"; 209 210 ret = of_property_read_string(of_root, "model", &soc_dev_attr->machine); 211 if (ret) 212 goto free_soc; 213 214 if (pdev) 215 id = of_match_node(imx8_soc_match, pdev->dev.of_node); 216 else 217 id = of_match_node(imx8_machine_match, of_root); 218 if (!id) { 219 ret = -ENODEV; 220 goto free_soc; 221 } 222 223 data = id->data; 224 if (data) { 225 soc_dev_attr->soc_id = data->name; 226 if (data->soc_revision) { 227 if (pdev) { 228 soc_rev = data->soc_revision(&pdev->dev); 229 ret = soc_rev; 230 if (ret < 0) 231 goto free_soc; 232 } else { 233 soc_rev = data->soc_revision(NULL); 234 } 235 } 236 } 237 238 soc_dev_attr->revision = imx8_revision(soc_rev); 239 if (!soc_dev_attr->revision) { 240 ret = -ENOMEM; 241 goto free_soc; 242 } 243 244 soc_dev_attr->serial_number = kasprintf(GFP_KERNEL, "%016llX", soc_uid); 245 if (!soc_dev_attr->serial_number) { 246 ret = -ENOMEM; 247 goto free_rev; 248 } 249 250 soc_dev = soc_device_register(soc_dev_attr); 251 if (IS_ERR(soc_dev)) { 252 ret = PTR_ERR(soc_dev); 253 goto free_serial_number; 254 } 255 256 pr_info("SoC: %s revision %s\n", soc_dev_attr->soc_id, 257 soc_dev_attr->revision); 258 259 if (IS_ENABLED(CONFIG_ARM_IMX_CPUFREQ_DT)) 260 platform_device_register_simple("imx-cpufreq-dt", -1, NULL, 0); 261 262 return 0; 263 264 free_serial_number: 265 kfree(soc_dev_attr->serial_number); 266 free_rev: 267 if (strcmp(soc_dev_attr->revision, "unknown")) 268 kfree(soc_dev_attr->revision); 269 free_soc: 270 kfree(soc_dev_attr); 271 return ret; 272 } 273 274 /* Retain device_initcall is for backward compatibility with DTS. */ 275 static int __init imx8_soc_init(void) 276 { 277 if (of_find_matching_node_and_match(NULL, imx8_soc_match, NULL)) 278 return 0; 279 280 return imx8_soc_info(NULL); 281 } 282 device_initcall(imx8_soc_init); 283 284 static struct platform_driver imx8_soc_info_driver = { 285 .probe = imx8_soc_info, 286 .driver = { 287 .name = "imx8_soc_info", 288 .of_match_table = imx8_soc_match, 289 }, 290 }; 291 292 module_platform_driver(imx8_soc_info_driver); 293 MODULE_LICENSE("GPL v2"); 294