// SPDX-License-Identifier: GPL-2.0-only /* * TI CPUFreq/OPP hw-supported driver * * Copyright (C) 2016-2017 Texas Instruments, Inc. * Dave Gerlach */ #include #include #include #include #include #include #include #include #include #include #define REVISION_MASK 0xF #define REVISION_SHIFT 28 #define AM33XX_800M_ARM_MPU_MAX_FREQ 0x1E2F #define AM43XX_600M_ARM_MPU_MAX_FREQ 0xFFA #define DRA7_EFUSE_HAS_OD_MPU_OPP 11 #define DRA7_EFUSE_HAS_HIGH_MPU_OPP 15 #define DRA76_EFUSE_HAS_PLUS_MPU_OPP 18 #define DRA7_EFUSE_HAS_ALL_MPU_OPP 23 #define DRA76_EFUSE_HAS_ALL_MPU_OPP 24 #define DRA7_EFUSE_NOM_MPU_OPP BIT(0) #define DRA7_EFUSE_OD_MPU_OPP BIT(1) #define DRA7_EFUSE_HIGH_MPU_OPP BIT(2) #define DRA76_EFUSE_PLUS_MPU_OPP BIT(3) #define OMAP3_CONTROL_DEVICE_STATUS 0x4800244C #define OMAP3_CONTROL_IDCODE 0x4830A204 #define OMAP34xx_ProdID_SKUID 0x4830A20C #define OMAP3_SYSCON_BASE (0x48000000 + 0x2000 + 0x270) #define VERSION_COUNT 2 struct ti_cpufreq_data; struct ti_cpufreq_soc_data { const char * const *reg_names; unsigned long (*efuse_xlate)(struct ti_cpufreq_data *opp_data, unsigned long efuse); unsigned long efuse_fallback; unsigned long efuse_offset; unsigned long efuse_mask; unsigned long efuse_shift; unsigned long rev_offset; bool multi_regulator; }; struct ti_cpufreq_data { struct device *cpu_dev; struct device_node *opp_node; struct regmap *syscon; const struct ti_cpufreq_soc_data *soc_data; }; static unsigned long amx3_efuse_xlate(struct ti_cpufreq_data *opp_data, unsigned long efuse) { if (!efuse) efuse = opp_data->soc_data->efuse_fallback; /* AM335x and AM437x use "OPP disable" bits, so invert */ return ~efuse; } static unsigned long dra7_efuse_xlate(struct ti_cpufreq_data *opp_data, unsigned long efuse) { unsigned long calculated_efuse = DRA7_EFUSE_NOM_MPU_OPP; /* * The efuse on dra7 and am57 parts contains a specific * value indicating the highest available OPP. */ switch (efuse) { case DRA76_EFUSE_HAS_PLUS_MPU_OPP: case DRA76_EFUSE_HAS_ALL_MPU_OPP: calculated_efuse |= DRA76_EFUSE_PLUS_MPU_OPP; fallthrough; case DRA7_EFUSE_HAS_ALL_MPU_OPP: case DRA7_EFUSE_HAS_HIGH_MPU_OPP: calculated_efuse |= DRA7_EFUSE_HIGH_MPU_OPP; fallthrough; case DRA7_EFUSE_HAS_OD_MPU_OPP: calculated_efuse |= DRA7_EFUSE_OD_MPU_OPP; } return calculated_efuse; } static unsigned long omap3_efuse_xlate(struct ti_cpufreq_data *opp_data, unsigned long efuse) { /* OPP enable bit ("Speed Binned") */ return BIT(efuse); } static struct ti_cpufreq_soc_data am3x_soc_data = { .efuse_xlate = amx3_efuse_xlate, .efuse_fallback = AM33XX_800M_ARM_MPU_MAX_FREQ, .efuse_offset = 0x07fc, .efuse_mask = 0x1fff, .rev_offset = 0x600, .multi_regulator = false, }; static struct ti_cpufreq_soc_data am4x_soc_data = { .efuse_xlate = amx3_efuse_xlate, .efuse_fallback = AM43XX_600M_ARM_MPU_MAX_FREQ, .efuse_offset = 0x0610, .efuse_mask = 0x3f, .rev_offset = 0x600, .multi_regulator = false, }; static struct ti_cpufreq_soc_data dra7_soc_data = { .efuse_xlate = dra7_efuse_xlate, .efuse_offset = 0x020c, .efuse_mask = 0xf80000, .efuse_shift = 19, .rev_offset = 0x204, .multi_regulator = true, }; /* * OMAP35x TRM (SPRUF98K): * CONTROL_IDCODE (0x4830 A204) describes Silicon revisions. * Control OMAP Status Register 15:0 (Address 0x4800 244C) * to separate between omap3503, omap3515, omap3525, omap3530 * and feature presence. * There are encodings for versions limited to 400/266MHz * but we ignore. * Not clear if this also holds for omap34xx. * some eFuse values e.g. CONTROL_FUSE_OPP1_VDD1 * are stored in the SYSCON register range * Register 0x4830A20C [ProdID.SKUID] [0:3] * 0x0 for normal 600/430MHz device. * 0x8 for 720/520MHz device. * Not clear what omap34xx value is. */ static struct ti_cpufreq_soc_data omap34xx_soc_data = { .efuse_xlate = omap3_efuse_xlate, .efuse_offset = OMAP34xx_ProdID_SKUID - OMAP3_SYSCON_BASE, .efuse_shift = 3, .efuse_mask = BIT(3), .rev_offset = OMAP3_CONTROL_IDCODE - OMAP3_SYSCON_BASE, .multi_regulator = false, }; /* * AM/DM37x TRM (SPRUGN4M) * CONTROL_IDCODE (0x4830 A204) describes Silicon revisions. * Control Device Status Register 15:0 (Address 0x4800 244C) * to separate between am3703, am3715, dm3725, dm3730 * and feature presence. * Speed Binned = Bit 9 * 0 800/600 MHz * 1 1000/800 MHz * some eFuse values e.g. CONTROL_FUSE_OPP 1G_VDD1 * are stored in the SYSCON register range. * There is no 0x4830A20C [ProdID.SKUID] register (exists but * seems to always read as 0). */ static const char * const omap3_reg_names[] = {"cpu0", "vbb", NULL}; static struct ti_cpufreq_soc_data omap36xx_soc_data = { .reg_names = omap3_reg_names, .efuse_xlate = omap3_efuse_xlate, .efuse_offset = OMAP3_CONTROL_DEVICE_STATUS - OMAP3_SYSCON_BASE, .efuse_shift = 9, .efuse_mask = BIT(9), .rev_offset = OMAP3_CONTROL_IDCODE - OMAP3_SYSCON_BASE, .multi_regulator = true, }; /* * AM3517 is quite similar to AM/DM37x except that it has no * high speed grade eFuse and no abb ldo */ static struct ti_cpufreq_soc_data am3517_soc_data = { .efuse_xlate = omap3_efuse_xlate, .efuse_offset = OMAP3_CONTROL_DEVICE_STATUS - OMAP3_SYSCON_BASE, .efuse_shift = 0, .efuse_mask = 0, .rev_offset = OMAP3_CONTROL_IDCODE - OMAP3_SYSCON_BASE, .multi_regulator = false, }; /** * ti_cpufreq_get_efuse() - Parse and return efuse value present on SoC * @opp_data: pointer to ti_cpufreq_data context * @efuse_value: Set to the value parsed from efuse * * Returns error code if efuse not read properly. */ static int ti_cpufreq_get_efuse(struct ti_cpufreq_data *opp_data, u32 *efuse_value) { struct device *dev = opp_data->cpu_dev; u32 efuse; int ret; ret = regmap_read(opp_data->syscon, opp_data->soc_data->efuse_offset, &efuse); if (ret == -EIO) { /* not a syscon register! */ void __iomem *regs = ioremap(OMAP3_SYSCON_BASE + opp_data->soc_data->efuse_offset, 4); if (!regs) return -ENOMEM; efuse = readl(regs); iounmap(regs); } else if (ret) { dev_err(dev, "Failed to read the efuse value from syscon: %d\n", ret); return ret; } efuse = (efuse & opp_data->soc_data->efuse_mask); efuse >>= opp_data->soc_data->efuse_shift; *efuse_value = opp_data->soc_data->efuse_xlate(opp_data, efuse); return 0; } /** * ti_cpufreq_get_rev() - Parse and return rev value present on SoC * @opp_data: pointer to ti_cpufreq_data context * @revision_value: Set to the value parsed from revision register * * Returns error code if revision not read properly. */ static int ti_cpufreq_get_rev(struct ti_cpufreq_data *opp_data, u32 *revision_value) { struct device *dev = opp_data->cpu_dev; u32 revision; int ret; ret = regmap_read(opp_data->syscon, opp_data->soc_data->rev_offset, &revision); if (ret == -EIO) { /* not a syscon register! */ void __iomem *regs = ioremap(OMAP3_SYSCON_BASE + opp_data->soc_data->rev_offset, 4); if (!regs) return -ENOMEM; revision = readl(regs); iounmap(regs); } else if (ret) { dev_err(dev, "Failed to read the revision number from syscon: %d\n", ret); return ret; } *revision_value = BIT((revision >> REVISION_SHIFT) & REVISION_MASK); return 0; } static int ti_cpufreq_setup_syscon_register(struct ti_cpufreq_data *opp_data) { struct device *dev = opp_data->cpu_dev; struct device_node *np = opp_data->opp_node; opp_data->syscon = syscon_regmap_lookup_by_phandle(np, "syscon"); if (IS_ERR(opp_data->syscon)) { dev_err(dev, "\"syscon\" is missing, cannot use OPPv2 table.\n"); return PTR_ERR(opp_data->syscon); } return 0; } static const struct of_device_id ti_cpufreq_of_match[] = { { .compatible = "ti,am33xx", .data = &am3x_soc_data, }, { .compatible = "ti,am3517", .data = &am3517_soc_data, }, { .compatible = "ti,am43", .data = &am4x_soc_data, }, { .compatible = "ti,dra7", .data = &dra7_soc_data }, { .compatible = "ti,omap34xx", .data = &omap34xx_soc_data, }, { .compatible = "ti,omap36xx", .data = &omap36xx_soc_data, }, /* legacy */ { .compatible = "ti,omap3430", .data = &omap34xx_soc_data, }, { .compatible = "ti,omap3630", .data = &omap36xx_soc_data, }, {}, }; static const struct of_device_id *ti_cpufreq_match_node(void) { struct device_node *np; const struct of_device_id *match; np = of_find_node_by_path("/"); match = of_match_node(ti_cpufreq_of_match, np); of_node_put(np); return match; } static int ti_cpufreq_probe(struct platform_device *pdev) { u32 version[VERSION_COUNT]; const struct of_device_id *match; struct ti_cpufreq_data *opp_data; const char * const default_reg_names[] = {"vdd", "vbb", NULL}; int ret; struct dev_pm_opp_config config = { .supported_hw = version, .supported_hw_count = ARRAY_SIZE(version), }; match = dev_get_platdata(&pdev->dev); if (!match) return -ENODEV; opp_data = devm_kzalloc(&pdev->dev, sizeof(*opp_data), GFP_KERNEL); if (!opp_data) return -ENOMEM; opp_data->soc_data = match->data; opp_data->cpu_dev = get_cpu_device(0); if (!opp_data->cpu_dev) { pr_err("%s: Failed to get device for CPU0\n", __func__); return -ENODEV; } opp_data->opp_node = dev_pm_opp_of_get_opp_desc_node(opp_data->cpu_dev); if (!opp_data->opp_node) { dev_info(opp_data->cpu_dev, "OPP-v2 not supported, cpufreq-dt will attempt to use legacy tables.\n"); goto register_cpufreq_dt; } ret = ti_cpufreq_setup_syscon_register(opp_data); if (ret) goto fail_put_node; /* * OPPs determine whether or not they are supported based on * two metrics: * 0 - SoC Revision * 1 - eFuse value */ ret = ti_cpufreq_get_rev(opp_data, &version[0]); if (ret) goto fail_put_node; ret = ti_cpufreq_get_efuse(opp_data, &version[1]); if (ret) goto fail_put_node; if (opp_data->soc_data->multi_regulator) { if (opp_data->soc_data->reg_names) config.regulator_names = opp_data->soc_data->reg_names; else config.regulator_names = default_reg_names; } ret = dev_pm_opp_set_config(opp_data->cpu_dev, &config); if (ret < 0) { dev_err(opp_data->cpu_dev, "Failed to set OPP config\n"); goto fail_put_node; } of_node_put(opp_data->opp_node); register_cpufreq_dt: platform_device_register_simple("cpufreq-dt", -1, NULL, 0); return 0; fail_put_node: of_node_put(opp_data->opp_node); return ret; } static int __init ti_cpufreq_init(void) { const struct of_device_id *match; /* Check to ensure we are on a compatible platform */ match = ti_cpufreq_match_node(); if (match) platform_device_register_data(NULL, "ti-cpufreq", -1, match, sizeof(*match)); return 0; } module_init(ti_cpufreq_init); static struct platform_driver ti_cpufreq_driver = { .probe = ti_cpufreq_probe, .driver = { .name = "ti-cpufreq", }, }; builtin_platform_driver(ti_cpufreq_driver); MODULE_DESCRIPTION("TI CPUFreq/OPP hw-supported driver"); MODULE_AUTHOR("Dave Gerlach "); MODULE_LICENSE("GPL v2");