| /* |
| * Copyright (C) 2016 Freescale Semiconductor, Inc. |
| * Copyright 2017-2018 NXP |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version 2 |
| * of the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <linux/arm-smccc.h> |
| #include <linux/cpu.h> |
| #include <linux/init.h> |
| #include <linux/io.h> |
| #include <linux/of_address.h> |
| #include <linux/pm_opp.h> |
| #include <linux/slab.h> |
| #include <linux/sys_soc.h> |
| #include <linux/platform_device.h> |
| #include <linux/of.h> |
| |
| #include <soc/imx8/sc/types.h> |
| #include <soc/imx8/sc/sci.h> |
| #include <soc/imx8/soc.h> |
| #include <soc/imx/revision.h> |
| #include <soc/imx/src.h> |
| #include <soc/imx/fsl_sip.h> |
| |
| struct imx8_soc_data { |
| char *name; |
| u32 (*soc_revision)(void); |
| }; |
| |
| static u32 imx8_soc_id; |
| static u32 imx8_soc_rev = IMX_CHIP_REVISION_UNKNOWN; |
| static u64 imx8_soc_uid; |
| |
| static const struct imx8_soc_data *soc_data; |
| |
| static inline void imx8_set_soc_revision(u32 rev) |
| { |
| imx8_soc_rev = rev; |
| } |
| |
| unsigned int imx8_get_soc_revision(void) |
| { |
| return imx8_soc_rev; |
| } |
| |
| static inline void imx8_set_soc_id(u32 id) |
| { |
| imx8_soc_id = id; |
| } |
| |
| inline bool cpu_is_imx8qm(void) |
| { |
| return imx8_soc_id == IMX_SOC_IMX8QM; |
| } |
| |
| inline bool cpu_is_imx8qxp(void) |
| { |
| return imx8_soc_id == IMX_SOC_IMX8QXP; |
| } |
| |
| inline bool cpu_is_imx8mq(void) |
| { |
| return imx8_soc_id == IMX_SOC_IMX8MQ; |
| } |
| |
| static u32 imx_init_revision_from_atf(void) |
| { |
| struct arm_smccc_res res; |
| u32 digprog; |
| u32 id, rev; |
| |
| arm_smccc_smc(FSL_SIP_GET_SOC_INFO, 0, 0, |
| 0, 0, 0, 0, 0, &res); |
| digprog = res.a0; |
| |
| /* |
| * Bit [23:16] is the silicon ID |
| * Bit[7:4] is the base layer revision, |
| * Bit[3:0] is the metal layer revision |
| * e.g. 0x10 stands for Tapeout 1.0 |
| */ |
| |
| rev = digprog & 0xff; |
| id = digprog >> 16 & 0xff; |
| |
| imx8_set_soc_id(id); |
| imx8_set_soc_revision(rev); |
| |
| return rev; |
| } |
| |
| static u32 imx_init_revision_from_scu(void) |
| { |
| uint32_t mu_id; |
| sc_err_t sc_err = SC_ERR_NONE; |
| sc_ipc_t ipc_handle; |
| u32 id, rev; |
| u32 uid_l = 0, uid_h = 0; |
| |
| sc_err = sc_ipc_getMuID(&mu_id); |
| if (sc_err != SC_ERR_NONE) { |
| WARN(1, "%s: Cannot obtain MU ID\n", __func__); |
| |
| return IMX_CHIP_REVISION_UNKNOWN; |
| } |
| |
| sc_err = sc_ipc_open(&ipc_handle, mu_id); |
| if (sc_err != SC_ERR_NONE) { |
| WARN(1, "%s: Cannot open MU channel\n", __func__); |
| |
| return IMX_CHIP_REVISION_UNKNOWN; |
| }; |
| |
| sc_err = sc_misc_get_control(ipc_handle, SC_R_SYSTEM, SC_C_ID, &id); |
| if (sc_err != SC_ERR_NONE) { |
| WARN(1, "%s: Cannot get control\n", __func__); |
| |
| return IMX_CHIP_REVISION_UNKNOWN; |
| }; |
| |
| rev = (id >> 5) & 0xf; |
| rev = (((rev >> 2) + 1) << 4) | (rev & 0x3); |
| id &= 0x1f; |
| |
| imx8_set_soc_id(id); |
| imx8_set_soc_revision(rev); |
| |
| sc_misc_unique_id(ipc_handle, &uid_l, &uid_h); |
| |
| imx8_soc_uid = uid_h; |
| imx8_soc_uid <<= 32; |
| imx8_soc_uid |= uid_l; |
| |
| return rev; |
| } |
| |
| bool TKT340553_SW_WORKAROUND; |
| |
| static u32 imx8qm_soc_revision(void) |
| { |
| u32 rev = imx_init_revision_from_scu(); |
| |
| if (rev == IMX_CHIP_REVISION_1_0 || rev == IMX_CHIP_REVISION_1_1) |
| TKT340553_SW_WORKAROUND = true; |
| |
| return rev; |
| } |
| |
| static u32 imx8qxp_soc_revision(void) |
| { |
| return imx_init_revision_from_scu(); |
| } |
| |
| #define OCOTP_UID_LOW 0x410 |
| #define OCOTP_UID_HIGH 0x420 |
| |
| static u64 imx8mq_soc_get_soc_uid(void) |
| { |
| struct device_node *np; |
| void __iomem *base; |
| |
| u64 val = 0; |
| |
| np = of_find_compatible_node(NULL, NULL, "fsl,imx8mq-ocotp"); |
| if (!np) { |
| pr_warn("failed to find ocotp node\n"); |
| return val; |
| } |
| |
| base = of_iomap(np, 0); |
| if (!base) { |
| pr_warn("failed to map ocotp\n"); |
| goto put_node; |
| } |
| |
| val = readl_relaxed(base + OCOTP_UID_HIGH); |
| val <<= 32; |
| val |= readl_relaxed(base + OCOTP_UID_LOW); |
| |
| iounmap(base); |
| |
| put_node: |
| of_node_put(np); |
| return val; |
| } |
| |
| static u32 imx8mq_soc_revision(void) |
| { |
| imx8_soc_uid = imx8mq_soc_get_soc_uid(); |
| return imx_init_revision_from_atf(); |
| } |
| |
| static u32 imx8mm_soc_revision(void) |
| { |
| imx8_soc_uid = imx8mq_soc_get_soc_uid(); |
| return imx_init_revision_from_atf(); |
| } |
| |
| static struct imx8_soc_data imx8qm_soc_data = { |
| .name = "i.MX8QM", |
| .soc_revision = imx8qm_soc_revision, |
| }; |
| |
| static struct imx8_soc_data imx8qxp_soc_data = { |
| .name = "i.MX8QXP", |
| .soc_revision = imx8qxp_soc_revision, |
| }; |
| |
| static struct imx8_soc_data imx8mq_soc_data = { |
| .name = "i.MX8MQ", |
| .soc_revision = imx8mq_soc_revision, |
| }; |
| |
| static struct imx8_soc_data imx8mm_soc_data = { |
| .name = "i.MX8MM", |
| .soc_revision = imx8mm_soc_revision, |
| }; |
| |
| static const struct of_device_id imx8_soc_match[] = { |
| { .compatible = "fsl,imx8qm", .data = &imx8qm_soc_data, }, |
| { .compatible = "fsl,imx8qxp", .data = &imx8qxp_soc_data, }, |
| { .compatible = "fsl,imx8mq", .data = &imx8mq_soc_data, }, |
| { .compatible = "fsl,imx8mm", .data = &imx8mm_soc_data, }, |
| { } |
| }; |
| |
| static int __init imx8_revision_init(void) |
| { |
| struct device_node *root; |
| const struct of_device_id *id; |
| const char *machine; |
| u32 rev = IMX_CHIP_REVISION_UNKNOWN; |
| int ret; |
| |
| root = of_find_node_by_path("/"); |
| ret = of_property_read_string(root, "model", &machine); |
| if (ret) |
| return -ENODEV; |
| |
| id = of_match_node(imx8_soc_match, root); |
| if (!id) |
| return -ENODEV; |
| |
| of_node_put(root); |
| |
| soc_data = id->data; |
| if (soc_data && soc_data->soc_revision) |
| rev = soc_data->soc_revision(); |
| |
| if (rev == IMX_CHIP_REVISION_UNKNOWN) |
| pr_info("CPU identified as %s, unknown revision\n", |
| soc_data->name); |
| else |
| pr_info("CPU identified as %s, silicon rev %d.%d\n", |
| soc_data->name, (rev >> 4) & 0xf, rev & 0xf); |
| |
| return 0; |
| } |
| early_initcall(imx8_revision_init); |
| |
| static ssize_t imx8_get_soc_uid(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| return sprintf(buf, "%016llX\n", imx8_soc_uid); |
| } |
| |
| static struct device_attribute imx8_uid = |
| __ATTR(soc_uid, 0444, imx8_get_soc_uid, NULL); |
| |
| static void __init imx8mq_noc_init(void) |
| { |
| struct arm_smccc_res res; |
| |
| pr_info("Config NOC for VPU and CPU\n"); |
| |
| arm_smccc_smc(FSL_SIP_NOC, FSL_SIP_NOC_PRIORITY, NOC_CPU_PRIORITY, |
| 0x80000300, 0, 0, 0, 0, &res); |
| if (res.a0) |
| pr_err("Config NOC for CPU fail!\n"); |
| |
| arm_smccc_smc(FSL_SIP_NOC, FSL_SIP_NOC_PRIORITY, NOC_VPU_PRIORITY, |
| 0x80000300, 0, 0, 0, 0, &res); |
| if (res.a0) |
| pr_err("Config NOC for VPU fail!\n"); |
| } |
| |
| static int __init imx8_soc_init(void) |
| { |
| struct soc_device_attribute *soc_dev_attr; |
| struct soc_device *soc_dev; |
| u32 soc_rev; |
| int ret; |
| |
| soc_dev_attr = kzalloc(sizeof(*soc_dev_attr), GFP_KERNEL); |
| if (!soc_dev_attr) |
| return -ENODEV; |
| |
| soc_dev_attr->family = "Freescale i.MX"; |
| |
| if (soc_data) |
| soc_dev_attr->soc_id = soc_data->name; |
| |
| soc_rev = imx8_get_soc_revision(); |
| soc_dev_attr->revision = kasprintf(GFP_KERNEL, "%d.%d", |
| (soc_rev >> 4) & 0xf, |
| soc_rev & 0xf); |
| if (!soc_dev_attr->revision) |
| goto free_soc; |
| |
| of_property_read_string(of_root, "model", &soc_dev_attr->machine); |
| |
| soc_dev = soc_device_register(soc_dev_attr); |
| if (IS_ERR(soc_dev)) |
| goto free_rev; |
| |
| ret = device_create_file(soc_device_to_device(soc_dev), &imx8_uid); |
| if (ret) { |
| pr_err("could not register sysfs entry\n"); |
| return ret; |
| } |
| |
| if (of_machine_is_compatible("fsl,imx8mq")) |
| imx8mq_noc_init(); |
| |
| return 0; |
| |
| free_rev: |
| kfree(soc_dev_attr->revision); |
| free_soc: |
| kfree(soc_dev_attr); |
| return -ENODEV; |
| } |
| device_initcall(imx8_soc_init); |
| |
| #define OCOTP_CFG3 0x440 |
| #define OCOTP_CFG3_SPEED_GRADING_SHIFT 8 |
| #define OCOTP_CFG3_SPEED_GRADING_MASK (0x7 << 8) |
| #define OCOTP_CFG3_SPEED_2GHZ 4 |
| #define OCOTP_CFG3_SPEED_1P8GHZ 3 |
| #define OCOTP_CFG3_SPEED_1P6GHZ 2 |
| #define OCOTP_CFG3_SPEED_1P2GHZ 1 |
| #define OCOTP_CFG3_SPEED_800MHZ 0 |
| #define OCOTP_CFG3_SPEED_1P0GHZ 1 |
| #define OCOTP_CFG3_SPEED_1P3GHZ 2 |
| #define OCOTP_CFG3_SPEED_1P5GHZ 3 |
| #define OCOTP_CFG3_MKT_SEGMENT_SHIFT 6 |
| #define OCOTP_CFG3_MKT_SEGMENT_MASK (0x3 << 6) |
| #define OCOTP_CFG3_CONSUMER 0 |
| #define OCOTP_CFG3_EXT_CONSUMER 1 |
| #define OCOTP_CFG3_INDUSTRIAL 2 |
| #define OCOTP_CFG3_AUTO 3 |
| |
| #if !defined(CONFIG_IMX8MQ_PHANBELL_POWERSAVE) |
| static void __init imx8mq_opp_check_speed_grading(struct device *cpu_dev) |
| { |
| struct device_node *np; |
| void __iomem *base; |
| u32 val, speed_grading; |
| |
| np = of_find_compatible_node(NULL, NULL, "fsl,imx8mq-ocotp"); |
| if (!np) { |
| pr_warn("failed to find ocotp node\n"); |
| return; |
| } |
| |
| base = of_iomap(np, 0); |
| if (!base) { |
| pr_warn("failed to map ocotp\n"); |
| goto put_node; |
| } |
| val = readl_relaxed(base + OCOTP_CFG3); |
| speed_grading = (val >> OCOTP_CFG3_SPEED_GRADING_SHIFT) & 0x3; |
| val >>= OCOTP_CFG3_MKT_SEGMENT_SHIFT; |
| val &= 0x3; |
| |
| switch (val) { |
| case OCOTP_CFG3_CONSUMER: |
| if (dev_pm_opp_disable(cpu_dev, 800000000)) |
| pr_warn("failed to disable 800MHz OPP!\n"); |
| if (dev_pm_opp_disable(cpu_dev, 1300000000)) |
| pr_warn("failed to disable 1.3GHz OPP!\n"); |
| if (speed_grading != OCOTP_CFG3_SPEED_1P5GHZ) { |
| if (dev_pm_opp_disable(cpu_dev, 1500000000)) |
| pr_warn("failed to disable 1.5GHz OPP!\n"); |
| } |
| break; |
| case OCOTP_CFG3_INDUSTRIAL: |
| if (dev_pm_opp_disable(cpu_dev, 1000000000)) |
| pr_warn("failed to disable 1GHz OPP!\n"); |
| if (dev_pm_opp_disable(cpu_dev, 1500000000)) |
| pr_warn("failed to disable 1.5GHz OPP!\n"); |
| if (speed_grading != OCOTP_CFG3_SPEED_1P3GHZ) { |
| if (dev_pm_opp_disable(cpu_dev, 1300000000)) |
| pr_warn("failed to disable 1.3GHz OPP!\n"); |
| } |
| break; |
| default: |
| /* consumer part for default */ |
| if (dev_pm_opp_disable(cpu_dev, 800000000)) |
| pr_warn("failed to disable 800MHz OPP!\n"); |
| if (dev_pm_opp_disable(cpu_dev, 1300000000)) |
| pr_warn("failed to disable 1.3GHz OPP!\n"); |
| if (speed_grading != OCOTP_CFG3_SPEED_1P5GHZ) { |
| if (dev_pm_opp_disable(cpu_dev, 1500000000)) |
| pr_warn("failed to disable 1.5GHz OPP!\n"); |
| } |
| break; |
| } |
| |
| iounmap(base); |
| |
| put_node: |
| of_node_put(np); |
| } |
| |
| static void __init imx8mm_opp_check_speed_grading(struct device *cpu_dev) |
| { |
| struct device_node *np; |
| void __iomem *base; |
| u32 val, market; |
| |
| np = of_find_compatible_node(NULL, NULL, "fsl,imx8mq-ocotp"); |
| if (!np) { |
| pr_warn("failed to find ocotp node\n"); |
| return; |
| } |
| |
| base = of_iomap(np, 0); |
| if (!base) { |
| pr_warn("failed to map ocotp\n"); |
| goto put_node; |
| } |
| val = readl_relaxed(base + OCOTP_CFG3); |
| /* market segment bit[7:6] */ |
| market = (val & OCOTP_CFG3_MKT_SEGMENT_MASK) |
| >> OCOTP_CFG3_MKT_SEGMENT_SHIFT; |
| /* speed grading bit[10:8] */ |
| val = (val & OCOTP_CFG3_SPEED_GRADING_MASK) |
| >> OCOTP_CFG3_SPEED_GRADING_SHIFT; |
| |
| switch (market) { |
| case OCOTP_CFG3_CONSUMER: |
| if (val < OCOTP_CFG3_SPEED_1P8GHZ) |
| if (dev_pm_opp_disable(cpu_dev, 1800000000)) |
| pr_warn("failed to disable 1.8GHz OPP!\n"); |
| if (val < OCOTP_CFG3_SPEED_1P6GHZ) |
| if (dev_pm_opp_disable(cpu_dev, 1600000000)) |
| pr_warn("failed to disable 1.6GHz OPP!\n"); |
| break; |
| case OCOTP_CFG3_INDUSTRIAL: |
| if (dev_pm_opp_disable(cpu_dev, 1800000000)) |
| pr_warn("failed to disable 1.8GHz OPP!\n"); |
| if (val < OCOTP_CFG3_SPEED_1P6GHZ) |
| if (dev_pm_opp_disable(cpu_dev, 1600000000)) |
| pr_warn("failed to disable 1.6GHz OPP!\n"); |
| break; |
| default: |
| break; |
| } |
| |
| iounmap(base); |
| |
| put_node: |
| of_node_put(np); |
| } |
| #endif |
| |
| static void __init imx8mq_opp_init(void) |
| { |
| struct device_node *np; |
| struct device *cpu_dev = get_cpu_device(0); |
| |
| if (!cpu_dev) { |
| pr_warn("failed to get cpu0 device\n"); |
| return; |
| } |
| np = of_node_get(cpu_dev->of_node); |
| if (!np) { |
| pr_warn("failed to find cpu0 node\n"); |
| return; |
| } |
| |
| if (dev_pm_opp_of_add_table(cpu_dev)) { |
| pr_warn("failed to init OPP table\n"); |
| goto put_node; |
| } |
| |
| #if !defined(CONFIG_IMX8MQ_PHANBELL_POWERSAVE) |
| if (of_machine_is_compatible("fsl,imx8mq")) |
| imx8mq_opp_check_speed_grading(cpu_dev); |
| else if (of_machine_is_compatible("fsl,imx8mm")) |
| imx8mm_opp_check_speed_grading(cpu_dev); |
| #endif |
| |
| put_node: |
| of_node_put(np); |
| } |
| |
| static int __init imx8_register_cpufreq(void) |
| { |
| if (of_machine_is_compatible("fsl,imx8mq") || |
| of_machine_is_compatible("fsl,imx8mm")) { |
| imx8mq_opp_init(); |
| platform_device_register_simple("imx8mq-cpufreq", -1, NULL, 0); |
| } else { |
| platform_device_register_simple("imx8-cpufreq", -1, NULL, 0); |
| } |
| |
| return 0; |
| } |
| late_initcall(imx8_register_cpufreq); |
| |
| /* To indicate M4 enabled or not on i.MX8MQ */ |
| static bool m4_is_enabled; |
| bool imx_src_is_m4_enabled(void) |
| { |
| return m4_is_enabled; |
| } |
| |
| int check_m4_enabled(void) |
| { |
| struct arm_smccc_res res; |
| |
| arm_smccc_smc(FSL_SIP_SRC, FSL_SIP_SRC_M4_STARTED, 0, |
| 0, 0, 0, 0, 0, &res); |
| m4_is_enabled = !!res.a0; |
| |
| if (m4_is_enabled) |
| pr_info("M4 is started\n"); |
| |
| return 0; |
| } |
| |
| #define IMX8MQ_FEATURE_BITS 0x450 |
| #define IMX8MQ_FEATURE_HDCP (1<<27) |
| |
| static bool imx8mq_soc_is_hdcp_available(void) |
| { |
| struct device_node *np; |
| void __iomem *base; |
| |
| u32 val = 0xffffffff; /* HDCP disabled */ |
| |
| np = of_find_compatible_node(NULL, NULL, "fsl,imx8mq-ocotp"); |
| if (!np) { |
| pr_warn("failed to find ocotp node\n"); |
| return val; |
| } |
| |
| base = of_iomap(np, 0); |
| if (!base) { |
| pr_warn("failed to map ocotp\n"); |
| goto put_node; |
| } |
| |
| val = readl_relaxed(base + IMX8MQ_FEATURE_BITS); |
| pr_debug("%s(), val 0x%08x hdcp %u\n", __func__, val, |
| (val & IMX8MQ_FEATURE_HDCP)); |
| |
| iounmap(base); |
| |
| put_node: |
| of_node_put(np); |
| |
| if ((val & IMX8MQ_FEATURE_HDCP) == 0) { |
| pr_debug("HDCP is enabled\n"); |
| return true; |
| } |
| pr_debug("HDCP is disabled\n"); |
| return false; |
| } |
| |
| #define IMX8QM_FEATURE_BITS 0x3 |
| #define IMX8QM_FEATURE_HDCP (1<<3) |
| |
| static int imx8qm_soc_is_hdcp_available(void) |
| { |
| sc_ipc_t mu_ipc; |
| sc_ipc_id_t mu_id; |
| uint32_t fuse = 0xffffffff; |
| int ret; |
| |
| ret = sc_ipc_getMuID(&mu_id); |
| if (ret) { |
| pr_warn("sc_ipc_getMuID() can't obtain mu id SCI! %d\n", |
| ret); |
| return false; |
| } |
| |
| ret = sc_ipc_open(&mu_ipc, mu_id); |
| if (ret) { |
| pr_warn("sc_ipc_getMuID() can't open MU channel to SCU! %d\n", |
| ret); |
| return false; |
| } |
| |
| ret = sc_misc_otp_fuse_read(mu_ipc, IMX8QM_FEATURE_BITS, &fuse); |
| sc_ipc_close(mu_ipc); |
| if (ret) { |
| pr_warn("sc_misc_otp_fuse_read fail! %d\n", ret); |
| return false; |
| } |
| |
| pr_debug("mu_id = %d, fuse[3] = 0x%x\n", mu_id, fuse); |
| |
| if ((fuse & IMX8QM_FEATURE_HDCP) == 0) { |
| pr_debug("HDCP is enabled\n"); |
| return true; |
| } |
| pr_debug("HDCP is disabled\n"); |
| return false; |
| } |
| |
| bool check_hdcp_enabled(void) |
| { |
| if (cpu_is_imx8mq()) |
| return imx8mq_soc_is_hdcp_available(); |
| if (cpu_is_imx8qm()) |
| return imx8qm_soc_is_hdcp_available(); |
| return false; |
| } |