diff options
Diffstat (limited to 'drivers/soc/imx')
-rw-r--r-- | drivers/soc/imx/Makefile | 2 | ||||
-rw-r--r-- | drivers/soc/imx/soc-imx8m.c | 203 | ||||
-rw-r--r-- | drivers/soc/imx/soc-imx9.c | 128 |
3 files changed, 261 insertions, 72 deletions
diff --git a/drivers/soc/imx/Makefile b/drivers/soc/imx/Makefile index 3ad321ca608a..ca6a5fa1618f 100644 --- a/drivers/soc/imx/Makefile +++ b/drivers/soc/imx/Makefile @@ -3,4 +3,4 @@ ifeq ($(CONFIG_ARM),y) obj-$(CONFIG_ARCH_MXC) += soc-imx.o endif obj-$(CONFIG_SOC_IMX8M) += soc-imx8m.o -obj-$(CONFIG_SOC_IMX9) += imx93-src.o +obj-$(CONFIG_SOC_IMX9) += imx93-src.o soc-imx9.o diff --git a/drivers/soc/imx/soc-imx8m.c b/drivers/soc/imx/soc-imx8m.c index 8ac7658e3d52..04a1b60f2f2b 100644 --- a/drivers/soc/imx/soc-imx8m.c +++ b/drivers/soc/imx/soc-imx8m.c @@ -24,13 +24,21 @@ #define OCOTP_UID_HIGH 0x420 #define IMX8MP_OCOTP_UID_OFFSET 0x10 +#define IMX8MP_OCOTP_UID_HIGH 0xE00 /* Same as ANADIG_DIGPROG_IMX7D */ #define ANADIG_DIGPROG_IMX8MM 0x800 struct imx8_soc_data { char *name; - int (*soc_revision)(u32 *socrev, u64 *socuid); + const char *ocotp_compatible; + int (*soc_revision)(struct platform_device *pdev, u32 *socrev); + int (*soc_uid)(struct platform_device *pdev, u64 *socuid); +}; + +struct imx8_soc_drvdata { + void __iomem *ocotp_base; + struct clk *clk; }; #ifdef CONFIG_HAVE_ARM_SMCCC @@ -49,30 +57,24 @@ static u32 imx8mq_soc_revision_from_atf(void) static inline u32 imx8mq_soc_revision_from_atf(void) { return 0; }; #endif -static int imx8mq_soc_revision(u32 *socrev, u64 *socuid) +static int imx8m_soc_uid(struct platform_device *pdev, u64 *socuid) { - struct device_node *np __free(device_node) = - of_find_compatible_node(NULL, NULL, "fsl,imx8mq-ocotp"); - void __iomem *ocotp_base; - u32 magic; - u32 rev; - struct clk *clk; - int ret; - - if (!np) - return -EINVAL; + struct imx8_soc_drvdata *drvdata = platform_get_drvdata(pdev); + void __iomem *ocotp_base = drvdata->ocotp_base; - ocotp_base = of_iomap(np, 0); - if (!ocotp_base) - return -EINVAL; + *socuid = readl_relaxed(ocotp_base + OCOTP_UID_HIGH); + *socuid <<= 32; + *socuid |= readl_relaxed(ocotp_base + OCOTP_UID_LOW); - clk = of_clk_get_by_name(np, NULL); - if (IS_ERR(clk)) { - ret = PTR_ERR(clk); - goto err_clk; - } + return 0; +} - clk_prepare_enable(clk); +static int imx8mq_soc_revision(struct platform_device *pdev, u32 *socrev) +{ + struct imx8_soc_drvdata *drvdata = platform_get_drvdata(pdev); + void __iomem *ocotp_base = drvdata->ocotp_base; + u32 magic; + u32 rev; /* * SOC revision on older imx8mq is not available in fuses so query @@ -85,98 +87,109 @@ static int imx8mq_soc_revision(u32 *socrev, u64 *socuid) rev = REV_B1; } - *socuid = readl_relaxed(ocotp_base + OCOTP_UID_HIGH); - *socuid <<= 32; - *socuid |= readl_relaxed(ocotp_base + OCOTP_UID_LOW); - *socrev = rev; - clk_disable_unprepare(clk); - clk_put(clk); - iounmap(ocotp_base); - return 0; +} -err_clk: - iounmap(ocotp_base); - return ret; +static int imx8mp_soc_uid(struct platform_device *pdev, u64 *socuid) +{ + struct imx8_soc_drvdata *drvdata = platform_get_drvdata(pdev); + void __iomem *ocotp_base = drvdata->ocotp_base; + + socuid[0] = readl_relaxed(ocotp_base + OCOTP_UID_HIGH + IMX8MP_OCOTP_UID_OFFSET); + socuid[0] <<= 32; + socuid[0] |= readl_relaxed(ocotp_base + OCOTP_UID_LOW + IMX8MP_OCOTP_UID_OFFSET); + + socuid[1] = readl_relaxed(ocotp_base + IMX8MP_OCOTP_UID_HIGH + 0x10); + socuid[1] <<= 32; + socuid[1] |= readl_relaxed(ocotp_base + IMX8MP_OCOTP_UID_HIGH); + + return 0; } -static int imx8mm_soc_uid(u64 *socuid) +static int imx8mm_soc_revision(struct platform_device *pdev, u32 *socrev) { struct device_node *np __free(device_node) = - of_find_compatible_node(NULL, NULL, "fsl,imx8mm-ocotp"); - void __iomem *ocotp_base; - struct clk *clk; - int ret = 0; - u32 offset = of_machine_is_compatible("fsl,imx8mp") ? - IMX8MP_OCOTP_UID_OFFSET : 0; + of_find_compatible_node(NULL, NULL, "fsl,imx8mm-anatop"); + void __iomem *anatop_base; if (!np) return -EINVAL; - ocotp_base = of_iomap(np, 0); - if (!ocotp_base) + anatop_base = of_iomap(np, 0); + if (!anatop_base) return -EINVAL; - clk = of_clk_get_by_name(np, NULL); - if (IS_ERR(clk)) { - ret = PTR_ERR(clk); - goto err_clk; - } - - clk_prepare_enable(clk); - - *socuid = readl_relaxed(ocotp_base + OCOTP_UID_HIGH + offset); - *socuid <<= 32; - *socuid |= readl_relaxed(ocotp_base + OCOTP_UID_LOW + offset); + *socrev = readl_relaxed(anatop_base + ANADIG_DIGPROG_IMX8MM); - clk_disable_unprepare(clk); - clk_put(clk); + iounmap(anatop_base); -err_clk: - iounmap(ocotp_base); - return ret; + return 0; } -static int imx8mm_soc_revision(u32 *socrev, u64 *socuid) +static int imx8m_soc_prepare(struct platform_device *pdev, const char *ocotp_compatible) { struct device_node *np __free(device_node) = - of_find_compatible_node(NULL, NULL, "fsl,imx8mm-anatop"); - void __iomem *anatop_base; + of_find_compatible_node(NULL, NULL, ocotp_compatible); + struct imx8_soc_drvdata *drvdata = platform_get_drvdata(pdev); + int ret = 0; if (!np) return -EINVAL; - anatop_base = of_iomap(np, 0); - if (!anatop_base) + drvdata->ocotp_base = of_iomap(np, 0); + if (!drvdata->ocotp_base) return -EINVAL; - *socrev = readl_relaxed(anatop_base + ANADIG_DIGPROG_IMX8MM); + drvdata->clk = of_clk_get_by_name(np, NULL); + if (IS_ERR(drvdata->clk)) { + ret = PTR_ERR(drvdata->clk); + goto err_clk; + } - iounmap(anatop_base); + return clk_prepare_enable(drvdata->clk); - return imx8mm_soc_uid(socuid); +err_clk: + iounmap(drvdata->ocotp_base); + return ret; +} + +static void imx8m_soc_unprepare(struct platform_device *pdev) +{ + struct imx8_soc_drvdata *drvdata = platform_get_drvdata(pdev); + + clk_disable_unprepare(drvdata->clk); + clk_put(drvdata->clk); + iounmap(drvdata->ocotp_base); } static const struct imx8_soc_data imx8mq_soc_data = { .name = "i.MX8MQ", + .ocotp_compatible = "fsl,imx8mq-ocotp", .soc_revision = imx8mq_soc_revision, + .soc_uid = imx8m_soc_uid, }; static const struct imx8_soc_data imx8mm_soc_data = { .name = "i.MX8MM", + .ocotp_compatible = "fsl,imx8mm-ocotp", .soc_revision = imx8mm_soc_revision, + .soc_uid = imx8m_soc_uid, }; static const struct imx8_soc_data imx8mn_soc_data = { .name = "i.MX8MN", + .ocotp_compatible = "fsl,imx8mm-ocotp", .soc_revision = imx8mm_soc_revision, + .soc_uid = imx8m_soc_uid, }; static const struct imx8_soc_data imx8mp_soc_data = { .name = "i.MX8MP", + .ocotp_compatible = "fsl,imx8mm-ocotp", .soc_revision = imx8mm_soc_revision, + .soc_uid = imx8mp_soc_uid, }; static __maybe_unused const struct of_device_id imx8_soc_match[] = { @@ -192,21 +205,39 @@ static __maybe_unused const struct of_device_id imx8_soc_match[] = { devm_kasprintf((dev), GFP_KERNEL, "%d.%d", ((soc_rev) >> 4) & 0xf, (soc_rev) & 0xf) : \ "unknown" +static void imx8m_unregister_soc(void *data) +{ + soc_device_unregister(data); +} + +static void imx8m_unregister_cpufreq(void *data) +{ + platform_device_unregister(data); +} + static int imx8m_soc_probe(struct platform_device *pdev) { struct soc_device_attribute *soc_dev_attr; + struct platform_device *cpufreq_dev; const struct imx8_soc_data *data; + struct imx8_soc_drvdata *drvdata; struct device *dev = &pdev->dev; const struct of_device_id *id; struct soc_device *soc_dev; u32 soc_rev = 0; - u64 soc_uid = 0; + u64 soc_uid[2] = {0, 0}; int ret; soc_dev_attr = devm_kzalloc(dev, sizeof(*soc_dev_attr), GFP_KERNEL); if (!soc_dev_attr) return -ENOMEM; + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + platform_set_drvdata(pdev, drvdata); + soc_dev_attr->family = "Freescale i.MX"; ret = of_property_read_string(of_root, "model", &soc_dev_attr->machine); @@ -220,18 +251,37 @@ static int imx8m_soc_probe(struct platform_device *pdev) data = id->data; if (data) { soc_dev_attr->soc_id = data->name; + ret = imx8m_soc_prepare(pdev, data->ocotp_compatible); + if (ret) + return ret; + if (data->soc_revision) { - ret = data->soc_revision(&soc_rev, &soc_uid); - if (ret) + ret = data->soc_revision(pdev, &soc_rev); + if (ret) { + imx8m_soc_unprepare(pdev); + return ret; + } + } + if (data->soc_uid) { + ret = data->soc_uid(pdev, soc_uid); + if (ret) { + imx8m_soc_unprepare(pdev); return ret; + } } + imx8m_soc_unprepare(pdev); } soc_dev_attr->revision = imx8_revision(dev, soc_rev); if (!soc_dev_attr->revision) return -ENOMEM; - soc_dev_attr->serial_number = devm_kasprintf(dev, GFP_KERNEL, "%016llX", soc_uid); + if (soc_uid[1]) + soc_dev_attr->serial_number = devm_kasprintf(dev, GFP_KERNEL, "%016llX%016llX", + soc_uid[1], soc_uid[0]); + else + soc_dev_attr->serial_number = devm_kasprintf(dev, GFP_KERNEL, "%016llX", + soc_uid[0]); if (!soc_dev_attr->serial_number) return -ENOMEM; @@ -239,11 +289,22 @@ static int imx8m_soc_probe(struct platform_device *pdev) if (IS_ERR(soc_dev)) return PTR_ERR(soc_dev); + ret = devm_add_action(dev, imx8m_unregister_soc, soc_dev); + if (ret) + return ret; + pr_info("SoC: %s revision %s\n", soc_dev_attr->soc_id, soc_dev_attr->revision); - if (IS_ENABLED(CONFIG_ARM_IMX_CPUFREQ_DT)) - platform_device_register_simple("imx-cpufreq-dt", -1, NULL, 0); + if (IS_ENABLED(CONFIG_ARM_IMX_CPUFREQ_DT)) { + cpufreq_dev = platform_device_register_simple("imx-cpufreq-dt", -1, NULL, 0); + if (IS_ERR(cpufreq_dev)) + return dev_err_probe(dev, PTR_ERR(cpufreq_dev), + "Failed to register imx-cpufreq-dev device\n"); + ret = devm_add_action(dev, imx8m_unregister_cpufreq, cpufreq_dev); + if (ret) + return ret; + } return 0; } diff --git a/drivers/soc/imx/soc-imx9.c b/drivers/soc/imx/soc-imx9.c new file mode 100644 index 000000000000..b46d22cf0212 --- /dev/null +++ b/drivers/soc/imx/soc-imx9.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2024 NXP + */ + +#include <linux/arm-smccc.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/sys_soc.h> + +#define IMX_SIP_GET_SOC_INFO 0xc2000006 +#define SOC_ID(x) (((x) & 0xFFFF) >> 8) +#define SOC_REV_MAJOR(x) ((((x) >> 28) & 0xF) - 0x9) +#define SOC_REV_MINOR(x) (((x) >> 24) & 0xF) + +static int imx9_soc_probe(struct platform_device *pdev) +{ + struct soc_device_attribute *attr; + struct arm_smccc_res res; + struct soc_device *sdev; + u32 soc_id, rev_major, rev_minor; + u64 uid127_64, uid63_0; + int err; + + attr = kzalloc(sizeof(*attr), GFP_KERNEL); + if (!attr) + return -ENOMEM; + + err = of_property_read_string(of_root, "model", &attr->machine); + if (err) { + pr_err("%s: missing model property: %d\n", __func__, err); + goto attr; + } + + attr->family = kasprintf(GFP_KERNEL, "Freescale i.MX"); + + /* + * Retrieve the soc id, rev & uid info: + * res.a1[31:16]: soc revision; + * res.a1[15:0]: soc id; + * res.a2: uid[127:64]; + * res.a3: uid[63:0]; + */ + arm_smccc_smc(IMX_SIP_GET_SOC_INFO, 0, 0, 0, 0, 0, 0, 0, &res); + if (res.a0 != SMCCC_RET_SUCCESS) { + pr_err("%s: SMC failed: 0x%lx\n", __func__, res.a0); + err = -EINVAL; + goto family; + } + + soc_id = SOC_ID(res.a1); + rev_major = SOC_REV_MAJOR(res.a1); + rev_minor = SOC_REV_MINOR(res.a1); + + attr->soc_id = kasprintf(GFP_KERNEL, "i.MX%2x", soc_id); + attr->revision = kasprintf(GFP_KERNEL, "%d.%d", rev_major, rev_minor); + + uid127_64 = res.a2; + uid63_0 = res.a3; + attr->serial_number = kasprintf(GFP_KERNEL, "%016llx%016llx", uid127_64, uid63_0); + + sdev = soc_device_register(attr); + if (IS_ERR(sdev)) { + err = PTR_ERR(sdev); + pr_err("%s failed to register SoC as a device: %d\n", __func__, err); + goto serial_number; + } + + return 0; + +serial_number: + kfree(attr->serial_number); + kfree(attr->revision); + kfree(attr->soc_id); +family: + kfree(attr->family); +attr: + kfree(attr); + return err; +} + +static __maybe_unused const struct of_device_id imx9_soc_match[] = { + { .compatible = "fsl,imx93", }, + { .compatible = "fsl,imx95", }, + { } +}; + +#define IMX_SOC_DRIVER "imx9-soc" + +static struct platform_driver imx9_soc_driver = { + .probe = imx9_soc_probe, + .driver = { + .name = IMX_SOC_DRIVER, + }, +}; + +static int __init imx9_soc_init(void) +{ + int ret; + struct platform_device *pdev; + + /* No match means it is not an i.MX 9 series SoC, do nothing. */ + if (!of_match_node(imx9_soc_match, of_root)) + return 0; + + ret = platform_driver_register(&imx9_soc_driver); + if (ret) { + pr_err("failed to register imx9_soc platform driver: %d\n", ret); + return ret; + } + + pdev = platform_device_register_simple(IMX_SOC_DRIVER, -1, NULL, 0); + if (IS_ERR(pdev)) { + pr_err("failed to register imx9_soc platform device: %ld\n", PTR_ERR(pdev)); + platform_driver_unregister(&imx9_soc_driver); + return PTR_ERR(pdev); + } + + return 0; +} +device_initcall(imx9_soc_init); + +MODULE_AUTHOR("NXP"); +MODULE_DESCRIPTION("NXP i.MX9 SoC"); +MODULE_LICENSE("GPL"); |