Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Dmitry Osipenko | 2851 | 98.62% | 1 | 14.29% |
Daniel Lezcano | 39 | 1.35% | 5 | 71.43% |
Rob Herring | 1 | 0.03% | 1 | 14.29% |
Total | 2891 | 7 |
// SPDX-License-Identifier: GPL-2.0 /* * Tegra30 SoC Thermal Sensor driver * * Based on downstream HWMON driver from NVIDIA. * Copyright (C) 2011 NVIDIA Corporation * * Author: Dmitry Osipenko <digetx@gmail.com> * Copyright (C) 2021 GRATE-DRIVER project */ #include <linux/bitfield.h> #include <linux/clk.h> #include <linux/delay.h> #include <linux/errno.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/iopoll.h> #include <linux/math.h> #include <linux/module.h> #include <linux/of.h> #include <linux/platform_device.h> #include <linux/pm.h> #include <linux/reset.h> #include <linux/slab.h> #include <linux/thermal.h> #include <linux/types.h> #include <soc/tegra/fuse.h> #include "../thermal_hwmon.h" #define TSENSOR_SENSOR0_CONFIG0 0x0 #define TSENSOR_SENSOR0_CONFIG0_SENSOR_STOP BIT(0) #define TSENSOR_SENSOR0_CONFIG0_HW_FREQ_DIV_EN BIT(1) #define TSENSOR_SENSOR0_CONFIG0_THERMAL_RST_EN BIT(2) #define TSENSOR_SENSOR0_CONFIG0_DVFS_EN BIT(3) #define TSENSOR_SENSOR0_CONFIG0_INTR_OVERFLOW_EN BIT(4) #define TSENSOR_SENSOR0_CONFIG0_INTR_HW_FREQ_DIV_EN BIT(5) #define TSENSOR_SENSOR0_CONFIG0_INTR_THERMAL_RST_EN BIT(6) #define TSENSOR_SENSOR0_CONFIG0_M GENMASK(23, 8) #define TSENSOR_SENSOR0_CONFIG0_N GENMASK(31, 24) #define TSENSOR_SENSOR0_CONFIG1 0x8 #define TSENSOR_SENSOR0_CONFIG1_TH1 GENMASK(15, 0) #define TSENSOR_SENSOR0_CONFIG1_TH2 GENMASK(31, 16) #define TSENSOR_SENSOR0_CONFIG2 0xc #define TSENSOR_SENSOR0_CONFIG2_TH3 GENMASK(15, 0) #define TSENSOR_SENSOR0_STATUS0 0x18 #define TSENSOR_SENSOR0_STATUS0_STATE GENMASK(2, 0) #define TSENSOR_SENSOR0_STATUS0_INTR BIT(8) #define TSENSOR_SENSOR0_STATUS0_CURRENT_VALID BIT(9) #define TSENSOR_SENSOR0_TS_STATUS1 0x1c #define TSENSOR_SENSOR0_TS_STATUS1_CURRENT_COUNT GENMASK(31, 16) #define TEGRA30_FUSE_TEST_PROG_VER 0x28 #define TEGRA30_FUSE_TSENSOR_CALIB 0x98 #define TEGRA30_FUSE_TSENSOR_CALIB_LOW GENMASK(15, 0) #define TEGRA30_FUSE_TSENSOR_CALIB_HIGH GENMASK(31, 16) #define TEGRA30_FUSE_SPARE_BIT 0x144 struct tegra_tsensor; struct tegra_tsensor_calibration_data { int a, b, m, n, p, r; }; struct tegra_tsensor_channel { void __iomem *regs; unsigned int id; struct tegra_tsensor *ts; struct thermal_zone_device *tzd; }; struct tegra_tsensor { void __iomem *regs; bool swap_channels; struct clk *clk; struct device *dev; struct reset_control *rst; struct tegra_tsensor_channel ch[2]; struct tegra_tsensor_calibration_data calib; }; static int tegra_tsensor_hw_enable(const struct tegra_tsensor *ts) { u32 val; int err; err = reset_control_assert(ts->rst); if (err) { dev_err(ts->dev, "failed to assert hardware reset: %d\n", err); return err; } err = clk_prepare_enable(ts->clk); if (err) { dev_err(ts->dev, "failed to enable clock: %d\n", err); return err; } fsleep(1000); err = reset_control_deassert(ts->rst); if (err) { dev_err(ts->dev, "failed to deassert hardware reset: %d\n", err); goto disable_clk; } /* * Sensors are enabled after reset by default, but not gauging * until clock counter is programmed. * * M: number of reference clock pulses after which every * temperature / voltage measurement is made * * N: number of reference clock counts for which the counter runs */ val = FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_M, 12500); val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_N, 255); /* apply the same configuration to both channels */ writel_relaxed(val, ts->regs + 0x40 + TSENSOR_SENSOR0_CONFIG0); writel_relaxed(val, ts->regs + 0x80 + TSENSOR_SENSOR0_CONFIG0); return 0; disable_clk: clk_disable_unprepare(ts->clk); return err; } static int tegra_tsensor_hw_disable(const struct tegra_tsensor *ts) { int err; err = reset_control_assert(ts->rst); if (err) { dev_err(ts->dev, "failed to assert hardware reset: %d\n", err); return err; } clk_disable_unprepare(ts->clk); return 0; } static void devm_tegra_tsensor_hw_disable(void *data) { const struct tegra_tsensor *ts = data; tegra_tsensor_hw_disable(ts); } static int tegra_tsensor_get_temp(struct thermal_zone_device *tz, int *temp) { const struct tegra_tsensor_channel *tsc = thermal_zone_device_priv(tz); const struct tegra_tsensor *ts = tsc->ts; int err, c1, c2, c3, c4, counter; u32 val; /* * Counter will be invalid if hardware is misprogrammed or not enough * time passed since the time when sensor was enabled. */ err = readl_relaxed_poll_timeout(tsc->regs + TSENSOR_SENSOR0_STATUS0, val, val & TSENSOR_SENSOR0_STATUS0_CURRENT_VALID, 21 * USEC_PER_MSEC, 21 * USEC_PER_MSEC * 50); if (err) { dev_err_once(ts->dev, "ch%u: counter invalid\n", tsc->id); return err; } val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_TS_STATUS1); counter = FIELD_GET(TSENSOR_SENSOR0_TS_STATUS1_CURRENT_COUNT, val); /* * This shouldn't happen with a valid counter status, nevertheless * lets verify the value since it's in a separate (from status) * register. */ if (counter == 0xffff) { dev_err_once(ts->dev, "ch%u: counter overflow\n", tsc->id); return -EINVAL; } /* * temperature = a * counter + b * temperature = m * (temperature ^ 2) + n * temperature + p */ c1 = DIV_ROUND_CLOSEST(ts->calib.a * counter + ts->calib.b, 1000000); c1 = c1 ?: 1; c2 = DIV_ROUND_CLOSEST(ts->calib.p, c1); c3 = c1 * ts->calib.m; c4 = ts->calib.n; *temp = DIV_ROUND_CLOSEST(c1 * (c2 + c3 + c4), 1000); return 0; } static int tegra_tsensor_temp_to_counter(const struct tegra_tsensor *ts, int temp) { int c1, c2; c1 = DIV_ROUND_CLOSEST(ts->calib.p - temp * 1000, ts->calib.m); c2 = -ts->calib.r - int_sqrt(ts->calib.r * ts->calib.r - c1); return DIV_ROUND_CLOSEST(c2 * 1000000 - ts->calib.b, ts->calib.a); } static int tegra_tsensor_set_trips(struct thermal_zone_device *tz, int low, int high) { const struct tegra_tsensor_channel *tsc = thermal_zone_device_priv(tz); const struct tegra_tsensor *ts = tsc->ts; u32 val; /* * TSENSOR doesn't trigger interrupt on the "low" temperature breach, * hence bail out if high temperature is unspecified. */ if (high == INT_MAX) return 0; val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_CONFIG1); val &= ~TSENSOR_SENSOR0_CONFIG1_TH1; high = tegra_tsensor_temp_to_counter(ts, high); val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG1_TH1, high); writel_relaxed(val, tsc->regs + TSENSOR_SENSOR0_CONFIG1); return 0; } static const struct thermal_zone_device_ops ops = { .get_temp = tegra_tsensor_get_temp, .set_trips = tegra_tsensor_set_trips, }; static bool tegra_tsensor_handle_channel_interrupt(const struct tegra_tsensor *ts, unsigned int id) { const struct tegra_tsensor_channel *tsc = &ts->ch[id]; u32 val; val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_STATUS0); writel_relaxed(val, tsc->regs + TSENSOR_SENSOR0_STATUS0); if (FIELD_GET(TSENSOR_SENSOR0_STATUS0_STATE, val) == 5) dev_err_ratelimited(ts->dev, "ch%u: counter overflowed\n", id); if (!FIELD_GET(TSENSOR_SENSOR0_STATUS0_INTR, val)) return false; thermal_zone_device_update(tsc->tzd, THERMAL_EVENT_UNSPECIFIED); return true; } static irqreturn_t tegra_tsensor_isr(int irq, void *data) { const struct tegra_tsensor *ts = data; bool handled = false; unsigned int i; for (i = 0; i < ARRAY_SIZE(ts->ch); i++) handled |= tegra_tsensor_handle_channel_interrupt(ts, i); return handled ? IRQ_HANDLED : IRQ_NONE; } static int tegra_tsensor_disable_hw_channel(const struct tegra_tsensor *ts, unsigned int id) { const struct tegra_tsensor_channel *tsc = &ts->ch[id]; struct thermal_zone_device *tzd = tsc->tzd; u32 val; int err; if (!tzd) goto stop_channel; err = thermal_zone_device_disable(tzd); if (err) { dev_err(ts->dev, "ch%u: failed to disable zone: %d\n", id, err); return err; } stop_channel: /* stop channel gracefully */ val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_CONFIG0); val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_SENSOR_STOP, 1); writel_relaxed(val, tsc->regs + TSENSOR_SENSOR0_CONFIG0); return 0; } static void tegra_tsensor_get_hw_channel_trips(struct thermal_zone_device *tzd, int *hot_trip, int *crit_trip) { unsigned int i; /* * 90C is the maximal critical temperature of all Tegra30 SoC variants, * use it for the default trip if unspecified in a device-tree. */ *hot_trip = 85000; *crit_trip = 90000; for (i = 0; i < thermal_zone_get_num_trips(tzd); i++) { struct thermal_trip trip; thermal_zone_get_trip(tzd, i, &trip); if (trip.type == THERMAL_TRIP_HOT) *hot_trip = trip.temperature; if (trip.type == THERMAL_TRIP_CRITICAL) *crit_trip = trip.temperature; } /* clamp hardware trips to the calibration limits */ *hot_trip = clamp(*hot_trip, 25000, 90000); /* * Kernel will perform a normal system shut down if it will * see that critical temperature is breached, hence set the * hardware limit by 5C higher in order to allow system to * shut down gracefully before sending signal to the Power * Management controller. */ *crit_trip = clamp(*crit_trip + 5000, 25000, 90000); } static int tegra_tsensor_enable_hw_channel(const struct tegra_tsensor *ts, unsigned int id) { const struct tegra_tsensor_channel *tsc = &ts->ch[id]; struct thermal_zone_device *tzd = tsc->tzd; int err, hot_trip = 0, crit_trip = 0; u32 val; if (!tzd) { val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_CONFIG0); val &= ~TSENSOR_SENSOR0_CONFIG0_SENSOR_STOP; writel_relaxed(val, tsc->regs + TSENSOR_SENSOR0_CONFIG0); return 0; } tegra_tsensor_get_hw_channel_trips(tzd, &hot_trip, &crit_trip); dev_info_once(ts->dev, "ch%u: PMC emergency shutdown trip set to %dC\n", id, DIV_ROUND_CLOSEST(crit_trip, 1000)); hot_trip = tegra_tsensor_temp_to_counter(ts, hot_trip); crit_trip = tegra_tsensor_temp_to_counter(ts, crit_trip); /* program LEVEL2 counter threshold */ val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_CONFIG1); val &= ~TSENSOR_SENSOR0_CONFIG1_TH2; val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG1_TH2, hot_trip); writel_relaxed(val, tsc->regs + TSENSOR_SENSOR0_CONFIG1); /* program LEVEL3 counter threshold */ val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_CONFIG2); val &= ~TSENSOR_SENSOR0_CONFIG2_TH3; val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG2_TH3, crit_trip); writel_relaxed(val, tsc->regs + TSENSOR_SENSOR0_CONFIG2); /* * Enable sensor, emergency shutdown, interrupts for level 1/2/3 * breaches and counter overflow condition. * * Disable DIV2 throttle for now since we need to figure out how * to integrate it properly with the thermal framework. * * Thermal levels supported by hardware: * * Level 0 = cold * Level 1 = passive cooling (cpufreq DVFS) * Level 2 = passive cooling assisted by hardware (DIV2) * Level 3 = emergency shutdown assisted by hardware (PMC) */ val = readl_relaxed(tsc->regs + TSENSOR_SENSOR0_CONFIG0); val &= ~TSENSOR_SENSOR0_CONFIG0_SENSOR_STOP; val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_DVFS_EN, 1); val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_HW_FREQ_DIV_EN, 0); val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_THERMAL_RST_EN, 1); val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_INTR_OVERFLOW_EN, 1); val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_INTR_HW_FREQ_DIV_EN, 1); val |= FIELD_PREP(TSENSOR_SENSOR0_CONFIG0_INTR_THERMAL_RST_EN, 1); writel_relaxed(val, tsc->regs + TSENSOR_SENSOR0_CONFIG0); err = thermal_zone_device_enable(tzd); if (err) { dev_err(ts->dev, "ch%u: failed to enable zone: %d\n", id, err); return err; } return 0; } static bool tegra_tsensor_fuse_read_spare(unsigned int spare) { u32 val = 0; tegra_fuse_readl(TEGRA30_FUSE_SPARE_BIT + spare * 4, &val); return !!val; } static int tegra_tsensor_nvmem_setup(struct tegra_tsensor *ts) { u32 i, ate_ver = 0, cal = 0, t1_25C = 0, t2_90C = 0; int err, c1_25C, c2_90C; err = tegra_fuse_readl(TEGRA30_FUSE_TEST_PROG_VER, &ate_ver); if (err) { dev_err_probe(ts->dev, err, "failed to get ATE version\n"); return err; } if (ate_ver < 8) { dev_info(ts->dev, "unsupported ATE version: %u\n", ate_ver); return -ENODEV; } /* * We have two TSENSOR channels in a two different spots on SoC. * Second channel provides more accurate data on older SoC versions, * use it as a primary channel. */ if (ate_ver <= 21) { dev_info_once(ts->dev, "older ATE version detected, channels remapped\n"); ts->swap_channels = true; } err = tegra_fuse_readl(TEGRA30_FUSE_TSENSOR_CALIB, &cal); if (err) { dev_err(ts->dev, "failed to get calibration data: %d\n", err); return err; } /* get calibrated counter values for 25C/90C thresholds */ c1_25C = FIELD_GET(TEGRA30_FUSE_TSENSOR_CALIB_LOW, cal); c2_90C = FIELD_GET(TEGRA30_FUSE_TSENSOR_CALIB_HIGH, cal); /* and calibrated temperatures corresponding to the counter values */ for (i = 0; i < 7; i++) { t1_25C |= tegra_tsensor_fuse_read_spare(14 + i) << i; t1_25C |= tegra_tsensor_fuse_read_spare(21 + i) << i; t2_90C |= tegra_tsensor_fuse_read_spare(0 + i) << i; t2_90C |= tegra_tsensor_fuse_read_spare(7 + i) << i; } if (c2_90C - c1_25C <= t2_90C - t1_25C) { dev_err(ts->dev, "invalid calibration data: %d %d %u %u\n", c2_90C, c1_25C, t2_90C, t1_25C); return -EINVAL; } /* all calibration coefficients are premultiplied by 1000000 */ ts->calib.a = DIV_ROUND_CLOSEST((t2_90C - t1_25C) * 1000000, (c2_90C - c1_25C)); ts->calib.b = t1_25C * 1000000 - ts->calib.a * c1_25C; if (tegra_sku_info.revision == TEGRA_REVISION_A01) { ts->calib.m = -2775; ts->calib.n = 1338811; ts->calib.p = -7300000; } else { ts->calib.m = -3512; ts->calib.n = 1528943; ts->calib.p = -11100000; } /* except the coefficient of a reduced quadratic equation */ ts->calib.r = DIV_ROUND_CLOSEST(ts->calib.n, ts->calib.m * 2); dev_info_once(ts->dev, "calibration: %d %d %u %u ATE ver: %u SoC rev: %u\n", c2_90C, c1_25C, t2_90C, t1_25C, ate_ver, tegra_sku_info.revision); return 0; } static int tegra_tsensor_register_channel(struct tegra_tsensor *ts, unsigned int id) { struct tegra_tsensor_channel *tsc = &ts->ch[id]; unsigned int hw_id = ts->swap_channels ? !id : id; tsc->ts = ts; tsc->id = id; tsc->regs = ts->regs + 0x40 * (hw_id + 1); tsc->tzd = devm_thermal_of_zone_register(ts->dev, id, tsc, &ops); if (IS_ERR(tsc->tzd)) { if (PTR_ERR(tsc->tzd) != -ENODEV) return dev_err_probe(ts->dev, PTR_ERR(tsc->tzd), "failed to register thermal zone\n"); /* * It's okay if sensor isn't assigned to any thermal zone * in a device-tree. */ tsc->tzd = NULL; return 0; } devm_thermal_add_hwmon_sysfs(ts->dev, tsc->tzd); return 0; } static int tegra_tsensor_probe(struct platform_device *pdev) { struct tegra_tsensor *ts; unsigned int i; int err, irq; ts = devm_kzalloc(&pdev->dev, sizeof(*ts), GFP_KERNEL); if (!ts) return -ENOMEM; irq = platform_get_irq(pdev, 0); if (irq < 0) return irq; ts->dev = &pdev->dev; platform_set_drvdata(pdev, ts); ts->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(ts->regs)) return PTR_ERR(ts->regs); ts->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(ts->clk)) return dev_err_probe(&pdev->dev, PTR_ERR(ts->clk), "failed to get clock\n"); ts->rst = devm_reset_control_get_exclusive(&pdev->dev, NULL); if (IS_ERR(ts->rst)) return dev_err_probe(&pdev->dev, PTR_ERR(ts->rst), "failed to get reset control\n"); err = tegra_tsensor_nvmem_setup(ts); if (err) return err; err = tegra_tsensor_hw_enable(ts); if (err) return err; err = devm_add_action_or_reset(&pdev->dev, devm_tegra_tsensor_hw_disable, ts); if (err) return err; for (i = 0; i < ARRAY_SIZE(ts->ch); i++) { err = tegra_tsensor_register_channel(ts, i); if (err) return err; } /* * Enable the channels before setting the interrupt so * set_trips() can not be called while we are setting up the * register TSENSOR_SENSOR0_CONFIG1. With this we close a * potential race window where we are setting up the TH2 and * the temperature hits TH1 resulting to an update of the * TSENSOR_SENSOR0_CONFIG1 register in the ISR. */ for (i = 0; i < ARRAY_SIZE(ts->ch); i++) { err = tegra_tsensor_enable_hw_channel(ts, i); if (err) return err; } err = devm_request_threaded_irq(&pdev->dev, irq, NULL, tegra_tsensor_isr, IRQF_ONESHOT, "tegra_tsensor", ts); if (err) return dev_err_probe(&pdev->dev, err, "failed to request interrupt\n"); return 0; } static int __maybe_unused tegra_tsensor_suspend(struct device *dev) { struct tegra_tsensor *ts = dev_get_drvdata(dev); unsigned int i; int err; for (i = 0; i < ARRAY_SIZE(ts->ch); i++) { err = tegra_tsensor_disable_hw_channel(ts, i); if (err) goto enable_channel; } err = tegra_tsensor_hw_disable(ts); if (err) goto enable_channel; return 0; enable_channel: while (i--) tegra_tsensor_enable_hw_channel(ts, i); return err; } static int __maybe_unused tegra_tsensor_resume(struct device *dev) { struct tegra_tsensor *ts = dev_get_drvdata(dev); unsigned int i; int err; err = tegra_tsensor_hw_enable(ts); if (err) return err; for (i = 0; i < ARRAY_SIZE(ts->ch); i++) { err = tegra_tsensor_enable_hw_channel(ts, i); if (err) return err; } return 0; } static const struct dev_pm_ops tegra_tsensor_pm_ops = { SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(tegra_tsensor_suspend, tegra_tsensor_resume) }; static const struct of_device_id tegra_tsensor_of_match[] = { { .compatible = "nvidia,tegra30-tsensor", }, {}, }; MODULE_DEVICE_TABLE(of, tegra_tsensor_of_match); static struct platform_driver tegra_tsensor_driver = { .probe = tegra_tsensor_probe, .driver = { .name = "tegra30-tsensor", .of_match_table = tegra_tsensor_of_match, .pm = &tegra_tsensor_pm_ops, }, }; module_platform_driver(tegra_tsensor_driver); MODULE_DESCRIPTION("NVIDIA Tegra30 Thermal Sensor driver"); MODULE_AUTHOR("Dmitry Osipenko <digetx@gmail.com>"); MODULE_LICENSE("GPL");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1