/* * Copyright (c) 2011-2013, NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include "tegra_soctherm.h" static const unsigned long t124_default_soctherm_clk_rate = 51000000; static const unsigned long t124_default_tsensor_clk_rate = 400000; static const s32 t124_nominal_calib_cp = 25; static const s32 t124_nominal_calib_ft = 105; static const struct soctherm_sensor t124_sensor_defaults = { .tall = 16300, .tiddq = 1, .ten_count = 1, .tsample = 120, .tsamp_ate = 481, .pdiv = 8, .pdiv_ate = 8, }; static struct soctherm_tsensor_pmu_data t124_pmu_data; static struct thermal_zone_params t124_soctherm_therm_cpu_tzp = { .governor_name = "pid_thermal_gov", }; static bool t124_cpu_edp_trips_done; static struct soctherm_platform_data t124_soctherm_data = { .therm = { [THERM_CPU] = { .zone_enable = true, .passive_delay = 1000, .hotspot_offset = 6000, .num_trips = 3, .trips = { { .cdev_type = "cpu-balanced", .trip_temp = 90000, .trip_type = THERMAL_TRIP_PASSIVE, .upper = THERMAL_NO_LIMIT, .lower = THERMAL_NO_LIMIT, }, { .cdev_type = "tegra-heavy", .trip_temp = 100000, .trip_type = THERMAL_TRIP_HOT, .upper = THERMAL_NO_LIMIT, .lower = THERMAL_NO_LIMIT, }, { .cdev_type = "tegra-shutdown", .trip_temp = 103000, .trip_type = THERMAL_TRIP_CRITICAL, .upper = THERMAL_NO_LIMIT, .lower = THERMAL_NO_LIMIT, }, }, .tzp = &t124_soctherm_therm_cpu_tzp, }, [THERM_GPU] = { .zone_enable = true, .passive_delay = 1000, .hotspot_offset = 6000, .num_trips = 3, .trips = { { .cdev_type = "gpu-balanced", .trip_temp = 89000, .trip_type = THERMAL_TRIP_PASSIVE, .upper = THERMAL_NO_LIMIT, .lower = THERMAL_NO_LIMIT, }, { .cdev_type = "tegra-heavy", .trip_temp = 99000, .trip_type = THERMAL_TRIP_HOT, .upper = THERMAL_NO_LIMIT, .lower = THERMAL_NO_LIMIT, }, { .cdev_type = "tegra-shutdown", .trip_temp = 101000, .trip_type = THERMAL_TRIP_CRITICAL, .upper = THERMAL_NO_LIMIT, .lower = THERMAL_NO_LIMIT, }, }, .tzp = &t124_soctherm_therm_cpu_tzp, }, [THERM_PLL] = { .zone_enable = true, }, }, .throttle = { [THROTTLE_HEAVY] = { .devs = { [THROTTLE_DEV_CPU] = { .enable = 1, }, }, }, }, .tshut_pmu_trip_data = &t124_pmu_data, }; static struct soctherm_sensor2tsensorcalib t124_tsensor_calib[] = { [TSENSE_CPU0] = { .tsensor = 0, }, [TSENSE_CPU1] = { .tsensor = 1, }, [TSENSE_CPU2] = { .tsensor = 2, }, [TSENSE_CPU3] = { .tsensor = 3, }, [TSENSE_MEM0] = { .tsensor = 5, }, [TSENSE_MEM1] = { .tsensor = 6, }, [TSENSE_GPU] = { .tsensor = 4, }, [TSENSE_PLLX] = { .tsensor = 7, }, }; static int t124_fuse_corr_alpha[] = { /* scaled *1000000 */ [TSENSE_CPU0] = 1148300, [TSENSE_CPU1] = 1126100, [TSENSE_CPU2] = 1155800, [TSENSE_CPU3] = 1134900, [TSENSE_MEM0] = 1062700, [TSENSE_MEM1] = 1084700, [TSENSE_GPU] = 1084300, [TSENSE_PLLX] = 1134500, }; static int t124_fuse_corr_beta[] = { /* scaled *1000000 */ [TSENSE_CPU0] = -6572300, [TSENSE_CPU1] = -5794600, [TSENSE_CPU2] = -7462800, [TSENSE_CPU3] = -6810800, [TSENSE_MEM0] = -4463200, [TSENSE_MEM1] = -5603400, [TSENSE_GPU] = -5111900, [TSENSE_PLLX] = -7410700, }; static struct soctherm_fuse_calib_data t124_calib_data = { .tsensor_calib = t124_tsensor_calib, .fuse_corr_alpha = t124_fuse_corr_alpha, .fuse_corr_beta = t124_fuse_corr_beta, }; /* fuse registers used in public fuse data read API */ #define T124_FUSE_FT_REV 0x28 #define T124_FUSE_CP_REV 0x90 /* sparse realignment register */ #define T124_FUSE_SPARE_REALIGNMENT_REG_0 0x1fc /* tsensor8_calib */ #define T124_FUSE_TSENSOR_CALIB_8 0x180 #define T124_FUSE_BASE_CP_SHIFT 0 #define T124_FUSE_BASE_CP_MASK 0x3ff #define T124_FUSE_BASE_FT_SHIFT 10 #define T124_FUSE_BASE_FT_MASK 0x7ff #define T124_FUSE_SHIFT_CP_SHIFT 0 #define T124_FUSE_SHIFT_CP_MASK 0x3f #define T124_FUSE_SHIFT_CP_BITS 6 #define T124_FUSE_SHIFT_FT_SHIFT 21 #define T124_FUSE_SHIFT_FT_MASK 0x1f #define T124_FUSE_SHIFT_FT_BITS 5 static int t124_tsensor_calib_offset[] = { [0] = 0x098, [1] = 0x084, [2] = 0x088, [3] = 0x12c, [4] = 0x154, [5] = 0x158, [6] = 0x15c, [7] = 0x160, }; static int tegra124_fuse_get_tsensor_calib(int index, u32 *calib) { if (index < 0 || index >= ARRAY_SIZE(t124_tsensor_calib_offset)) return -EINVAL; *calib = tegra30_fuse_readl(t124_tsensor_calib_offset[index]); return 0; } static int fuse_cp_rev_check(void) { u32 rev, rev_major, rev_minor; rev = tegra30_fuse_readl(T124_FUSE_CP_REV); rev_minor = rev & 0x1f; rev_major = (rev >> 5) & 0x3f; /* CP rev < 00.4 is unsupported */ if ((rev_major == 0) && (rev_minor < 4)) return -EINVAL; return 0; } static inline int fuse_ft_rev_check(void) { u32 rev, rev_major, rev_minor; rev = tegra30_fuse_readl(T124_FUSE_FT_REV); rev_minor = rev & 0x1f; rev_major = (rev >> 5) & 0x3f; /* FT rev < 00.5 is unsupported */ if ((rev_major == 0) && (rev_minor < 5)) return -EINVAL; return 0; } static int tegra124_fuse_calib_base_get_cp(u32 *base_cp, s32 *shifted_cp) { s32 cp; u32 val = tegra30_fuse_readl(T124_FUSE_TSENSOR_CALIB_8); if (fuse_cp_rev_check() || !val) return -EINVAL; if (base_cp && shifted_cp) { *base_cp = (((val) & (T124_FUSE_BASE_CP_MASK << T124_FUSE_BASE_CP_SHIFT)) >> T124_FUSE_BASE_CP_SHIFT); val = tegra30_fuse_readl(T124_FUSE_SPARE_REALIGNMENT_REG_0); cp = (((val) & (T124_FUSE_SHIFT_CP_MASK << T124_FUSE_SHIFT_CP_SHIFT)) >> T124_FUSE_SHIFT_CP_SHIFT); *shifted_cp = ((s32)(cp) << (32 - T124_FUSE_SHIFT_CP_BITS) >> (32 - T124_FUSE_SHIFT_CP_BITS)); } return 0; } static int tegra124_fuse_calib_base_get_ft(u32 *base_ft, s32 *shifted_ft) { s32 ft; u32 val = tegra30_fuse_readl(T124_FUSE_TSENSOR_CALIB_8); if (fuse_ft_rev_check() || !val) return -EINVAL; if (base_ft && shifted_ft) { *base_ft = (((val) & (T124_FUSE_BASE_FT_MASK << T124_FUSE_BASE_FT_SHIFT)) >> T124_FUSE_BASE_FT_SHIFT); ft = (((val) & (T124_FUSE_SHIFT_FT_MASK << T124_FUSE_SHIFT_FT_SHIFT)) >> T124_FUSE_SHIFT_FT_SHIFT); *shifted_ft = ((s32)(ft) << (32 - T124_FUSE_SHIFT_FT_BITS) >> (32 - T124_FUSE_SHIFT_FT_BITS)); } return 0; } static int tegra124_soctherm_fuse_read_calib_base(void) { s32 calib_cp, calib_ft; u32 fuse_calib_base_cp, fuse_calib_base_ft; if (tegra124_fuse_calib_base_get_cp(&fuse_calib_base_cp, &calib_cp) || tegra124_fuse_calib_base_get_ft(&fuse_calib_base_ft, &calib_ft)) { return -EINVAL; } t124_calib_data.fuse_calib_base_cp = fuse_calib_base_cp; t124_calib_data.fuse_calib_base_ft = fuse_calib_base_ft; /* use HI precision to calculate: use fuse_temp in 0.5C */ t124_calib_data.actual_temp_cp = 2 * t124_nominal_calib_cp + calib_cp; t124_calib_data.actual_temp_ft = 2 * t124_nominal_calib_ft + calib_ft; return 0; } static int tegra124_soctherm_fuse_get_tsensor_calib(void) { u32 value; int i, sensor, ret; for (i = 0; i < TSENSE_SIZE; i++) { sensor = t124_calib_data.tsensor_calib[i].tsensor; ret = tegra124_fuse_get_tsensor_calib(sensor, &value); if (ret < 0) return ret; t124_calib_data.tsensor_calib[i].calib_val = value; } return 0; } static int tegra124_soctherm_parse_cdev(struct device_node *soctherm_dn, char *property_name, const char **cdev_cap_type, const char **cdev_floor_type, struct platform_device **pdev) { struct device_node *dn_cdev, *dn; const __be32 *prop; int ret = 0; dn_cdev = NULL; dn = NULL; prop = of_get_property(soctherm_dn, property_name, NULL); if (!prop) { ret = -EINVAL; goto error; } dn_cdev = of_find_node_by_phandle(be32_to_cpup(prop)); if (!dn_cdev) { ret = -ENOENT; goto error; } *cdev_cap_type = of_get_property(dn_cdev, "cdev-cap-type", NULL); *cdev_floor_type = of_get_property(dn_cdev, "cdev-floor-type", NULL); if (IS_ERR_OR_NULL(*cdev_floor_type) && IS_ERR_OR_NULL(*cdev_cap_type)) { ret = -EINVAL; goto error; } prop = of_get_property(dn_cdev, "act-dev", NULL); if (!prop) { ret = -EINVAL; goto error; } dn = of_find_node_by_phandle(be32_to_cpup(prop)); if (!dn) { ret = -ENOENT; goto error; } *pdev = of_find_device_by_node(dn); if (!*pdev) { ret = -ENOENT; goto error; } error: if (dn) of_node_put(dn); if (dn_cdev) of_node_put(dn_cdev); return ret; } static int tegra124_soctherm_parse_dfll_cdev(struct device_node *soctherm_dn, struct platform_device *pdev, struct thermal_trip_info *trips, int *num_trips) { struct platform_device *pdev_cdev; struct tegra_cooling_device dfll_cdev; int err, i, num, *trip_temp; struct clk *cpu_dfll_clk; const char *cdev_cap_type, *cdev_floor_type; err = tegra124_soctherm_parse_cdev(soctherm_dn, "dfll-cdev", &cdev_cap_type, &cdev_floor_type, &pdev_cdev); if (err) { dev_warn(&pdev->dev, "Can't find dfll cdev node\n"); return 0; } cpu_dfll_clk = devm_clk_get(&pdev->dev, "dfllCPU_out"); if (IS_ERR(cpu_dfll_clk)) { dev_info(&pdev->dev, "DFLL not ready\n"); return -EPROBE_DEFER; } /* add floor trips of dfll cdev */ if (IS_ERR_OR_NULL(cdev_floor_type)) goto ignore_dfll_floor_cdev; dfll_cdev.cdev_type = (char *)cdev_floor_type; num = tegra124_dfll_count_therm_states(pdev_cdev, TEGRA_DFLL_THERM_FLOOR); if (num == 0) goto ignore_dfll_floor_cdev; dfll_cdev.trip_temperatures_num = num; trip_temp = devm_kzalloc(&pdev->dev, sizeof(int) * num, GFP_KERNEL); if (!trip_temp) return -ENOMEM; dfll_cdev.trip_temperatures = trip_temp; for (i = 0; i < num; i++) { int temp; temp = tegra124_dfll_get_therm_state_temp( pdev_cdev, TEGRA_DFLL_THERM_FLOOR, i); if (IS_ERR_VALUE(temp)) { devm_kfree(&pdev->dev, trip_temp); goto ignore_dfll_floor_cdev; } dfll_cdev.trip_temperatures[i] = temp; } soctherm_add_trip_points(pdev, trips, num_trips, &dfll_cdev); ignore_dfll_floor_cdev: /* add cap trips of dfll cdev */ if (IS_ERR_OR_NULL(cdev_cap_type)) return 0; dfll_cdev.cdev_type = (char *)cdev_cap_type; num = tegra124_dfll_count_therm_states(pdev_cdev, TEGRA_DFLL_THERM_CAP); if (num == 0) return 0; dfll_cdev.trip_temperatures_num = num; trip_temp = devm_kzalloc(&pdev->dev, sizeof(int) * num, GFP_KERNEL); if (!trip_temp) return -ENOMEM; dfll_cdev.trip_temperatures = trip_temp; for (i = 0; i < num; i++) { int temp; temp = tegra124_dfll_get_therm_state_temp( pdev_cdev, TEGRA_DFLL_THERM_CAP, i); if (IS_ERR_VALUE(temp)) { devm_kfree(&pdev->dev, trip_temp); return 0; } dfll_cdev.trip_temperatures[i] = temp; } soctherm_add_trip_points(pdev, trips, num_trips, &dfll_cdev); return 0; } int tegra124_soctherm_init(struct device_node *soctherm_dn) { struct platform_device *pdev, *pdev_cdev; struct soctherm_platform_data *pdata; struct device_node *dn; void __iomem *base; struct thermal_trip_info *trips; struct tegra_cooling_device *tegra_cdev; int *num_trips; const char *cdev_cap_type, *cdev_floor_type; int err; pdev = of_find_device_by_node(soctherm_dn); if (!pdev) { dev_err(&pdev->dev, "Failed to get soctherm device\n"); return -EINVAL; } pdata = &t124_soctherm_data; platform_set_drvdata(pdev, pdata); if (!t124_calib_data.fuse_calib_base_cp) { if (tegra124_soctherm_fuse_read_calib_base() < 0) { dev_err(&pdev->dev, "ERROR: Improper CP or FT calib fuse.\n"); return -EINVAL; } if (tegra124_soctherm_fuse_get_tsensor_calib() < 0) { dev_err(&pdev->dev, "ERROR: Improper tsensor calib fuse.\n"); return -EINVAL; } pdata->fuse_calib_data = &t124_calib_data; } trips = pdata->therm[THERM_GPU].trips; num_trips = &pdata->therm[THERM_GPU].num_trips; /* add trips of gpu_cold */ tegra_cdev = tegra_get_gpu_vmin_cdev(); if (PTR_ERR(tegra_cdev) == -EPROBE_DEFER) { dev_warn(&pdev->dev, "The gpu cdev is not ready.\n"); return PTR_ERR(tegra_cdev); } else if (IS_ERR(tegra_cdev)) { dev_warn(&pdev->dev, "Can't get the gpu cdev, ignore it.\n"); } else { soctherm_add_trip_points(pdev, trips, num_trips, tegra_cdev); } /* add trips of gpu_scaling */ tegra_cdev = tegra_get_gpu_vts_cdev(); if (PTR_ERR(tegra_cdev) == -EPROBE_DEFER) { dev_warn(&pdev->dev, "The gpu cdev is not ready.\n"); return PTR_ERR(tegra_cdev); } else if (IS_ERR(tegra_cdev)) { dev_warn(&pdev->dev, "Can't get the gpu cdev, ignore it.\n"); } else { soctherm_add_trip_points(pdev, trips, num_trips, tegra_cdev); } trips = pdata->therm[THERM_CPU].trips; num_trips = &pdata->therm[THERM_CPU].num_trips; /* add trips of core_cold */ tegra_cdev = tegra_dvfs_get_core_vmin_cdev(); if (PTR_ERR(tegra_cdev) == -EPROBE_DEFER) { dev_warn(&pdev->dev, "The core cdev is not ready.\n"); return PTR_ERR(tegra_cdev); } else if (IS_ERR(tegra_cdev)) { dev_warn(&pdev->dev, "Can't get the core cdev, ignore it.\n"); } else { soctherm_add_trip_points(pdev, trips, num_trips, tegra_cdev); } /* add trips of dfll cdev */ err = tegra124_soctherm_parse_dfll_cdev(soctherm_dn, pdev, trips, num_trips); if (err == -EPROBE_DEFER) return err; /* add trips of cpu edp */ if (!t124_cpu_edp_trips_done) { err = tegra124_soctherm_parse_cdev(soctherm_dn, "cpu-edp-cdev", &cdev_cap_type, &cdev_floor_type, &pdev_cdev); if (err || IS_ERR_OR_NULL(cdev_cap_type)) { dev_warn(&pdev->dev, "Can't find cpu-edp node\n"); goto ignore_cpu_edp; } /* edp temperature margin is 7000 */ err = tegra_cpu_edp_init_trips(pdev_cdev, trips, num_trips, (char *)cdev_cap_type, 7000); if (err == -EPROBE_DEFER) return err; else if (err) goto ignore_cpu_edp; else t124_cpu_edp_trips_done = true; } ignore_cpu_edp: /* get soctherm base address */ base = of_iomap(soctherm_dn, 0); if (!base) { dev_err(&pdev->dev, "Can't map soctherm registers\n"); WARN_ON(1); return -EINVAL; } pdata->soctherm_base = base; /* get pmc base address */ dn = of_find_compatible_node(NULL, NULL, "nvidia,tegra124-pmc"); if (!dn) { dev_err(&pdev->dev, "Failed to find pmc node\n"); WARN_ON(1); return -EINVAL; } base = of_iomap(dn, 0); if (!base) { dev_err(&pdev->dev, "Can't map pmc registers\n"); WARN_ON(1); return -EINVAL; } pdata->pmc_base = base; /* get clk base address */ dn = of_find_compatible_node(NULL, NULL, "nvidia,tegra124-car"); if (!dn) { dev_err(&pdev->dev, "Failed to find clk node\n"); WARN_ON(1); return -EINVAL; } base = of_iomap(dn, 0); if (!base) { dev_err(&pdev->dev, "Can't map clk registers\n"); WARN_ON(1); return -EINVAL; } pdata->clk_reset_base = base; soctherm_parse_pmu_dt(pdev); /* initialize default sensor value */ soctherm_init_sensor(pdev, &t124_sensor_defaults); /* initialize default clock rates */ soctherm_init_clk_rate(pdev, t124_default_soctherm_clk_rate, t124_default_tsensor_clk_rate); return 0; }