// SPDX-License-Identifier: GPL-2.0+ /* * Texas Instruments' K3 Clas 0 Adaptive Voltage Scaling driver * * Copyright (C) 2019 Texas Instruments Incorporated - https://www.ti.com/ * Tero Kristo * */ #include #include #include #include #include #include #include #include #include #define AM6_VTM_DEVINFO(i) (priv->base + 0x100 + 0x20 * (i)) #define AM6_VTM_OPPVID_VD(i) (priv->base + 0x104 + 0x20 * (i)) #define AM6_VTM_AVS0_SUPPORTED BIT(12) #define AM6_VTM_OPP_SHIFT(opp) (8 * (opp)) #define AM6_VTM_OPP_MASK 0xff #define K3_VTM_DEVINFO_PWR0_OFFSET 0x4 #define K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK 0xf0 #define K3_VTM_TMPSENS0_CTRL_OFFSET 0x300 #define K3_VTM_TMPSENS_STAT_OFFSET 0x8 #define K3_VTM_ANYMAXT_OUTRG_ALERT_EN 0x1 #define K3_VTM_LOW_TEMP_OFFSET 0x10 #define K3_VTM_MISC_CTRL2_OFFSET 0x10 #define K3_VTM_MISC_CTRL1_OFFSET 0xc #define K3_VTM_TMPSENS_CTRL1_SOC BIT(5) #define K3_VTM_TMPSENS_CTRL_CLRZ BIT(6) #define K3_VTM_TMPSENS_CTRL_MAXT_OUTRG_EN BIT(11) #define K3_VTM_ADC_COUNT_FOR_123C 0x2f8 #define K3_VTM_ADC_COUNT_FOR_105C 0x288 #define K3_VTM_ADC_WA_VALUE 0x2c #define K3_VTM_FUSE_MASK 0xc0000000 #define VD_FLAG_INIT_DONE BIT(0) struct k3_avs_privdata { void *base; struct vd_config *vd_config; struct udevice *dev; }; struct opp { u32 freq; u32 volt; }; struct vd_data { int id; u8 opp; u8 flags; int dev_id; int clk_id; struct opp opps[NUM_OPPS]; struct udevice *supply; }; struct vd_config { struct vd_data *vds; u32 (*efuse_xlate)(struct k3_avs_privdata *priv, int idx, int opp); }; static struct k3_avs_privdata *k3_avs_priv; /** * am6_efuse_voltage: read efuse voltage from VTM * @priv: driver private data * @idx: VD to read efuse for * @opp: opp id to read * * Reads efuse value for the specified OPP, and converts the register * value to a voltage. Returns the voltage in uV, or 0 if nominal voltage * should be used. * * Efuse val to volt conversion logic: * * val > 171 volt increments in 20mV steps with base 171 => 1.66V * val between 115 to 11 increments in 10mV steps with base 115 => 1.1V * val between 15 to 115 increments in 5mV steps with base 15 => .6V * val between 1 to 15 increments in 20mv steps with base 0 => .3V * val 0 is invalid */ static u32 am6_efuse_xlate(struct k3_avs_privdata *priv, int idx, int opp) { u32 val = readl(AM6_VTM_OPPVID_VD(idx)); val >>= AM6_VTM_OPP_SHIFT(opp); val &= AM6_VTM_OPP_MASK; if (!val) return 0; if (val > 171) return 1660000 + 20000 * (val - 171); if (val > 115) return 1100000 + 10000 * (val - 115); if (val > 15) return 600000 + 5000 * (val - 15); return 300000 + 20000 * val; } static int k3_avs_program_voltage(struct k3_avs_privdata *priv, struct vd_data *vd, int opp_id) { u32 volt = vd->opps[opp_id].volt; struct vd_data *vd2; if (!vd->supply) return -ENODEV; vd->opp = opp_id; vd->flags |= VD_FLAG_INIT_DONE; /* Take care of ganged rails and pick the Max amongst them*/ for (vd2 = priv->vd_config->vds; vd2->id >= 0; vd2++) { if (vd == vd2) continue; if (vd2->supply != vd->supply) continue; if (vd2->opps[vd2->opp].volt > volt) volt = vd2->opps[vd2->opp].volt; vd2->flags |= VD_FLAG_INIT_DONE; } return regulator_set_value(vd->supply, volt); } static struct vd_data *get_vd(struct k3_avs_privdata *priv, int idx) { struct vd_data *vd; for (vd = priv->vd_config->vds; vd->id >= 0 && vd->id != idx; vd++) ; if (vd->id < 0) return NULL; return vd; } /** * k3_avs_set_opp: Sets the voltage for an arbitrary VD rail * @dev: AVS device * @vdd_id: voltage domain ID * @opp_id: OPP ID * * Programs the desired OPP value for the defined voltage rail. This * should be called from board files if reconfiguration is desired. * Returns 0 on success, negative error value on failure. */ int k3_avs_set_opp(struct udevice *dev, int vdd_id, int opp_id) { struct k3_avs_privdata *priv = dev_get_priv(dev); struct vd_data *vd; vd = get_vd(priv, vdd_id); if (!vd) return -EINVAL; return k3_avs_program_voltage(priv, vd, opp_id); } static int match_opp(struct vd_data *vd, u32 freq) { struct opp *opp; int opp_id; for (opp_id = 0; opp_id < NUM_OPPS; opp_id++) { opp = &vd->opps[opp_id]; if (opp->freq == freq) return opp_id; } printf("No matching OPP found for freq %d.\n", freq); return -EINVAL; } /** * k3_avs_notify_freq: Notify clock rate change towards AVS subsystem * @dev_id: Device ID for the clock to be changed * @clk_id: Clock ID for the clock to be changed * @freq: New frequency for clock * * Checks if the provided clock is the MPU clock or not, if not, return * immediately. If MPU clock is provided, maps the provided MPU frequency * towards an MPU OPP, and programs the voltage to the regulator. Return 0 * on success, negative error value on failure. */ int k3_avs_notify_freq(int dev_id, int clk_id, u32 freq) { int opp_id; struct k3_avs_privdata *priv = k3_avs_priv; struct vd_data *vd; /* Driver may not be probed yet */ if (!priv) return -EINVAL; for (vd = priv->vd_config->vds; vd->id >= 0; vd++) { if (vd->dev_id != dev_id || vd->clk_id != clk_id) continue; opp_id = match_opp(vd, freq); if (opp_id < 0) return opp_id; vd->opp = opp_id; return k3_avs_program_voltage(priv, vd, opp_id); } return -EINVAL; } static int k3_avs_configure(struct udevice *dev, struct k3_avs_privdata *priv) { struct vd_config *conf; int ret; char pname[20]; struct vd_data *vd; conf = (void *)dev_get_driver_data(dev); priv->vd_config = conf; for (vd = conf->vds; vd->id >= 0; vd++) { sprintf(pname, "vdd-supply-%d", vd->id); ret = device_get_supply_regulator(dev, pname, &vd->supply); if (ret) dev_warn(dev, "supply not found for VD%d.\n", vd->id); sprintf(pname, "ti,default-opp-%d", vd->id); ret = dev_read_u32_default(dev, pname, -1); if (ret != -1) vd->opp = ret; } return 0; } /* k3_avs_program_tshut : Program thermal shutdown value for SOC * set the values corresponding to thresholds to ~123C and 105C * This is optional feature, Few times OS driver takes care of * tshut programing. */ static void k3_avs_program_tshut(struct k3_avs_privdata *priv) { int cnt, id, val; int workaround_needed = 0; u32 ctrl_offset; void __iomem *cfg2_base; void __iomem *fuse_base; cfg2_base = (void __iomem *)devfdt_get_addr_index(priv->dev, 1); if (IS_ERR(cfg2_base)) { dev_err(priv->dev, "cfg base is not defined\n"); return; } /* * Some of TI's J721E SoCs require a software trimming procedure * for the temperature monitors to function properly. To determine * if this particular SoC is NOT affected, both bits in the * WKUP_SPARE_FUSE0[31:30] will be set (0xC0000000) indicating * when software trimming should NOT be applied. * * https://www.ti.com/lit/er/sprz455c/sprz455c.pdf * This routine checks if workaround_needed to be applied or not * based upon workaround_needed, adjust fixed value of tshut high and low */ if (device_is_compatible(priv->dev, "ti,j721e-vtm")) { fuse_base = (void __iomem *)devfdt_get_addr_index(priv->dev, 2); if (IS_ERR(fuse_base)) { dev_err(priv->dev, "fuse-base is not defined for J721E Soc\n"); return; } if (!((readl(fuse_base) & K3_VTM_FUSE_MASK) == K3_VTM_FUSE_MASK)) workaround_needed = 1; } dev_dbg(priv->dev, "Work around %sneeded\n", workaround_needed ? "" : "not "); /* Get the sensor count in the VTM */ val = readl(priv->base + K3_VTM_DEVINFO_PWR0_OFFSET); cnt = val & K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK; cnt >>= __ffs(K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK); /* Program the thermal sensors */ for (id = 0; id < cnt; id++) { ctrl_offset = K3_VTM_TMPSENS0_CTRL_OFFSET + id * 0x20; val = readl(cfg2_base + ctrl_offset); val |= (K3_VTM_TMPSENS_CTRL_MAXT_OUTRG_EN | K3_VTM_TMPSENS_CTRL1_SOC | K3_VTM_TMPSENS_CTRL_CLRZ | BIT(4)); writel(val, cfg2_base + ctrl_offset); } /* * Program TSHUT thresholds * Step 1: set the thresholds to ~123C and 105C WKUP_VTM_MISC_CTRL2 * Step 2: WKUP_VTM_TMPSENS_CTRL_j set the MAXT_OUTRG_EN bit * This is already taken care as per of init * Step 3: WKUP_VTM_MISC_CTRL set the ANYMAXT_OUTRG_ALERT_EN bit */ /* Low thresholds for tshut*/ val = (K3_VTM_ADC_COUNT_FOR_105C - workaround_needed * K3_VTM_ADC_WA_VALUE) << K3_VTM_LOW_TEMP_OFFSET; /* high thresholds */ val |= K3_VTM_ADC_COUNT_FOR_123C - workaround_needed * K3_VTM_ADC_WA_VALUE; writel(val, cfg2_base + K3_VTM_MISC_CTRL2_OFFSET); /* ramp-up delay from Linux code */ mdelay(100); val = readl(cfg2_base + K3_VTM_MISC_CTRL1_OFFSET) | K3_VTM_ANYMAXT_OUTRG_ALERT_EN; writel(val, cfg2_base + K3_VTM_MISC_CTRL1_OFFSET); } /** * k3_avs_probe: parses VD info from VTM, and re-configures the OPP data * * Parses all VDs on a device calculating the AVS class-0 voltages for them, * and updates the vd_data based on this. The vd_data itself shall be used * to program the required OPPs later on. Returns 0 on success, negative * error value on failure. */ static int k3_avs_probe(struct udevice *dev) { int opp_id; u32 volt; struct opp *opp; struct k3_avs_privdata *priv; struct vd_data *vd; int ret; ofnode node; struct ofnode_phandle_args phandle_args; int i = 0; priv = dev_get_priv(dev); priv->dev = dev; k3_avs_priv = priv; ret = k3_avs_configure(dev, priv); if (ret) return ret; priv->base = dev_read_addr_ptr(dev); if (!priv->base) return -ENODEV; for (vd = priv->vd_config->vds; vd->id >= 0; vd++) { /* Get the clock and dev id for Jacinto platforms */ if (vd->id == J721E_VDD_MPU) { node = ofnode_by_compatible(ofnode_null(), "ti,am654-rproc"); if (!ofnode_valid(node)) return -ENODEV; i = ofnode_stringlist_search(node, "clock-names", "core"); if (i < 0) return -ENODEV; ret = ofnode_parse_phandle_with_args(node, "clocks", "#clock-cells", 0, i, &phandle_args); if (ret) { printf("Couldn't get the clock node, ret = %d\n", ret); return ret; } vd->dev_id = phandle_args.args[0]; vd->clk_id = phandle_args.args[1]; debug("%s: MPU dev_id: %d, clk_id: %d", __func__, vd->dev_id, vd->clk_id); } if (!(readl(AM6_VTM_DEVINFO(vd->id)) & AM6_VTM_AVS0_SUPPORTED)) { dev_warn(dev, "AVS-class 0 not supported for VD%d\n", vd->id); continue; } for (opp_id = 0; opp_id < NUM_OPPS; opp_id++) { opp = &vd->opps[opp_id]; if (!opp->freq) continue; volt = priv->vd_config->efuse_xlate(priv, vd->id, opp_id); if (volt) opp->volt = volt; } } for (vd = priv->vd_config->vds; vd->id >= 0; vd++) { if (vd->flags & VD_FLAG_INIT_DONE) continue; ret = k3_avs_program_voltage(priv, vd, vd->opp); if (ret) dev_warn(dev, "Could not program AVS voltage for VD%d, vd->opp=%d, ret=%d\n", vd->id, vd->opp, ret); } if (!device_is_compatible(priv->dev, "ti,am654-avs")) k3_avs_program_tshut(priv); return 0; } static struct vd_data am654_vd_data[] = { { .id = AM6_VDD_CORE, .dev_id = 82, /* AM6_DEV_CBASS0 */ .clk_id = 0, /* main sysclk0 */ .opp = AM6_OPP_NOM, .opps = { [AM6_OPP_NOM] = { .volt = 1000000, .freq = 250000000, /* CBASS0 */ }, }, }, { .id = AM6_VDD_MPU0, .dev_id = 202, /* AM6_DEV_COMPUTE_CLUSTER_A53_0 */ .clk_id = 0, /* ARM clock */ .opp = AM6_OPP_NOM, .opps = { [AM6_OPP_NOM] = { .volt = 1100000, .freq = 800000000, }, [AM6_OPP_OD] = { .volt = 1200000, .freq = 1000000000, }, [AM6_OPP_TURBO] = { .volt = 1240000, .freq = 1100000000, }, }, }, { .id = AM6_VDD_MPU1, .opp = AM6_OPP_NOM, .dev_id = 204, /* AM6_DEV_COMPUTE_CLUSTER_A53_2 */ .clk_id = 0, /* ARM clock */ .opps = { [AM6_OPP_NOM] = { .volt = 1100000, .freq = 800000000, }, [AM6_OPP_OD] = { .volt = 1200000, .freq = 1000000000, }, [AM6_OPP_TURBO] = { .volt = 1240000, .freq = 1100000000, }, }, }, { .id = -1 }, }; static struct vd_data j721e_vd_data[] = { { .id = J721E_VDD_MPU, .opp = AM6_OPP_NOM, /* * XXX: DEPRECATION WARNING: Around 2 u-boot versions * * These values will be picked up from DT, kept for backward * compatibility */ .dev_id = 202, /* J721E_DEV_A72SS0_CORE0 */ .clk_id = 2, /* ARM clock */ .opps = { [AM6_OPP_NOM] = { .volt = 880000, /* TBD in DM */ .freq = 2000000000, }, }, }, { .id = -1 }, }; static struct vd_config j721e_vd_config = { .efuse_xlate = am6_efuse_xlate, .vds = j721e_vd_data, }; static struct vd_config am654_vd_config = { .efuse_xlate = am6_efuse_xlate, .vds = am654_vd_data, }; static const struct udevice_id k3_avs_ids[] = { { .compatible = "ti,am654-avs", .data = (ulong)&am654_vd_config }, { .compatible = "ti,j721e-avs", .data = (ulong)&j721e_vd_config }, { .compatible = "ti,j721e-vtm", .data = (ulong)&j721e_vd_config }, { .compatible = "ti,j7200-vtm", .data = (ulong)&j721e_vd_config }, {} }; U_BOOT_DRIVER(k3_avs) = { .name = "k3_avs", .of_match = k3_avs_ids, .id = UCLASS_MISC, .probe = k3_avs_probe, .priv_auto = sizeof(struct k3_avs_privdata), };