diff options
Diffstat (limited to 'drivers/thermal')
23 files changed, 2236 insertions, 669 deletions
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 3c3dc4a3d52c..4166c10ba314 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -260,16 +260,6 @@ config ARMADA_THERMAL Enable this option if you want to have support for thermal management controller present in Armada 370 and Armada XP SoC. -config TEGRA_SOCTHERM - tristate "Tegra SOCTHERM thermal management" - depends on ARCH_TEGRA - help - Enable this option for integrated thermal management support on NVIDIA - Tegra124 systems-on-chip. The driver supports four thermal zones - (CPU, GPU, MEM, PLLX). Cooling devices can be bound to the thermal - zones to manage temperatures. This option is also required for the - emergency thermal reset (thermtrip) feature to function. - config DB8500_CPUFREQ_COOLING tristate "DB8500 cpufreq cooling" depends on ARCH_U8500 || COMPILE_TEST @@ -399,6 +389,17 @@ depends on ARCH_STI && OF source "drivers/thermal/st/Kconfig" endmenu +config TANGO_THERMAL + tristate "Tango thermal management" + depends on ARCH_TANGO || COMPILE_TEST + help + Enable the Tango thermal driver, which supports the primitive + temperature sensor embedded in Tango chips since the SMP8758. + This sensor only generates a 1-bit signal to indicate whether + the die temperature exceeds a programmable threshold. + +source "drivers/thermal/tegra/Kconfig" + config QCOM_SPMI_TEMP_ALARM tristate "Qualcomm SPMI PMIC Temperature Alarm" depends on OF && SPMI && IIO @@ -410,4 +411,14 @@ config QCOM_SPMI_TEMP_ALARM real time die temperature if an ADC is present or an estimate of the temperature based upon the over temperature stage value. +config GENERIC_ADC_THERMAL + tristate "Generic ADC based thermal sensor" + depends on IIO + help + This enabled a thermal sysfs driver for the temperature sensor + which is connected to the General Purpose ADC. The ADC channel + is read via IIO framework and the channel information is provided + to this driver. This driver reports the temperature by reading ADC + channel and converts it to temperature based on lookup table. + endif diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 8e9cbc3b5679..10b07c14f8a9 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -35,6 +35,7 @@ obj-y += samsung/ obj-$(CONFIG_DOVE_THERMAL) += dove_thermal.o obj-$(CONFIG_DB8500_THERMAL) += db8500_thermal.o obj-$(CONFIG_ARMADA_THERMAL) += armada_thermal.o +obj-$(CONFIG_TANGO_THERMAL) += tango_thermal.o obj-$(CONFIG_IMX_THERMAL) += imx_thermal.o obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.o obj-$(CONFIG_INTEL_POWERCLAMP) += intel_powerclamp.o @@ -46,6 +47,7 @@ obj-$(CONFIG_TI_SOC_THERMAL) += ti-soc-thermal/ obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal/ obj-$(CONFIG_INTEL_PCH_THERMAL) += intel_pch_thermal.o obj-$(CONFIG_ST_THERMAL) += st/ -obj-$(CONFIG_TEGRA_SOCTHERM) += tegra_soctherm.o +obj-$(CONFIG_TEGRA_SOCTHERM) += tegra/ obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o obj-$(CONFIG_MTK_THERMAL) += mtk_thermal.o +obj-$(CONFIG_GENERIC_ADC_THERMAL) += thermal-generic-adc.o diff --git a/drivers/thermal/hisi_thermal.c b/drivers/thermal/hisi_thermal.c index 5e820b541506..97fad8f51e1c 100644 --- a/drivers/thermal/hisi_thermal.c +++ b/drivers/thermal/hisi_thermal.c @@ -160,7 +160,7 @@ static int hisi_thermal_get_temp(void *_sensor, int *temp) struct hisi_thermal_sensor *sensor = _sensor; struct hisi_thermal_data *data = sensor->thermal; - int sensor_id = 0, i; + int sensor_id = -1, i; long max_temp = 0; *temp = hisi_thermal_get_sensor_temp(data, sensor); @@ -168,12 +168,19 @@ static int hisi_thermal_get_temp(void *_sensor, int *temp) sensor->sensor_temp = *temp; for (i = 0; i < HISI_MAX_SENSORS; i++) { + if (!data->sensors[i].tzd) + continue; + if (data->sensors[i].sensor_temp >= max_temp) { max_temp = data->sensors[i].sensor_temp; sensor_id = i; } } + /* If no sensor has been enabled, then skip to enable irq */ + if (sensor_id == -1) + return 0; + mutex_lock(&data->thermal_lock); data->irq_bind_sensor = sensor_id; mutex_unlock(&data->thermal_lock); @@ -226,8 +233,12 @@ static irqreturn_t hisi_thermal_alarm_irq_thread(int irq, void *dev) sensor->thres_temp / 1000); mutex_unlock(&data->thermal_lock); - for (i = 0; i < HISI_MAX_SENSORS; i++) + for (i = 0; i < HISI_MAX_SENSORS; i++) { + if (!data->sensors[i].tzd) + continue; + thermal_zone_device_update(data->sensors[i].tzd); + } return IRQ_HANDLED; } @@ -243,10 +254,11 @@ static int hisi_thermal_register_sensor(struct platform_device *pdev, sensor->id = index; sensor->thermal = data; - sensor->tzd = thermal_zone_of_sensor_register(&pdev->dev, sensor->id, - sensor, &hisi_of_thermal_ops); + sensor->tzd = devm_thermal_zone_of_sensor_register(&pdev->dev, + sensor->id, sensor, &hisi_of_thermal_ops); if (IS_ERR(sensor->tzd)) { ret = PTR_ERR(sensor->tzd); + sensor->tzd = NULL; dev_err(&pdev->dev, "failed to register sensor id %d: %d\n", sensor->id, ret); return ret; @@ -331,28 +343,21 @@ static int hisi_thermal_probe(struct platform_device *pdev) return ret; } + hisi_thermal_enable_bind_irq_sensor(data); + irq_get_irqchip_state(data->irq, IRQCHIP_STATE_MASKED, + &data->irq_enabled); + for (i = 0; i < HISI_MAX_SENSORS; ++i) { ret = hisi_thermal_register_sensor(pdev, data, &data->sensors[i], i); - if (ret) { + if (ret) dev_err(&pdev->dev, "failed to register thermal sensor: %d\n", ret); - goto err_get_sensor_data; - } + else + hisi_thermal_toggle_sensor(&data->sensors[i], true); } - hisi_thermal_enable_bind_irq_sensor(data); - data->irq_enabled = true; - - for (i = 0; i < HISI_MAX_SENSORS; i++) - hisi_thermal_toggle_sensor(&data->sensors[i], true); - return 0; - -err_get_sensor_data: - clk_disable_unprepare(data->clk); - - return ret; } static int hisi_thermal_remove(struct platform_device *pdev) @@ -363,8 +368,10 @@ static int hisi_thermal_remove(struct platform_device *pdev) for (i = 0; i < HISI_MAX_SENSORS; i++) { struct hisi_thermal_sensor *sensor = &data->sensors[i]; + if (!sensor->tzd) + continue; + hisi_thermal_toggle_sensor(sensor, false); - thermal_zone_of_sensor_unregister(&pdev->dev, sensor->tzd); } hisi_thermal_disable_sensor(data); diff --git a/drivers/thermal/int340x_thermal/processor_thermal_device.c b/drivers/thermal/int340x_thermal/processor_thermal_device.c index 36fa724a36c8..42c1ac057bad 100644 --- a/drivers/thermal/int340x_thermal/processor_thermal_device.c +++ b/drivers/thermal/int340x_thermal/processor_thermal_device.c @@ -198,49 +198,33 @@ static struct thermal_zone_device_ops proc_thermal_local_ops = { .get_temp = proc_thermal_get_zone_temp, }; -static int proc_thermal_add(struct device *dev, - struct proc_thermal_device **priv) +static int proc_thermal_read_ppcc(struct proc_thermal_device *proc_priv) { - struct proc_thermal_device *proc_priv; - struct acpi_device *adev; + int i; acpi_status status; struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *elements, *ppcc; union acpi_object *p; - unsigned long long tmp; - struct thermal_zone_device_ops *ops = NULL; - int i; - int ret; - - adev = ACPI_COMPANION(dev); - if (!adev) - return -ENODEV; + int ret = 0; - status = acpi_evaluate_object(adev->handle, "PPCC", NULL, &buf); + status = acpi_evaluate_object(proc_priv->adev->handle, "PPCC", + NULL, &buf); if (ACPI_FAILURE(status)) return -ENODEV; p = buf.pointer; if (!p || (p->type != ACPI_TYPE_PACKAGE)) { - dev_err(dev, "Invalid PPCC data\n"); + dev_err(proc_priv->dev, "Invalid PPCC data\n"); ret = -EFAULT; goto free_buffer; } + if (!p->package.count) { - dev_err(dev, "Invalid PPCC package size\n"); + dev_err(proc_priv->dev, "Invalid PPCC package size\n"); ret = -EFAULT; goto free_buffer; } - proc_priv = devm_kzalloc(dev, sizeof(*proc_priv), GFP_KERNEL); - if (!proc_priv) { - ret = -ENOMEM; - goto free_buffer; - } - - proc_priv->dev = dev; - proc_priv->adev = adev; - for (i = 0; i < min((int)p->package.count - 1, 2); ++i) { elements = &(p->package.elements[i+1]); if (elements->type != ACPI_TYPE_PACKAGE || @@ -257,12 +241,62 @@ static int proc_thermal_add(struct device *dev, proc_priv->power_limits[i].step_uw = ppcc[5].integer.value; } +free_buffer: + kfree(buf.pointer); + + return ret; +} + +#define PROC_POWER_CAPABILITY_CHANGED 0x83 +static void proc_thermal_notify(acpi_handle handle, u32 event, void *data) +{ + struct proc_thermal_device *proc_priv = data; + + if (!proc_priv) + return; + + switch (event) { + case PROC_POWER_CAPABILITY_CHANGED: + proc_thermal_read_ppcc(proc_priv); + int340x_thermal_zone_device_update(proc_priv->int340x_zone); + break; + default: + dev_err(proc_priv->dev, "Unsupported event [0x%x]\n", event); + break; + } +} + + +static int proc_thermal_add(struct device *dev, + struct proc_thermal_device **priv) +{ + struct proc_thermal_device *proc_priv; + struct acpi_device *adev; + acpi_status status; + unsigned long long tmp; + struct thermal_zone_device_ops *ops = NULL; + int ret; + + adev = ACPI_COMPANION(dev); + if (!adev) + return -ENODEV; + + proc_priv = devm_kzalloc(dev, sizeof(*proc_priv), GFP_KERNEL); + if (!proc_priv) + return -ENOMEM; + + proc_priv->dev = dev; + proc_priv->adev = adev; *priv = proc_priv; - ret = sysfs_create_group(&dev->kobj, - &power_limit_attribute_group); + ret = proc_thermal_read_ppcc(proc_priv); + if (!ret) { + ret = sysfs_create_group(&dev->kobj, + &power_limit_attribute_group); + + } if (ret) - goto free_buffer; + return ret; status = acpi_evaluate_integer(adev->handle, "_TMP", NULL, &tmp); if (ACPI_FAILURE(status)) { @@ -274,20 +308,32 @@ static int proc_thermal_add(struct device *dev, proc_priv->int340x_zone = int340x_thermal_zone_add(adev, ops); if (IS_ERR(proc_priv->int340x_zone)) { - sysfs_remove_group(&proc_priv->dev->kobj, - &power_limit_attribute_group); ret = PTR_ERR(proc_priv->int340x_zone); + goto remove_group; } else ret = 0; -free_buffer: - kfree(buf.pointer); + ret = acpi_install_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY, + proc_thermal_notify, + (void *)proc_priv); + if (ret) + goto remove_zone; + + return 0; + +remove_zone: + int340x_thermal_zone_remove(proc_priv->int340x_zone); +remove_group: + sysfs_remove_group(&proc_priv->dev->kobj, + &power_limit_attribute_group); return ret; } static void proc_thermal_remove(struct proc_thermal_device *proc_priv) { + acpi_remove_notify_handler(proc_priv->adev->handle, + ACPI_DEVICE_NOTIFY, proc_thermal_notify); int340x_thermal_zone_remove(proc_priv->int340x_zone); sysfs_remove_group(&proc_priv->dev->kobj, &power_limit_attribute_group); diff --git a/drivers/thermal/intel_powerclamp.c b/drivers/thermal/intel_powerclamp.c index 6c79588251d5..015ce2eb6eb7 100644 --- a/drivers/thermal/intel_powerclamp.c +++ b/drivers/thermal/intel_powerclamp.c @@ -510,12 +510,6 @@ static int start_power_clamp(void) unsigned long cpu; struct task_struct *thread; - /* check if pkg cstate counter is completely 0, abort in this case */ - if (!has_pkg_state_counter()) { - pr_err("pkg cstate counter not functional, abort\n"); - return -EINVAL; - } - set_target_ratio = clamp(set_target_ratio, 0U, MAX_TARGET_RATIO - 1); /* prevent cpu hotplug */ get_online_cpus(); @@ -672,35 +666,11 @@ static struct thermal_cooling_device_ops powerclamp_cooling_ops = { .set_cur_state = powerclamp_set_cur_state, }; -/* runs on Nehalem and later */ static const struct x86_cpu_id intel_powerclamp_ids[] __initconst = { - { X86_VENDOR_INTEL, 6, 0x1a}, - { X86_VENDOR_INTEL, 6, 0x1c}, - { X86_VENDOR_INTEL, 6, 0x1e}, - { X86_VENDOR_INTEL, 6, 0x1f}, - { X86_VENDOR_INTEL, 6, 0x25}, - { X86_VENDOR_INTEL, 6, 0x26}, - { X86_VENDOR_INTEL, 6, 0x2a}, - { X86_VENDOR_INTEL, 6, 0x2c}, - { X86_VENDOR_INTEL, 6, 0x2d}, - { X86_VENDOR_INTEL, 6, 0x2e}, - { X86_VENDOR_INTEL, 6, 0x2f}, - { X86_VENDOR_INTEL, 6, 0x37}, - { X86_VENDOR_INTEL, 6, 0x3a}, - { X86_VENDOR_INTEL, 6, 0x3c}, - { X86_VENDOR_INTEL, 6, 0x3d}, - { X86_VENDOR_INTEL, 6, 0x3e}, - { X86_VENDOR_INTEL, 6, 0x3f}, - { X86_VENDOR_INTEL, 6, 0x45}, - { X86_VENDOR_INTEL, 6, 0x46}, - { X86_VENDOR_INTEL, 6, 0x47}, - { X86_VENDOR_INTEL, 6, 0x4c}, - { X86_VENDOR_INTEL, 6, 0x4d}, - { X86_VENDOR_INTEL, 6, 0x4e}, - { X86_VENDOR_INTEL, 6, 0x4f}, - { X86_VENDOR_INTEL, 6, 0x56}, - { X86_VENDOR_INTEL, 6, 0x57}, - { X86_VENDOR_INTEL, 6, 0x5e}, + { X86_VENDOR_INTEL, X86_FAMILY_ANY, X86_MODEL_ANY, X86_FEATURE_MWAIT }, + { X86_VENDOR_INTEL, X86_FAMILY_ANY, X86_MODEL_ANY, X86_FEATURE_ARAT }, + { X86_VENDOR_INTEL, X86_FAMILY_ANY, X86_MODEL_ANY, X86_FEATURE_NONSTOP_TSC }, + { X86_VENDOR_INTEL, X86_FAMILY_ANY, X86_MODEL_ANY, X86_FEATURE_CONSTANT_TSC}, {} }; MODULE_DEVICE_TABLE(x86cpu, intel_powerclamp_ids); @@ -712,11 +682,12 @@ static int __init powerclamp_probe(void) boot_cpu_data.x86, boot_cpu_data.x86_model); return -ENODEV; } - if (!boot_cpu_has(X86_FEATURE_NONSTOP_TSC) || - !boot_cpu_has(X86_FEATURE_CONSTANT_TSC) || - !boot_cpu_has(X86_FEATURE_MWAIT) || - !boot_cpu_has(X86_FEATURE_ARAT)) + + /* The goal for idle time alignment is to achieve package cstate. */ + if (!has_pkg_state_counter()) { + pr_info("No package C-state available"); return -ENODEV; + } /* find the deepest mwait value */ find_target_mwait(); diff --git a/drivers/thermal/mtk_thermal.c b/drivers/thermal/mtk_thermal.c index 507632b9648e..262ab0a2266f 100644 --- a/drivers/thermal/mtk_thermal.c +++ b/drivers/thermal/mtk_thermal.c @@ -144,7 +144,6 @@ struct mtk_thermal { s32 o_slope; s32 vts[MT8173_NUM_SENSORS]; - struct thermal_zone_device *tzd; }; struct mtk_thermal_bank_cfg { @@ -572,16 +571,11 @@ static int mtk_thermal_probe(struct platform_device *pdev) platform_set_drvdata(pdev, mt); - mt->tzd = thermal_zone_of_sensor_register(&pdev->dev, 0, mt, - &mtk_thermal_ops); - if (IS_ERR(mt->tzd)) - goto err_register; + devm_thermal_zone_of_sensor_register(&pdev->dev, 0, mt, + &mtk_thermal_ops); return 0; -err_register: - clk_disable_unprepare(mt->clk_peri_therm); - err_disable_clk_auxadc: clk_disable_unprepare(mt->clk_auxadc); @@ -592,8 +586,6 @@ static int mtk_thermal_remove(struct platform_device *pdev) { struct mtk_thermal *mt = platform_get_drvdata(pdev); - thermal_zone_of_sensor_unregister(&pdev->dev, mt->tzd); - clk_disable_unprepare(mt->clk_peri_therm); clk_disable_unprepare(mt->clk_auxadc); diff --git a/drivers/thermal/of-thermal.c b/drivers/thermal/of-thermal.c index d8ec44b194d6..b8e509c60848 100644 --- a/drivers/thermal/of-thermal.c +++ b/drivers/thermal/of-thermal.c @@ -331,6 +331,14 @@ static int of_thermal_set_trip_temp(struct thermal_zone_device *tz, int trip, if (trip >= data->ntrips || trip < 0) return -EDOM; + if (data->ops->set_trip_temp) { + int ret; + + ret = data->ops->set_trip_temp(data->sensor_data, trip, temp); + if (ret) + return ret; + } + /* thermal framework should take care of data->mask & (1 << trip) */ data->trips[trip].temperature = temp; @@ -906,7 +914,7 @@ finish: return tz; free_tbps: - for (i = 0; i < tz->num_tbps; i++) + for (i = i - 1; i >= 0; i--) of_node_put(tz->tbps[i].cooling_device); kfree(tz->tbps); free_trips: diff --git a/drivers/thermal/qcom-spmi-temp-alarm.c b/drivers/thermal/qcom-spmi-temp-alarm.c index b677aada5b52..f8a3c60bef94 100644 --- a/drivers/thermal/qcom-spmi-temp-alarm.c +++ b/drivers/thermal/qcom-spmi-temp-alarm.c @@ -260,7 +260,7 @@ static int qpnp_tm_probe(struct platform_device *pdev) if (ret < 0) goto fail; - chip->tz_dev = thermal_zone_of_sensor_register(&pdev->dev, 0, chip, + chip->tz_dev = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, chip, &qpnp_tm_sensor_ops); if (IS_ERR(chip->tz_dev)) { dev_err(&pdev->dev, "failed to register sensor\n"); @@ -281,7 +281,6 @@ static int qpnp_tm_remove(struct platform_device *pdev) { struct qpnp_tm_chip *chip = dev_get_drvdata(&pdev->dev); - thermal_zone_of_sensor_unregister(&pdev->dev, chip->tz_dev); if (!IS_ERR(chip->adc)) iio_channel_release(chip->adc); diff --git a/drivers/thermal/rcar_thermal.c b/drivers/thermal/rcar_thermal.c index 82daba09e150..71a339271fa5 100644 --- a/drivers/thermal/rcar_thermal.c +++ b/drivers/thermal/rcar_thermal.c @@ -492,7 +492,7 @@ static int rcar_thermal_probe(struct platform_device *pdev) goto error_unregister; if (of_data == USE_OF_THERMAL) - priv->zone = thermal_zone_of_sensor_register( + priv->zone = devm_thermal_zone_of_sensor_register( dev, i, priv, &rcar_thermal_zone_of_ops); else diff --git a/drivers/thermal/rockchip_thermal.c b/drivers/thermal/rockchip_thermal.c index 233a564442a0..5d491f16a866 100644 --- a/drivers/thermal/rockchip_thermal.c +++ b/drivers/thermal/rockchip_thermal.c @@ -1,7 +1,5 @@ /* - * Copyright (c) 2014, Fuzhou Rockchip Electronics Co., Ltd - * - * Copyright (c) 2015, Fuzhou Rockchip Electronics Co., Ltd + * Copyright (c) 2014-2016, Fuzhou Rockchip Electronics Co., Ltd * Caesar Wang <wxt@rock-chips.com> * * This program is free software; you can redistribute it and/or modify it @@ -23,8 +21,10 @@ #include <linux/of_address.h> #include <linux/of_irq.h> #include <linux/platform_device.h> +#include <linux/regmap.h> #include <linux/reset.h> #include <linux/thermal.h> +#include <linux/mfd/syscon.h> #include <linux/pinctrl/consumer.h> /** @@ -73,7 +73,7 @@ enum adc_sort_mode { #define SOC_MAX_SENSORS 2 /** - * struct chip_tsadc_table: hold information about chip-specific differences + * struct chip_tsadc_table - hold information about chip-specific differences * @id: conversion table * @length: size of conversion table * @data_mask: mask to apply on data inputs @@ -86,6 +86,20 @@ struct chip_tsadc_table { enum adc_sort_mode mode; }; +/** + * struct rockchip_tsadc_chip - hold the private data of tsadc chip + * @chn_id[SOC_MAX_SENSORS]: the sensor id of chip correspond to the channel + * @chn_num: the channel number of tsadc chip + * @tshut_temp: the hardware-controlled shutdown temperature value + * @tshut_mode: the hardware-controlled shutdown mode (0:CRU 1:GPIO) + * @tshut_polarity: the hardware-controlled active polarity (0:LOW 1:HIGH) + * @initialize: SoC special initialize tsadc controller method + * @irq_ack: clear the interrupt + * @get_temp: get the temperature + * @set_tshut_temp: set the hardware-controlled shutdown temperature + * @set_tshut_mode: set the hardware-controlled shutdown mode + * @table: the chip-specific conversion table + */ struct rockchip_tsadc_chip { /* The sensor id of chip correspond to the ADC channel */ int chn_id[SOC_MAX_SENSORS]; @@ -97,7 +111,8 @@ struct rockchip_tsadc_chip { enum tshut_polarity tshut_polarity; /* Chip-wide methods */ - void (*initialize)(void __iomem *reg, enum tshut_polarity p); + void (*initialize)(struct regmap *grf, + void __iomem *reg, enum tshut_polarity p); void (*irq_ack)(void __iomem *reg); void (*control)(void __iomem *reg, bool on); @@ -112,12 +127,32 @@ struct rockchip_tsadc_chip { struct chip_tsadc_table table; }; +/** + * struct rockchip_thermal_sensor - hold the information of thermal sensor + * @thermal: pointer to the platform/configuration data + * @tzd: pointer to a thermal zone + * @id: identifier of the thermal sensor + */ struct rockchip_thermal_sensor { struct rockchip_thermal_data *thermal; struct thermal_zone_device *tzd; int id; }; +/** + * struct rockchip_thermal_data - hold the private data of thermal driver + * @chip: pointer to the platform/configuration data + * @pdev: platform device of thermal + * @reset: the reset controller of tsadc + * @sensors[SOC_MAX_SENSORS]: the thermal sensor + * @clk: the controller clock is divided by the exteral 24MHz + * @pclk: the advanced peripherals bus clock + * @grf: the general register file will be used to do static set by software + * @regs: the base address of tsadc controller + * @tshut_temp: the hardware-controlled shutdown temperature value + * @tshut_mode: the hardware-controlled shutdown mode (0:CRU 1:GPIO) + * @tshut_polarity: the hardware-controlled active polarity (0:LOW 1:HIGH) + */ struct rockchip_thermal_data { const struct rockchip_tsadc_chip *chip; struct platform_device *pdev; @@ -128,6 +163,7 @@ struct rockchip_thermal_data { struct clk *clk; struct clk *pclk; + struct regmap *grf; void __iomem *regs; int tshut_temp; @@ -142,6 +178,7 @@ struct rockchip_thermal_data { * TSADCV3_* are used for newer SoCs than RK3288. (e.g: RK3228, RK3399) * */ +#define TSADCV2_USER_CON 0x00 #define TSADCV2_AUTO_CON 0x04 #define TSADCV2_INT_EN 0x08 #define TSADCV2_INT_PD 0x0c @@ -155,12 +192,7 @@ struct rockchip_thermal_data { #define TSADCV2_AUTO_EN BIT(0) #define TSADCV2_AUTO_SRC_EN(chn) BIT(4 + (chn)) #define TSADCV2_AUTO_TSHUT_POLARITY_HIGH BIT(8) -/** - * TSADCV1_AUTO_Q_SEL_EN: - * whether select (1024 - tsadc_q) as output - * 1'b0:use tsadc_q as output(temperature-code is rising sequence) - * 1'b1:use(1024 - tsadc_q) as output (temperature-code is falling sequence) - */ + #define TSADCV3_AUTO_Q_SEL_EN BIT(1) #define TSADCV2_INT_SRC_EN(chn) BIT(chn) @@ -177,19 +209,32 @@ struct rockchip_thermal_data { #define TSADCV2_HIGHT_TSHUT_DEBOUNCE_COUNT 4 #define TSADCV2_AUTO_PERIOD_TIME 250 /* msec */ #define TSADCV2_AUTO_PERIOD_HT_TIME 50 /* msec */ +#define TSADCV2_USER_INTER_PD_SOC 0x340 /* 13 clocks */ -struct tsadc_table { - u32 code; - int temp; -}; +#define GRF_SARADC_TESTBIT 0x0e644 +#define GRF_TSADC_TESTBIT_L 0x0e648 +#define GRF_TSADC_TESTBIT_H 0x0e64c + +#define GRF_TSADC_TSEN_PD_ON (0x30003 << 0) +#define GRF_TSADC_TSEN_PD_OFF (0x30000 << 0) +#define GRF_SARADC_TESTBIT_ON (0x10001 << 2) +#define GRF_TSADC_TESTBIT_H_ON (0x10001 << 2) /** + * struct tsadc_table - code to temperature conversion table + * @code: the value of adc channel + * @temp: the temperature * Note: - * Code to Temperature mapping of the Temperature sensor is a piece wise linear + * code to temperature mapping of the temperature sensor is a piece wise linear * curve.Any temperature, code faling between to 2 give temperatures can be * linearly interpolated. - * Code to Temperature mapping should be updated based on sillcon results. + * Code to Temperature mapping should be updated based on manufacturer results. */ +struct tsadc_table { + u32 code; + int temp; +}; + static const struct tsadc_table rk3228_code_table[] = { {0, -40000}, {588, -40000}, @@ -308,40 +353,40 @@ static const struct tsadc_table rk3368_code_table[] = { static const struct tsadc_table rk3399_code_table[] = { {0, -40000}, - {593, -40000}, - {598, -35000}, - {603, -30000}, - {609, -25000}, - {614, -20000}, - {619, -15000}, - {625, -10000}, - {630, -5000}, - {635, 0}, - {641, 5000}, - {646, 10000}, - {651, 15000}, - {657, 20000}, - {662, 25000}, - {667, 30000}, - {673, 35000}, - {678, 40000}, - {684, 45000}, - {689, 50000}, - {694, 55000}, - {700, 60000}, - {705, 65000}, - {711, 70000}, - {716, 75000}, - {722, 80000}, - {727, 85000}, - {733, 90000}, - {738, 95000}, - {743, 100000}, - {749, 105000}, - {754, 110000}, - {760, 115000}, - {765, 120000}, - {771, 125000}, + {402, -40000}, + {410, -35000}, + {419, -30000}, + {427, -25000}, + {436, -20000}, + {444, -15000}, + {453, -10000}, + {461, -5000}, + {470, 0}, + {478, 5000}, + {487, 10000}, + {496, 15000}, + {504, 20000}, + {513, 25000}, + {521, 30000}, + {530, 35000}, + {538, 40000}, + {547, 45000}, + {555, 50000}, + {564, 55000}, + {573, 60000}, + {581, 65000}, + {590, 70000}, + {599, 75000}, + {607, 80000}, + {616, 85000}, + {624, 90000}, + {633, 95000}, + {642, 100000}, + {650, 105000}, + {659, 110000}, + {668, 115000}, + {677, 120000}, + {685, 125000}, {TSADCV3_DATA_MASK, 125000}, }; @@ -405,8 +450,8 @@ static int rk_tsadcv2_code_to_temp(struct chip_tsadc_table table, u32 code, return -EAGAIN; /* Incorrect reading */ while (low <= high) { - if (code >= table.id[mid - 1].code && - code < table.id[mid].code) + if (code <= table.id[mid].code && + code > table.id[mid - 1].code) break; else if (code > table.id[mid].code) low = mid + 1; @@ -449,7 +494,7 @@ static int rk_tsadcv2_code_to_temp(struct chip_tsadc_table table, u32 code, * If the temperature is higher than COMP_INT or COMP_SHUT for * "debounce" times, TSADC controller will generate interrupt or TSHUT. */ -static void rk_tsadcv2_initialize(void __iomem *regs, +static void rk_tsadcv2_initialize(struct regmap *grf, void __iomem *regs, enum tshut_polarity tshut_polarity) { if (tshut_polarity == TSHUT_HIGH_ACTIVE) @@ -466,6 +511,62 @@ static void rk_tsadcv2_initialize(void __iomem *regs, regs + TSADCV2_AUTO_PERIOD_HT); writel_relaxed(TSADCV2_HIGHT_TSHUT_DEBOUNCE_COUNT, regs + TSADCV2_HIGHT_TSHUT_DEBOUNCE); + + if (IS_ERR(grf)) { + pr_warn("%s: Missing rockchip,grf property\n", __func__); + return; + } +} + +/** + * rk_tsadcv3_initialize - initialize TASDC Controller. + * + * (1) The tsadc control power sequence. + * + * (2) Set TSADC_V2_AUTO_PERIOD: + * Configure the interleave between every two accessing of + * TSADC in normal operation. + * + * (2) Set TSADCV2_AUTO_PERIOD_HT: + * Configure the interleave between every two accessing of + * TSADC after the temperature is higher than COM_SHUT or COM_INT. + * + * (3) Set TSADCV2_HIGH_INT_DEBOUNCE and TSADC_HIGHT_TSHUT_DEBOUNCE: + * If the temperature is higher than COMP_INT or COMP_SHUT for + * "debounce" times, TSADC controller will generate interrupt or TSHUT. + */ +static void rk_tsadcv3_initialize(struct regmap *grf, void __iomem *regs, + enum tshut_polarity tshut_polarity) +{ + /* The tsadc control power sequence */ + if (IS_ERR(grf)) { + /* Set interleave value to workround ic time sync issue */ + writel_relaxed(TSADCV2_USER_INTER_PD_SOC, regs + + TSADCV2_USER_CON); + } else { + regmap_write(grf, GRF_TSADC_TESTBIT_L, GRF_TSADC_TSEN_PD_ON); + mdelay(10); + regmap_write(grf, GRF_TSADC_TESTBIT_L, GRF_TSADC_TSEN_PD_OFF); + usleep_range(15, 100); /* The spec note says at least 15 us */ + regmap_write(grf, GRF_SARADC_TESTBIT, GRF_SARADC_TESTBIT_ON); + regmap_write(grf, GRF_TSADC_TESTBIT_H, GRF_TSADC_TESTBIT_H_ON); + usleep_range(90, 200); /* The spec note says at least 90 us */ + } + + if (tshut_polarity == TSHUT_HIGH_ACTIVE) + writel_relaxed(0U | TSADCV2_AUTO_TSHUT_POLARITY_HIGH, + regs + TSADCV2_AUTO_CON); + else + writel_relaxed(0U & ~TSADCV2_AUTO_TSHUT_POLARITY_HIGH, + regs + TSADCV2_AUTO_CON); + + writel_relaxed(TSADCV2_AUTO_PERIOD_TIME, regs + TSADCV2_AUTO_PERIOD); + writel_relaxed(TSADCV2_HIGHT_INT_DEBOUNCE_COUNT, + regs + TSADCV2_HIGHT_INT_DEBOUNCE); + writel_relaxed(TSADCV2_AUTO_PERIOD_HT_TIME, + regs + TSADCV2_AUTO_PERIOD_HT); + writel_relaxed(TSADCV2_HIGHT_TSHUT_DEBOUNCE_COUNT, + regs + TSADCV2_HIGHT_TSHUT_DEBOUNCE); } static void rk_tsadcv2_irq_ack(void __iomem *regs) @@ -498,10 +599,11 @@ static void rk_tsadcv2_control(void __iomem *regs, bool enable) } /** - * @rk_tsadcv3_control: - * TSADC controller works at auto mode, and some SoCs need set the tsadc_q_sel - * bit on TSADCV2_AUTO_CON[1]. The (1024 - tsadc_q) as output adc value if - * setting this bit to enable. + * rk_tsadcv3_control - the tsadc controller is enabled or disabled. + * + * NOTE: TSADC controller works at auto mode, and some SoCs need set the + * tsadc_q_sel bit on TSADCV2_AUTO_CON[1]. The (1024 - tsadc_q) as output + * adc value if setting this bit to enable. */ static void rk_tsadcv3_control(void __iomem *regs, bool enable) { @@ -603,6 +705,30 @@ static const struct rockchip_tsadc_chip rk3288_tsadc_data = { }, }; +static const struct rockchip_tsadc_chip rk3366_tsadc_data = { + .chn_id[SENSOR_CPU] = 0, /* cpu sensor is channel 0 */ + .chn_id[SENSOR_GPU] = 1, /* gpu sensor is channel 1 */ + .chn_num = 2, /* two channels for tsadc */ + + .tshut_mode = TSHUT_MODE_GPIO, /* default TSHUT via GPIO give PMIC */ + .tshut_polarity = TSHUT_LOW_ACTIVE, /* default TSHUT LOW ACTIVE */ + .tshut_temp = 95000, + + .initialize = rk_tsadcv3_initialize, + .irq_ack = rk_tsadcv3_irq_ack, + .control = rk_tsadcv3_control, + .get_temp = rk_tsadcv2_get_temp, + .set_tshut_temp = rk_tsadcv2_tshut_temp, + .set_tshut_mode = rk_tsadcv2_tshut_mode, + + .table = { + .id = rk3228_code_table, + .length = ARRAY_SIZE(rk3228_code_table), + .data_mask = TSADCV3_DATA_MASK, + .mode = ADC_INCREMENT, + }, +}; + static const struct rockchip_tsadc_chip rk3368_tsadc_data = { .chn_id[SENSOR_CPU] = 0, /* cpu sensor is channel 0 */ .chn_id[SENSOR_GPU] = 1, /* gpu sensor is channel 1 */ @@ -636,7 +762,7 @@ static const struct rockchip_tsadc_chip rk3399_tsadc_data = { .tshut_polarity = TSHUT_LOW_ACTIVE, /* default TSHUT LOW ACTIVE */ .tshut_temp = 95000, - .initialize = rk_tsadcv2_initialize, + .initialize = rk_tsadcv3_initialize, .irq_ack = rk_tsadcv3_irq_ack, .control = rk_tsadcv3_control, .get_temp = rk_tsadcv2_get_temp, @@ -661,6 +787,10 @@ static const struct of_device_id of_rockchip_thermal_match[] = { .data = (void *)&rk3288_tsadc_data, }, { + .compatible = "rockchip,rk3366-tsadc", + .data = (void *)&rk3366_tsadc_data, + }, + { .compatible = "rockchip,rk3368-tsadc", .data = (void *)&rk3368_tsadc_data, }, @@ -768,6 +898,11 @@ static int rockchip_configure_from_dt(struct device *dev, return -EINVAL; } + /* The tsadc wont to handle the error in here since some SoCs didn't + * need this property. + */ + thermal->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); + return 0; } @@ -786,8 +921,8 @@ rockchip_thermal_register_sensor(struct platform_device *pdev, sensor->thermal = thermal; sensor->id = id; - sensor->tzd = thermal_zone_of_sensor_register(&pdev->dev, id, sensor, - &rockchip_of_thermal_ops); + sensor->tzd = devm_thermal_zone_of_sensor_register(&pdev->dev, id, + sensor, &rockchip_of_thermal_ops); if (IS_ERR(sensor->tzd)) { error = PTR_ERR(sensor->tzd); dev_err(&pdev->dev, "failed to register sensor %d: %d\n", @@ -815,7 +950,7 @@ static int rockchip_thermal_probe(struct platform_device *pdev) const struct of_device_id *match; struct resource *res; int irq; - int i, j; + int i; int error; match = of_match_node(of_rockchip_thermal_match, np); @@ -888,7 +1023,8 @@ static int rockchip_thermal_probe(struct platform_device *pdev) goto err_disable_pclk; } - thermal->chip->initialize(thermal->regs, thermal->tshut_polarity); + thermal->chip->initialize(thermal->grf, thermal->regs, + thermal->tshut_polarity); for (i = 0; i < thermal->chip->chn_num; i++) { error = rockchip_thermal_register_sensor(pdev, thermal, @@ -898,9 +1034,6 @@ static int rockchip_thermal_probe(struct platform_device *pdev) dev_err(&pdev->dev, "failed to register sensor[%d] : error = %d\n", i, error); - for (j = 0; j < i; j++) - thermal_zone_of_sensor_unregister(&pdev->dev, - thermal->sensors[j].tzd); goto err_disable_pclk; } } @@ -912,7 +1045,7 @@ static int rockchip_thermal_probe(struct platform_device *pdev) if (error) { dev_err(&pdev->dev, "failed to request tsadc irq: %d\n", error); - goto err_unregister_sensor; + goto err_disable_pclk; } thermal->chip->control(thermal->regs, true); @@ -924,11 +1057,6 @@ static int rockchip_thermal_probe(struct platform_device *pdev) return 0; -err_unregister_sensor: - while (i--) - thermal_zone_of_sensor_unregister(&pdev->dev, - thermal->sensors[i].tzd); - err_disable_pclk: clk_disable_unprepare(thermal->pclk); err_disable_clk: @@ -946,7 +1074,6 @@ static int rockchip_thermal_remove(struct platform_device *pdev) struct rockchip_thermal_sensor *sensor = &thermal->sensors[i]; rockchip_thermal_toggle_sensor(sensor, false); - thermal_zone_of_sensor_unregister(&pdev->dev, sensor->tzd); } thermal->chip->control(thermal->regs, false); @@ -988,12 +1115,15 @@ static int __maybe_unused rockchip_thermal_resume(struct device *dev) return error; error = clk_enable(thermal->pclk); - if (error) + if (error) { + clk_disable(thermal->clk); return error; + } rockchip_thermal_reset_controller(thermal->reset); - thermal->chip->initialize(thermal->regs, thermal->tshut_polarity); + thermal->chip->initialize(thermal->grf, thermal->regs, + thermal->tshut_polarity); for (i = 0; i < thermal->chip->chn_num; i++) { int id = thermal->sensors[i].id; diff --git a/drivers/thermal/tango_thermal.c b/drivers/thermal/tango_thermal.c new file mode 100644 index 000000000000..70e0d9f406e9 --- /dev/null +++ b/drivers/thermal/tango_thermal.c @@ -0,0 +1,109 @@ +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/thermal.h> +#include <linux/platform_device.h> + +/* + * According to a data sheet draft, "this temperature sensor uses a bandgap + * type of circuit to compare a voltage which has a negative temperature + * coefficient with a voltage that is proportional to absolute temperature. + * A resistor bank allows 41 different temperature thresholds to be selected + * and the logic output will then indicate whether the actual die temperature + * lies above or below the selected threshold." + */ + +#define TEMPSI_CMD 0 +#define TEMPSI_RES 4 +#define TEMPSI_CFG 8 + +#define CMD_OFF 0 +#define CMD_ON 1 +#define CMD_READ 2 + +#define IDX_MIN 15 +#define IDX_MAX 40 + +struct tango_thermal_priv { + void __iomem *base; + int thresh_idx; +}; + +static bool temp_above_thresh(void __iomem *base, int thresh_idx) +{ + writel(CMD_READ | thresh_idx << 8, base + TEMPSI_CMD); + usleep_range(10, 20); + writel(CMD_READ | thresh_idx << 8, base + TEMPSI_CMD); + + return readl(base + TEMPSI_RES); +} + +static int tango_get_temp(void *arg, int *res) +{ + struct tango_thermal_priv *priv = arg; + int idx = priv->thresh_idx; + + if (temp_above_thresh(priv->base, idx)) { + /* Search upward by incrementing thresh_idx */ + while (idx < IDX_MAX && temp_above_thresh(priv->base, ++idx)) + cpu_relax(); + idx = idx - 1; /* always return lower bound */ + } else { + /* Search downward by decrementing thresh_idx */ + while (idx > IDX_MIN && !temp_above_thresh(priv->base, --idx)) + cpu_relax(); + } + + *res = (idx * 9 / 2 - 38) * 1000; /* millidegrees Celsius */ + priv->thresh_idx = idx; + + return 0; +} + +static const struct thermal_zone_of_device_ops ops = { + .get_temp = tango_get_temp, +}; + +static int tango_thermal_probe(struct platform_device *pdev) +{ + struct resource *res; + struct tango_thermal_priv *priv; + struct thermal_zone_device *tzdev; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + priv->thresh_idx = IDX_MIN; + writel(0, priv->base + TEMPSI_CFG); + writel(CMD_ON, priv->base + TEMPSI_CMD); + + tzdev = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, priv, &ops); + return PTR_ERR_OR_ZERO(tzdev); +} + +static const struct of_device_id tango_sensor_ids[] = { + { + .compatible = "sigma,smp8758-thermal", + }, + { /* sentinel */ } +}; + +static struct platform_driver tango_thermal_driver = { + .probe = tango_thermal_probe, + .driver = { + .name = "tango-thermal", + .of_match_table = tango_sensor_ids, + }, +}; + +module_platform_driver(tango_thermal_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Sigma Designs"); +MODULE_DESCRIPTION("Tango temperature sensor"); diff --git a/drivers/thermal/tegra/Kconfig b/drivers/thermal/tegra/Kconfig new file mode 100644 index 000000000000..cec586ec7e4b --- /dev/null +++ b/drivers/thermal/tegra/Kconfig @@ -0,0 +1,13 @@ +menu "NVIDIA Tegra thermal drivers" +depends on ARCH_TEGRA + +config TEGRA_SOCTHERM + tristate "Tegra SOCTHERM thermal management" + help + Enable this option for integrated thermal management support on NVIDIA + Tegra systems-on-chip. The driver supports four thermal zones + (CPU, GPU, MEM, PLLX). Cooling devices can be bound to the thermal + zones to manage temperatures. This option is also required for the + emergency thermal reset (thermtrip) feature to function. + +endmenu diff --git a/drivers/thermal/tegra/Makefile b/drivers/thermal/tegra/Makefile new file mode 100644 index 000000000000..1ce1af2cf0f5 --- /dev/null +++ b/drivers/thermal/tegra/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_TEGRA_SOCTHERM) += tegra-soctherm.o + +tegra-soctherm-y := soctherm.o soctherm-fuse.o +tegra-soctherm-$(CONFIG_ARCH_TEGRA_124_SOC) += tegra124-soctherm.o +tegra-soctherm-$(CONFIG_ARCH_TEGRA_132_SOC) += tegra132-soctherm.o +tegra-soctherm-$(CONFIG_ARCH_TEGRA_210_SOC) += tegra210-soctherm.o diff --git a/drivers/thermal/tegra/soctherm-fuse.c b/drivers/thermal/tegra/soctherm-fuse.c new file mode 100644 index 000000000000..29963180c453 --- /dev/null +++ b/drivers/thermal/tegra/soctherm-fuse.c @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2014-2016, NVIDIA CORPORATION. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/module.h> +#include <linux/platform_device.h> +#include <soc/tegra/fuse.h> + +#include "soctherm.h" + +#define NOMINAL_CALIB_FT 105 +#define NOMINAL_CALIB_CP 25 + +#define FUSE_TSENSOR_CALIB_CP_TS_BASE_MASK 0x1fff +#define FUSE_TSENSOR_CALIB_FT_TS_BASE_MASK (0x1fff << 13) +#define FUSE_TSENSOR_CALIB_FT_TS_BASE_SHIFT 13 + +#define FUSE_TSENSOR_COMMON 0x180 + +/* + * Tegra210: Layout of bits in FUSE_TSENSOR_COMMON: + * 3 2 1 0 + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | BASE_FT | BASE_CP | SHFT_FT | SHIFT_CP | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * Tegra12x, etc: + * In chips prior to Tegra210, this fuse was incorrectly sized as 26 bits, + * and didn't hold SHIFT_CP in [31:26]. Therefore these missing six bits + * were obtained via the FUSE_SPARE_REALIGNMENT_REG register [5:0]. + * + * FUSE_TSENSOR_COMMON: + * 3 2 1 0 + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |-----------| SHFT_FT | BASE_FT | BASE_CP | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * FUSE_SPARE_REALIGNMENT_REG: + * 3 2 1 0 + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |---------------------------------------------------| SHIFT_CP | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + +#define CALIB_COEFFICIENT 1000000LL + +/** + * div64_s64_precise() - wrapper for div64_s64() + * @a: the dividend + * @b: the divisor + * + * Implements division with fairly accurate rounding instead of truncation by + * shifting the dividend to the left by 16 so that the quotient has a + * much higher precision. + * + * Return: the quotient of a / b. + */ +static s64 div64_s64_precise(s64 a, s32 b) +{ + s64 r, al; + + /* Scale up for increased precision division */ + al = a << 16; + + r = div64_s64(al * 2 + 1, 2 * b); + return r >> 16; +} + +int tegra_calc_shared_calib(const struct tegra_soctherm_fuse *tfuse, + struct tsensor_shared_calib *shared) +{ + u32 val; + s32 shifted_cp, shifted_ft; + int err; + + err = tegra_fuse_readl(FUSE_TSENSOR_COMMON, &val); + if (err) + return err; + + shared->base_cp = (val & tfuse->fuse_base_cp_mask) >> + tfuse->fuse_base_cp_shift; + shared->base_ft = (val & tfuse->fuse_base_ft_mask) >> + tfuse->fuse_base_ft_shift; + + shifted_ft = (val & tfuse->fuse_shift_ft_mask) >> + tfuse->fuse_shift_ft_shift; + shifted_ft = sign_extend32(shifted_ft, 4); + + if (tfuse->fuse_spare_realignment) { + err = tegra_fuse_readl(tfuse->fuse_spare_realignment, &val); + if (err) + return err; + } + + shifted_cp = sign_extend32(val, 5); + + shared->actual_temp_cp = 2 * NOMINAL_CALIB_CP + shifted_cp; + shared->actual_temp_ft = 2 * NOMINAL_CALIB_FT + shifted_ft; + + return 0; +} + +int tegra_calc_tsensor_calib(const struct tegra_tsensor *sensor, + const struct tsensor_shared_calib *shared, + u32 *calibration) +{ + const struct tegra_tsensor_group *sensor_group; + u32 val, calib; + s32 actual_tsensor_ft, actual_tsensor_cp; + s32 delta_sens, delta_temp; + s32 mult, div; + s16 therma, thermb; + s64 temp; + int err; + + sensor_group = sensor->group; + + err = tegra_fuse_readl(sensor->calib_fuse_offset, &val); + if (err) + return err; + + actual_tsensor_cp = (shared->base_cp * 64) + sign_extend32(val, 12); + val = (val & FUSE_TSENSOR_CALIB_FT_TS_BASE_MASK) >> + FUSE_TSENSOR_CALIB_FT_TS_BASE_SHIFT; + actual_tsensor_ft = (shared->base_ft * 32) + sign_extend32(val, 12); + + delta_sens = actual_tsensor_ft - actual_tsensor_cp; + delta_temp = shared->actual_temp_ft - shared->actual_temp_cp; + + mult = sensor_group->pdiv * sensor->config->tsample_ate; + div = sensor->config->tsample * sensor_group->pdiv_ate; + + temp = (s64)delta_temp * (1LL << 13) * mult; + therma = div64_s64_precise(temp, (s64)delta_sens * div); + + temp = ((s64)actual_tsensor_ft * shared->actual_temp_cp) - + ((s64)actual_tsensor_cp * shared->actual_temp_ft); + thermb = div64_s64_precise(temp, delta_sens); + + temp = (s64)therma * sensor->fuse_corr_alpha; + therma = div64_s64_precise(temp, CALIB_COEFFICIENT); + + temp = (s64)thermb * sensor->fuse_corr_alpha + sensor->fuse_corr_beta; + thermb = div64_s64_precise(temp, CALIB_COEFFICIENT); + + calib = ((u16)therma << SENSOR_CONFIG2_THERMA_SHIFT) | + ((u16)thermb << SENSOR_CONFIG2_THERMB_SHIFT); + + *calibration = calib; + + return 0; +} + +MODULE_AUTHOR("Wei Ni <wni@nvidia.com>"); +MODULE_DESCRIPTION("Tegra SOCTHERM fuse management"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/thermal/tegra/soctherm.c b/drivers/thermal/tegra/soctherm.c new file mode 100644 index 000000000000..b8651726201e --- /dev/null +++ b/drivers/thermal/tegra/soctherm.c @@ -0,0 +1,685 @@ +/* + * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. + * + * Author: + * Mikko Perttunen <mperttunen@nvidia.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/debugfs.h> +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/reset.h> +#include <linux/thermal.h> + +#include <dt-bindings/thermal/tegra124-soctherm.h> + +#include "soctherm.h" + +#define SENSOR_CONFIG0 0 +#define SENSOR_CONFIG0_STOP BIT(0) +#define SENSOR_CONFIG0_CPTR_OVER BIT(2) +#define SENSOR_CONFIG0_OVER BIT(3) +#define SENSOR_CONFIG0_TCALC_OVER BIT(4) +#define SENSOR_CONFIG0_TALL_MASK (0xfffff << 8) +#define SENSOR_CONFIG0_TALL_SHIFT 8 + +#define SENSOR_CONFIG1 4 +#define SENSOR_CONFIG1_TSAMPLE_MASK 0x3ff +#define SENSOR_CONFIG1_TSAMPLE_SHIFT 0 +#define SENSOR_CONFIG1_TIDDQ_EN_MASK (0x3f << 15) +#define SENSOR_CONFIG1_TIDDQ_EN_SHIFT 15 +#define SENSOR_CONFIG1_TEN_COUNT_MASK (0x3f << 24) +#define SENSOR_CONFIG1_TEN_COUNT_SHIFT 24 +#define SENSOR_CONFIG1_TEMP_ENABLE BIT(31) + +/* + * SENSOR_CONFIG2 is defined in soctherm.h + * because, it will be used by tegra_soctherm_fuse.c + */ + +#define SENSOR_STATUS0 0xc +#define SENSOR_STATUS0_VALID_MASK BIT(31) +#define SENSOR_STATUS0_CAPTURE_MASK 0xffff + +#define SENSOR_STATUS1 0x10 +#define SENSOR_STATUS1_TEMP_VALID_MASK BIT(31) +#define SENSOR_STATUS1_TEMP_MASK 0xffff + +#define READBACK_VALUE_MASK 0xff00 +#define READBACK_VALUE_SHIFT 8 +#define READBACK_ADD_HALF BIT(7) +#define READBACK_NEGATE BIT(0) + +/* get val from register(r) mask bits(m) */ +#define REG_GET_MASK(r, m) (((r) & (m)) >> (ffs(m) - 1)) +/* set val(v) to mask bits(m) of register(r) */ +#define REG_SET_MASK(r, m, v) (((r) & ~(m)) | \ + (((v) & (m >> (ffs(m) - 1))) << (ffs(m) - 1))) + +static const int min_low_temp = -127000; +static const int max_high_temp = 127000; + +struct tegra_thermctl_zone { + void __iomem *reg; + struct device *dev; + struct thermal_zone_device *tz; + const struct tegra_tsensor_group *sg; +}; + +struct tegra_soctherm { + struct reset_control *reset; + struct clk *clock_tsensor; + struct clk *clock_soctherm; + void __iomem *regs; + struct thermal_zone_device **thermctl_tzs; + + u32 *calib; + struct tegra_soctherm_soc *soc; + + struct dentry *debugfs_dir; +}; + +static void enable_tsensor(struct tegra_soctherm *tegra, unsigned int i) +{ + const struct tegra_tsensor *sensor = &tegra->soc->tsensors[i]; + void __iomem *base = tegra->regs + sensor->base; + unsigned int val; + + val = sensor->config->tall << SENSOR_CONFIG0_TALL_SHIFT; + writel(val, base + SENSOR_CONFIG0); + + val = (sensor->config->tsample - 1) << SENSOR_CONFIG1_TSAMPLE_SHIFT; + val |= sensor->config->tiddq_en << SENSOR_CONFIG1_TIDDQ_EN_SHIFT; + val |= sensor->config->ten_count << SENSOR_CONFIG1_TEN_COUNT_SHIFT; + val |= SENSOR_CONFIG1_TEMP_ENABLE; + writel(val, base + SENSOR_CONFIG1); + + writel(tegra->calib[i], base + SENSOR_CONFIG2); +} + +/* + * Translate from soctherm readback format to millicelsius. + * The soctherm readback format in bits is as follows: + * TTTTTTTT H______N + * where T's contain the temperature in Celsius, + * H denotes an addition of 0.5 Celsius and N denotes negation + * of the final value. + */ +static int translate_temp(u16 val) +{ + int t; + + t = ((val & READBACK_VALUE_MASK) >> READBACK_VALUE_SHIFT) * 1000; + if (val & READBACK_ADD_HALF) + t += 500; + if (val & READBACK_NEGATE) + t *= -1; + + return t; +} + +static int tegra_thermctl_get_temp(void *data, int *out_temp) +{ + struct tegra_thermctl_zone *zone = data; + u32 val; + + val = readl(zone->reg); + val = REG_GET_MASK(val, zone->sg->sensor_temp_mask); + *out_temp = translate_temp(val); + + return 0; +} + +static int +thermtrip_program(struct device *dev, const struct tegra_tsensor_group *sg, + int trip_temp); + +static int tegra_thermctl_set_trip_temp(void *data, int trip, int temp) +{ + struct tegra_thermctl_zone *zone = data; + struct thermal_zone_device *tz = zone->tz; + const struct tegra_tsensor_group *sg = zone->sg; + struct device *dev = zone->dev; + enum thermal_trip_type type; + int ret; + + if (!tz) + return -EINVAL; + + ret = tz->ops->get_trip_type(tz, trip, &type); + if (ret) + return ret; + + if (type != THERMAL_TRIP_CRITICAL) + return 0; + + return thermtrip_program(dev, sg, temp); +} + +static const struct thermal_zone_of_device_ops tegra_of_thermal_ops = { + .get_temp = tegra_thermctl_get_temp, + .set_trip_temp = tegra_thermctl_set_trip_temp, +}; + +/** + * enforce_temp_range() - check and enforce temperature range [min, max] + * @trip_temp: the trip temperature to check + * + * Checks and enforces the permitted temperature range that SOC_THERM + * HW can support This is + * done while taking care of precision. + * + * Return: The precision adjusted capped temperature in millicelsius. + */ +static int enforce_temp_range(struct device *dev, int trip_temp) +{ + int temp; + + temp = clamp_val(trip_temp, min_low_temp, max_high_temp); + if (temp != trip_temp) + dev_info(dev, "soctherm: trip temperature %d forced to %d\n", + trip_temp, temp); + return temp; +} + +/** + * thermtrip_program() - Configures the hardware to shut down the + * system if a given sensor group reaches a given temperature + * @dev: ptr to the struct device for the SOC_THERM IP block + * @sg: pointer to the sensor group to set the thermtrip temperature for + * @trip_temp: the temperature in millicelsius to trigger the thermal trip at + * + * Sets the thermal trip threshold of the given sensor group to be the + * @trip_temp. If this threshold is crossed, the hardware will shut + * down. + * + * Note that, although @trip_temp is specified in millicelsius, the + * hardware is programmed in degrees Celsius. + * + * Return: 0 upon success, or %-EINVAL upon failure. + */ +static int thermtrip_program(struct device *dev, + const struct tegra_tsensor_group *sg, + int trip_temp) +{ + struct tegra_soctherm *ts = dev_get_drvdata(dev); + int temp; + u32 r; + + if (!sg || !sg->thermtrip_threshold_mask) + return -EINVAL; + + temp = enforce_temp_range(dev, trip_temp) / ts->soc->thresh_grain; + + r = readl(ts->regs + THERMCTL_THERMTRIP_CTL); + r = REG_SET_MASK(r, sg->thermtrip_threshold_mask, temp); + r = REG_SET_MASK(r, sg->thermtrip_enable_mask, 1); + r = REG_SET_MASK(r, sg->thermtrip_any_en_mask, 0); + writel(r, ts->regs + THERMCTL_THERMTRIP_CTL); + + return 0; +} + +/** + * tegra_soctherm_set_hwtrips() - set HW trip point from DT data + * @dev: struct device * of the SOC_THERM instance + * + * Configure the SOC_THERM HW trip points, setting "THERMTRIP" + * trip points , using "critical" type trip_temp from thermal + * zone. + * After they have been configured, THERMTRIP will take action + * when the configured SoC thermal sensor group reaches a + * certain temperature. + * + * Return: 0 upon success, or a negative error code on failure. + * "Success" does not mean that trips was enabled; it could also + * mean that no node was found in DT. + * THERMTRIP has been enabled successfully when a message similar to + * this one appears on the serial console: + * "thermtrip: will shut down when sensor group XXX reaches YYYYYY mC" + */ +static int tegra_soctherm_set_hwtrips(struct device *dev, + const struct tegra_tsensor_group *sg, + struct thermal_zone_device *tz) +{ + int temperature; + int ret; + + ret = tz->ops->get_crit_temp(tz, &temperature); + if (ret) { + dev_warn(dev, "thermtrip: %s: missing critical temperature\n", + sg->name); + return ret; + } + + ret = thermtrip_program(dev, sg, temperature); + if (ret) { + dev_err(dev, "thermtrip: %s: error during enable\n", + sg->name); + return ret; + } + + dev_info(dev, + "thermtrip: will shut down when %s reaches %d mC\n", + sg->name, temperature); + + return 0; +} + +#ifdef CONFIG_DEBUG_FS +static int regs_show(struct seq_file *s, void *data) +{ + struct platform_device *pdev = s->private; + struct tegra_soctherm *ts = platform_get_drvdata(pdev); + const struct tegra_tsensor *tsensors = ts->soc->tsensors; + const struct tegra_tsensor_group **ttgs = ts->soc->ttgs; + u32 r, state; + int i; + + seq_puts(s, "-----TSENSE (convert HW)-----\n"); + + for (i = 0; i < ts->soc->num_tsensors; i++) { + r = readl(ts->regs + tsensors[i].base + SENSOR_CONFIG1); + state = REG_GET_MASK(r, SENSOR_CONFIG1_TEMP_ENABLE); + + seq_printf(s, "%s: ", tsensors[i].name); + seq_printf(s, "En(%d) ", state); + + if (!state) { + seq_puts(s, "\n"); + continue; + } + + state = REG_GET_MASK(r, SENSOR_CONFIG1_TIDDQ_EN_MASK); + seq_printf(s, "tiddq(%d) ", state); + state = REG_GET_MASK(r, SENSOR_CONFIG1_TEN_COUNT_MASK); + seq_printf(s, "ten_count(%d) ", state); + state = REG_GET_MASK(r, SENSOR_CONFIG1_TSAMPLE_MASK); + seq_printf(s, "tsample(%d) ", state + 1); + + r = readl(ts->regs + tsensors[i].base + SENSOR_STATUS1); + state = REG_GET_MASK(r, SENSOR_STATUS1_TEMP_VALID_MASK); + seq_printf(s, "Temp(%d/", state); + state = REG_GET_MASK(r, SENSOR_STATUS1_TEMP_MASK); + seq_printf(s, "%d) ", translate_temp(state)); + + r = readl(ts->regs + tsensors[i].base + SENSOR_STATUS0); + state = REG_GET_MASK(r, SENSOR_STATUS0_VALID_MASK); + seq_printf(s, "Capture(%d/", state); + state = REG_GET_MASK(r, SENSOR_STATUS0_CAPTURE_MASK); + seq_printf(s, "%d) ", state); + + r = readl(ts->regs + tsensors[i].base + SENSOR_CONFIG0); + state = REG_GET_MASK(r, SENSOR_CONFIG0_STOP); + seq_printf(s, "Stop(%d) ", state); + state = REG_GET_MASK(r, SENSOR_CONFIG0_TALL_MASK); + seq_printf(s, "Tall(%d) ", state); + state = REG_GET_MASK(r, SENSOR_CONFIG0_TCALC_OVER); + seq_printf(s, "Over(%d/", state); + state = REG_GET_MASK(r, SENSOR_CONFIG0_OVER); + seq_printf(s, "%d/", state); + state = REG_GET_MASK(r, SENSOR_CONFIG0_CPTR_OVER); + seq_printf(s, "%d) ", state); + + r = readl(ts->regs + tsensors[i].base + SENSOR_CONFIG2); + state = REG_GET_MASK(r, SENSOR_CONFIG2_THERMA_MASK); + seq_printf(s, "Therm_A/B(%d/", state); + state = REG_GET_MASK(r, SENSOR_CONFIG2_THERMB_MASK); + seq_printf(s, "%d)\n", (s16)state); + } + + r = readl(ts->regs + SENSOR_PDIV); + seq_printf(s, "PDIV: 0x%x\n", r); + + r = readl(ts->regs + SENSOR_HOTSPOT_OFF); + seq_printf(s, "HOTSPOT: 0x%x\n", r); + + seq_puts(s, "\n"); + seq_puts(s, "-----SOC_THERM-----\n"); + + r = readl(ts->regs + SENSOR_TEMP1); + state = REG_GET_MASK(r, SENSOR_TEMP1_CPU_TEMP_MASK); + seq_printf(s, "Temperatures: CPU(%d) ", translate_temp(state)); + state = REG_GET_MASK(r, SENSOR_TEMP1_GPU_TEMP_MASK); + seq_printf(s, " GPU(%d) ", translate_temp(state)); + r = readl(ts->regs + SENSOR_TEMP2); + state = REG_GET_MASK(r, SENSOR_TEMP2_PLLX_TEMP_MASK); + seq_printf(s, " PLLX(%d) ", translate_temp(state)); + state = REG_GET_MASK(r, SENSOR_TEMP2_MEM_TEMP_MASK); + seq_printf(s, " MEM(%d)\n", translate_temp(state)); + + r = readl(ts->regs + THERMCTL_THERMTRIP_CTL); + state = REG_GET_MASK(r, ttgs[0]->thermtrip_any_en_mask); + seq_printf(s, "Thermtrip Any En(%d)\n", state); + for (i = 0; i < ts->soc->num_ttgs; i++) { + state = REG_GET_MASK(r, ttgs[i]->thermtrip_enable_mask); + seq_printf(s, " %s En(%d) ", ttgs[i]->name, state); + state = REG_GET_MASK(r, ttgs[i]->thermtrip_threshold_mask); + state *= ts->soc->thresh_grain; + seq_printf(s, "Thresh(%d)\n", state); + } + + return 0; +} + +static int regs_open(struct inode *inode, struct file *file) +{ + return single_open(file, regs_show, inode->i_private); +} + +static const struct file_operations regs_fops = { + .open = regs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void soctherm_debug_init(struct platform_device *pdev) +{ + struct tegra_soctherm *tegra = platform_get_drvdata(pdev); + struct dentry *root, *file; + + root = debugfs_create_dir("soctherm", NULL); + if (!root) { + dev_err(&pdev->dev, "failed to create debugfs directory\n"); + return; + } + + tegra->debugfs_dir = root; + + file = debugfs_create_file("reg_contents", 0644, root, + pdev, ®s_fops); + if (!file) { + dev_err(&pdev->dev, "failed to create debugfs file\n"); + debugfs_remove_recursive(tegra->debugfs_dir); + tegra->debugfs_dir = NULL; + } +} +#else +static inline void soctherm_debug_init(struct platform_device *pdev) {} +#endif + +static int soctherm_clk_enable(struct platform_device *pdev, bool enable) +{ + struct tegra_soctherm *tegra = platform_get_drvdata(pdev); + int err; + + if (!tegra->clock_soctherm || !tegra->clock_tsensor) + return -EINVAL; + + reset_control_assert(tegra->reset); + + if (enable) { + err = clk_prepare_enable(tegra->clock_soctherm); + if (err) { + reset_control_deassert(tegra->reset); + return err; + } + + err = clk_prepare_enable(tegra->clock_tsensor); + if (err) { + clk_disable_unprepare(tegra->clock_soctherm); + reset_control_deassert(tegra->reset); + return err; + } + } else { + clk_disable_unprepare(tegra->clock_tsensor); + clk_disable_unprepare(tegra->clock_soctherm); + } + + reset_control_deassert(tegra->reset); + + return 0; +} + +static void soctherm_init(struct platform_device *pdev) +{ + struct tegra_soctherm *tegra = platform_get_drvdata(pdev); + const struct tegra_tsensor_group **ttgs = tegra->soc->ttgs; + int i; + u32 pdiv, hotspot; + + /* Initialize raw sensors */ + for (i = 0; i < tegra->soc->num_tsensors; ++i) + enable_tsensor(tegra, i); + + /* program pdiv and hotspot offsets per THERM */ + pdiv = readl(tegra->regs + SENSOR_PDIV); + hotspot = readl(tegra->regs + SENSOR_HOTSPOT_OFF); + for (i = 0; i < tegra->soc->num_ttgs; ++i) { + pdiv = REG_SET_MASK(pdiv, ttgs[i]->pdiv_mask, + ttgs[i]->pdiv); + /* hotspot offset from PLLX, doesn't need to configure PLLX */ + if (ttgs[i]->id == TEGRA124_SOCTHERM_SENSOR_PLLX) + continue; + hotspot = REG_SET_MASK(hotspot, + ttgs[i]->pllx_hotspot_mask, + ttgs[i]->pllx_hotspot_diff); + } + writel(pdiv, tegra->regs + SENSOR_PDIV); + writel(hotspot, tegra->regs + SENSOR_HOTSPOT_OFF); +} + +static const struct of_device_id tegra_soctherm_of_match[] = { +#ifdef CONFIG_ARCH_TEGRA_124_SOC + { + .compatible = "nvidia,tegra124-soctherm", + .data = &tegra124_soctherm, + }, +#endif +#ifdef CONFIG_ARCH_TEGRA_132_SOC + { + .compatible = "nvidia,tegra132-soctherm", + .data = &tegra132_soctherm, + }, +#endif +#ifdef CONFIG_ARCH_TEGRA_210_SOC + { + .compatible = "nvidia,tegra210-soctherm", + .data = &tegra210_soctherm, + }, +#endif + { }, +}; +MODULE_DEVICE_TABLE(of, tegra_soctherm_of_match); + +static int tegra_soctherm_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + struct tegra_soctherm *tegra; + struct thermal_zone_device *z; + struct tsensor_shared_calib shared_calib; + struct resource *res; + struct tegra_soctherm_soc *soc; + unsigned int i; + int err; + + match = of_match_node(tegra_soctherm_of_match, pdev->dev.of_node); + if (!match) + return -ENODEV; + + soc = (struct tegra_soctherm_soc *)match->data; + if (soc->num_ttgs > TEGRA124_SOCTHERM_SENSOR_NUM) + return -EINVAL; + + tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL); + if (!tegra) + return -ENOMEM; + + dev_set_drvdata(&pdev->dev, tegra); + + tegra->soc = soc; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + tegra->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(tegra->regs)) + return PTR_ERR(tegra->regs); + + tegra->reset = devm_reset_control_get(&pdev->dev, "soctherm"); + if (IS_ERR(tegra->reset)) { + dev_err(&pdev->dev, "can't get soctherm reset\n"); + return PTR_ERR(tegra->reset); + } + + tegra->clock_tsensor = devm_clk_get(&pdev->dev, "tsensor"); + if (IS_ERR(tegra->clock_tsensor)) { + dev_err(&pdev->dev, "can't get tsensor clock\n"); + return PTR_ERR(tegra->clock_tsensor); + } + + tegra->clock_soctherm = devm_clk_get(&pdev->dev, "soctherm"); + if (IS_ERR(tegra->clock_soctherm)) { + dev_err(&pdev->dev, "can't get soctherm clock\n"); + return PTR_ERR(tegra->clock_soctherm); + } + + tegra->calib = devm_kzalloc(&pdev->dev, + sizeof(u32) * soc->num_tsensors, + GFP_KERNEL); + if (!tegra->calib) + return -ENOMEM; + + /* calculate shared calibration data */ + err = tegra_calc_shared_calib(soc->tfuse, &shared_calib); + if (err) + return err; + + /* calculate tsensor calibaration data */ + for (i = 0; i < soc->num_tsensors; ++i) { + err = tegra_calc_tsensor_calib(&soc->tsensors[i], + &shared_calib, + &tegra->calib[i]); + if (err) + return err; + } + + tegra->thermctl_tzs = devm_kzalloc(&pdev->dev, + sizeof(*z) * soc->num_ttgs, + GFP_KERNEL); + if (!tegra->thermctl_tzs) + return -ENOMEM; + + err = soctherm_clk_enable(pdev, true); + if (err) + return err; + + soctherm_init(pdev); + + for (i = 0; i < soc->num_ttgs; ++i) { + struct tegra_thermctl_zone *zone = + devm_kzalloc(&pdev->dev, sizeof(*zone), GFP_KERNEL); + if (!zone) { + err = -ENOMEM; + goto disable_clocks; + } + + zone->reg = tegra->regs + soc->ttgs[i]->sensor_temp_offset; + zone->dev = &pdev->dev; + zone->sg = soc->ttgs[i]; + + z = devm_thermal_zone_of_sensor_register(&pdev->dev, + soc->ttgs[i]->id, zone, + &tegra_of_thermal_ops); + if (IS_ERR(z)) { + err = PTR_ERR(z); + dev_err(&pdev->dev, "failed to register sensor: %d\n", + err); + goto disable_clocks; + } + + zone->tz = z; + tegra->thermctl_tzs[soc->ttgs[i]->id] = z; + + /* Configure hw trip points */ + tegra_soctherm_set_hwtrips(&pdev->dev, soc->ttgs[i], z); + } + + soctherm_debug_init(pdev); + + return 0; + +disable_clocks: + soctherm_clk_enable(pdev, false); + + return err; +} + +static int tegra_soctherm_remove(struct platform_device *pdev) +{ + struct tegra_soctherm *tegra = platform_get_drvdata(pdev); + + debugfs_remove_recursive(tegra->debugfs_dir); + + soctherm_clk_enable(pdev, false); + + return 0; +} + +static int __maybe_unused soctherm_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + + soctherm_clk_enable(pdev, false); + + return 0; +} + +static int __maybe_unused soctherm_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct tegra_soctherm *tegra = platform_get_drvdata(pdev); + struct tegra_soctherm_soc *soc = tegra->soc; + int err, i; + + err = soctherm_clk_enable(pdev, true); + if (err) { + dev_err(&pdev->dev, + "Resume failed: enable clocks failed\n"); + return err; + } + + soctherm_init(pdev); + + for (i = 0; i < soc->num_ttgs; ++i) { + struct thermal_zone_device *tz; + + tz = tegra->thermctl_tzs[soc->ttgs[i]->id]; + tegra_soctherm_set_hwtrips(dev, soc->ttgs[i], tz); + } + + return 0; +} + +static SIMPLE_DEV_PM_OPS(tegra_soctherm_pm, soctherm_suspend, soctherm_resume); + +static struct platform_driver tegra_soctherm_driver = { + .probe = tegra_soctherm_probe, + .remove = tegra_soctherm_remove, + .driver = { + .name = "tegra_soctherm", + .pm = &tegra_soctherm_pm, + .of_match_table = tegra_soctherm_of_match, + }, +}; +module_platform_driver(tegra_soctherm_driver); + +MODULE_AUTHOR("Mikko Perttunen <mperttunen@nvidia.com>"); +MODULE_DESCRIPTION("NVIDIA Tegra SOCTHERM thermal management driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/thermal/tegra/soctherm.h b/drivers/thermal/tegra/soctherm.h new file mode 100644 index 000000000000..28e18ec4b4c3 --- /dev/null +++ b/drivers/thermal/tegra/soctherm.h @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2014-2016, NVIDIA CORPORATION. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#ifndef __DRIVERS_THERMAL_TEGRA_SOCTHERM_H +#define __DRIVERS_THERMAL_TEGRA_SOCTHERM_H + +#define SENSOR_CONFIG2 8 +#define SENSOR_CONFIG2_THERMA_MASK (0xffff << 16) +#define SENSOR_CONFIG2_THERMA_SHIFT 16 +#define SENSOR_CONFIG2_THERMB_MASK 0xffff +#define SENSOR_CONFIG2_THERMB_SHIFT 0 + +#define THERMCTL_THERMTRIP_CTL 0x80 +/* BITs are defined in device file */ + +#define SENSOR_PDIV 0x1c0 +#define SENSOR_PDIV_CPU_MASK (0xf << 12) +#define SENSOR_PDIV_GPU_MASK (0xf << 8) +#define SENSOR_PDIV_MEM_MASK (0xf << 4) +#define SENSOR_PDIV_PLLX_MASK (0xf << 0) + +#define SENSOR_HOTSPOT_OFF 0x1c4 +#define SENSOR_HOTSPOT_CPU_MASK (0xff << 16) +#define SENSOR_HOTSPOT_GPU_MASK (0xff << 8) +#define SENSOR_HOTSPOT_MEM_MASK (0xff << 0) + +#define SENSOR_TEMP1 0x1c8 +#define SENSOR_TEMP1_CPU_TEMP_MASK (0xffff << 16) +#define SENSOR_TEMP1_GPU_TEMP_MASK 0xffff +#define SENSOR_TEMP2 0x1cc +#define SENSOR_TEMP2_MEM_TEMP_MASK (0xffff << 16) +#define SENSOR_TEMP2_PLLX_TEMP_MASK 0xffff + +/** + * struct tegra_tsensor_group - SOC_THERM sensor group data + * @name: short name of the temperature sensor group + * @id: numeric ID of the temperature sensor group + * @sensor_temp_offset: offset of the SENSOR_TEMP* register + * @sensor_temp_mask: bit mask for this sensor group in SENSOR_TEMP* register + * @pdiv: the sensor count post-divider to use during runtime + * @pdiv_ate: the sensor count post-divider used during automated test + * @pdiv_mask: register bitfield mask for the PDIV field for this sensor + * @pllx_hotspot_diff: hotspot offset from the PLLX sensor, must be 0 for + PLLX sensor group + * @pllx_hotspot_mask: register bitfield mask for the HOTSPOT field + */ +struct tegra_tsensor_group { + const char *name; + u8 id; + u16 sensor_temp_offset; + u32 sensor_temp_mask; + u32 pdiv, pdiv_ate, pdiv_mask; + u32 pllx_hotspot_diff, pllx_hotspot_mask; + u32 thermtrip_enable_mask; + u32 thermtrip_any_en_mask; + u32 thermtrip_threshold_mask; +}; + +struct tegra_tsensor_configuration { + u32 tall, tiddq_en, ten_count, pdiv, pdiv_ate, tsample, tsample_ate; +}; + +struct tegra_tsensor { + const char *name; + const u32 base; + const struct tegra_tsensor_configuration *config; + const u32 calib_fuse_offset; + /* + * Correction values used to modify values read from + * calibration fuses + */ + const s32 fuse_corr_alpha, fuse_corr_beta; + const struct tegra_tsensor_group *group; +}; + +struct tegra_soctherm_fuse { + u32 fuse_base_cp_mask, fuse_base_cp_shift; + u32 fuse_base_ft_mask, fuse_base_ft_shift; + u32 fuse_shift_ft_mask, fuse_shift_ft_shift; + u32 fuse_spare_realignment; +}; + +struct tsensor_shared_calib { + u32 base_cp, base_ft; + u32 actual_temp_cp, actual_temp_ft; +}; + +struct tegra_soctherm_soc { + const struct tegra_tsensor *tsensors; + const unsigned int num_tsensors; + const struct tegra_tsensor_group **ttgs; + const unsigned int num_ttgs; + const struct tegra_soctherm_fuse *tfuse; + const int thresh_grain; +}; + +int tegra_calc_shared_calib(const struct tegra_soctherm_fuse *tfuse, + struct tsensor_shared_calib *shared); +int tegra_calc_tsensor_calib(const struct tegra_tsensor *sensor, + const struct tsensor_shared_calib *shared, + u32 *calib); + +#ifdef CONFIG_ARCH_TEGRA_124_SOC +extern const struct tegra_soctherm_soc tegra124_soctherm; +#endif + +#ifdef CONFIG_ARCH_TEGRA_132_SOC +extern const struct tegra_soctherm_soc tegra132_soctherm; +#endif + +#ifdef CONFIG_ARCH_TEGRA_210_SOC +extern const struct tegra_soctherm_soc tegra210_soctherm; +#endif + +#endif + diff --git a/drivers/thermal/tegra/tegra124-soctherm.c b/drivers/thermal/tegra/tegra124-soctherm.c new file mode 100644 index 000000000000..beb9d36b9c8a --- /dev/null +++ b/drivers/thermal/tegra/tegra124-soctherm.c @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2014-2016, NVIDIA CORPORATION. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/module.h> +#include <linux/platform_device.h> + +#include <dt-bindings/thermal/tegra124-soctherm.h> + +#include "soctherm.h" + +#define TEGRA124_THERMTRIP_ANY_EN_MASK (0x1 << 28) +#define TEGRA124_THERMTRIP_MEM_EN_MASK (0x1 << 27) +#define TEGRA124_THERMTRIP_GPU_EN_MASK (0x1 << 26) +#define TEGRA124_THERMTRIP_CPU_EN_MASK (0x1 << 25) +#define TEGRA124_THERMTRIP_TSENSE_EN_MASK (0x1 << 24) +#define TEGRA124_THERMTRIP_GPUMEM_THRESH_MASK (0xff << 16) +#define TEGRA124_THERMTRIP_CPU_THRESH_MASK (0xff << 8) +#define TEGRA124_THERMTRIP_TSENSE_THRESH_MASK 0xff + +#define TEGRA124_THRESH_GRAIN 1000 + +static const struct tegra_tsensor_configuration tegra124_tsensor_config = { + .tall = 16300, + .tiddq_en = 1, + .ten_count = 1, + .tsample = 120, + .tsample_ate = 480, +}; + +static const struct tegra_tsensor_group tegra124_tsensor_group_cpu = { + .id = TEGRA124_SOCTHERM_SENSOR_CPU, + .name = "cpu", + .sensor_temp_offset = SENSOR_TEMP1, + .sensor_temp_mask = SENSOR_TEMP1_CPU_TEMP_MASK, + .pdiv = 8, + .pdiv_ate = 8, + .pdiv_mask = SENSOR_PDIV_CPU_MASK, + .pllx_hotspot_diff = 10, + .pllx_hotspot_mask = SENSOR_HOTSPOT_CPU_MASK, + .thermtrip_any_en_mask = TEGRA124_THERMTRIP_ANY_EN_MASK, + .thermtrip_enable_mask = TEGRA124_THERMTRIP_CPU_EN_MASK, + .thermtrip_threshold_mask = TEGRA124_THERMTRIP_CPU_THRESH_MASK, +}; + +static const struct tegra_tsensor_group tegra124_tsensor_group_gpu = { + .id = TEGRA124_SOCTHERM_SENSOR_GPU, + .name = "gpu", + .sensor_temp_offset = SENSOR_TEMP1, + .sensor_temp_mask = SENSOR_TEMP1_GPU_TEMP_MASK, + .pdiv = 8, + .pdiv_ate = 8, + .pdiv_mask = SENSOR_PDIV_GPU_MASK, + .pllx_hotspot_diff = 5, + .pllx_hotspot_mask = SENSOR_HOTSPOT_GPU_MASK, + .thermtrip_any_en_mask = TEGRA124_THERMTRIP_ANY_EN_MASK, + .thermtrip_enable_mask = TEGRA124_THERMTRIP_GPU_EN_MASK, + .thermtrip_threshold_mask = TEGRA124_THERMTRIP_GPUMEM_THRESH_MASK, +}; + +static const struct tegra_tsensor_group tegra124_tsensor_group_pll = { + .id = TEGRA124_SOCTHERM_SENSOR_PLLX, + .name = "pll", + .sensor_temp_offset = SENSOR_TEMP2, + .sensor_temp_mask = SENSOR_TEMP2_PLLX_TEMP_MASK, + .pdiv = 8, + .pdiv_ate = 8, + .pdiv_mask = SENSOR_PDIV_PLLX_MASK, + .thermtrip_any_en_mask = TEGRA124_THERMTRIP_ANY_EN_MASK, + .thermtrip_enable_mask = TEGRA124_THERMTRIP_TSENSE_EN_MASK, + .thermtrip_threshold_mask = TEGRA124_THERMTRIP_TSENSE_THRESH_MASK, +}; + +static const struct tegra_tsensor_group tegra124_tsensor_group_mem = { + .id = TEGRA124_SOCTHERM_SENSOR_MEM, + .name = "mem", + .sensor_temp_offset = SENSOR_TEMP2, + .sensor_temp_mask = SENSOR_TEMP2_MEM_TEMP_MASK, + .pdiv = 8, + .pdiv_ate = 8, + .pdiv_mask = SENSOR_PDIV_MEM_MASK, + .pllx_hotspot_diff = 0, + .pllx_hotspot_mask = SENSOR_HOTSPOT_MEM_MASK, + .thermtrip_any_en_mask = TEGRA124_THERMTRIP_ANY_EN_MASK, + .thermtrip_enable_mask = TEGRA124_THERMTRIP_MEM_EN_MASK, + .thermtrip_threshold_mask = TEGRA124_THERMTRIP_GPUMEM_THRESH_MASK, +}; + +static const struct tegra_tsensor_group *tegra124_tsensor_groups[] = { + &tegra124_tsensor_group_cpu, + &tegra124_tsensor_group_gpu, + &tegra124_tsensor_group_pll, + &tegra124_tsensor_group_mem, +}; + +static const struct tegra_tsensor tegra124_tsensors[] = { + { + .name = "cpu0", + .base = 0xc0, + .config = &tegra124_tsensor_config, + .calib_fuse_offset = 0x098, + .fuse_corr_alpha = 1135400, + .fuse_corr_beta = -6266900, + .group = &tegra124_tsensor_group_cpu, + }, { + .name = "cpu1", + .base = 0xe0, + .config = &tegra124_tsensor_config, + .calib_fuse_offset = 0x084, + .fuse_corr_alpha = 1122220, + .fuse_corr_beta = -5700700, + .group = &tegra124_tsensor_group_cpu, + }, { + .name = "cpu2", + .base = 0x100, + .config = &tegra124_tsensor_config, + .calib_fuse_offset = 0x088, + .fuse_corr_alpha = 1127000, + .fuse_corr_beta = -6768200, + .group = &tegra124_tsensor_group_cpu, + }, { + .name = "cpu3", + .base = 0x120, + .config = &tegra124_tsensor_config, + .calib_fuse_offset = 0x12c, + .fuse_corr_alpha = 1110900, + .fuse_corr_beta = -6232000, + .group = &tegra124_tsensor_group_cpu, + }, { + .name = "mem0", + .base = 0x140, + .config = &tegra124_tsensor_config, + .calib_fuse_offset = 0x158, + .fuse_corr_alpha = 1122300, + .fuse_corr_beta = -5936400, + .group = &tegra124_tsensor_group_mem, + }, { + .name = "mem1", + .base = 0x160, + .config = &tegra124_tsensor_config, + .calib_fuse_offset = 0x15c, + .fuse_corr_alpha = 1145700, + .fuse_corr_beta = -7124600, + .group = &tegra124_tsensor_group_mem, + }, { + .name = "gpu", + .base = 0x180, + .config = &tegra124_tsensor_config, + .calib_fuse_offset = 0x154, + .fuse_corr_alpha = 1120100, + .fuse_corr_beta = -6000500, + .group = &tegra124_tsensor_group_gpu, + }, { + .name = "pllx", + .base = 0x1a0, + .config = &tegra124_tsensor_config, + .calib_fuse_offset = 0x160, + .fuse_corr_alpha = 1106500, + .fuse_corr_beta = -6729300, + .group = &tegra124_tsensor_group_pll, + }, +}; + +/* + * Mask/shift bits in FUSE_TSENSOR_COMMON and + * FUSE_TSENSOR_COMMON, which are described in + * tegra_soctherm_fuse.c + */ +static const struct tegra_soctherm_fuse tegra124_soctherm_fuse = { + .fuse_base_cp_mask = 0x3ff, + .fuse_base_cp_shift = 0, + .fuse_base_ft_mask = 0x7ff << 10, + .fuse_base_ft_shift = 10, + .fuse_shift_ft_mask = 0x1f << 21, + .fuse_shift_ft_shift = 21, + .fuse_spare_realignment = 0x1fc, +}; + +const struct tegra_soctherm_soc tegra124_soctherm = { + .tsensors = tegra124_tsensors, + .num_tsensors = ARRAY_SIZE(tegra124_tsensors), + .ttgs = tegra124_tsensor_groups, + .num_ttgs = ARRAY_SIZE(tegra124_tsensor_groups), + .tfuse = &tegra124_soctherm_fuse, + .thresh_grain = TEGRA124_THRESH_GRAIN, +}; diff --git a/drivers/thermal/tegra/tegra132-soctherm.c b/drivers/thermal/tegra/tegra132-soctherm.c new file mode 100644 index 000000000000..e2aa84e1b307 --- /dev/null +++ b/drivers/thermal/tegra/tegra132-soctherm.c @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2014-2016, NVIDIA CORPORATION. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/module.h> +#include <linux/platform_device.h> + +#include <dt-bindings/thermal/tegra124-soctherm.h> + +#include "soctherm.h" + +#define TEGRA132_THERMTRIP_ANY_EN_MASK (0x1 << 28) +#define TEGRA132_THERMTRIP_MEM_EN_MASK (0x1 << 27) +#define TEGRA132_THERMTRIP_GPU_EN_MASK (0x1 << 26) +#define TEGRA132_THERMTRIP_CPU_EN_MASK (0x1 << 25) +#define TEGRA132_THERMTRIP_TSENSE_EN_MASK (0x1 << 24) +#define TEGRA132_THERMTRIP_GPUMEM_THRESH_MASK (0xff << 16) +#define TEGRA132_THERMTRIP_CPU_THRESH_MASK (0xff << 8) +#define TEGRA132_THERMTRIP_TSENSE_THRESH_MASK 0xff + +#define TEGRA132_THRESH_GRAIN 1000 + +static const struct tegra_tsensor_configuration tegra132_tsensor_config = { + .tall = 16300, + .tiddq_en = 1, + .ten_count = 1, + .tsample = 120, + .tsample_ate = 480, +}; + +static const struct tegra_tsensor_group tegra132_tsensor_group_cpu = { + .id = TEGRA124_SOCTHERM_SENSOR_CPU, + .name = "cpu", + .sensor_temp_offset = SENSOR_TEMP1, + .sensor_temp_mask = SENSOR_TEMP1_CPU_TEMP_MASK, + .pdiv = 8, + .pdiv_ate = 8, + .pdiv_mask = SENSOR_PDIV_CPU_MASK, + .pllx_hotspot_diff = 10, + .pllx_hotspot_mask = SENSOR_HOTSPOT_CPU_MASK, + .thermtrip_any_en_mask = TEGRA132_THERMTRIP_ANY_EN_MASK, + .thermtrip_enable_mask = TEGRA132_THERMTRIP_CPU_EN_MASK, + .thermtrip_threshold_mask = TEGRA132_THERMTRIP_CPU_THRESH_MASK, +}; + +static const struct tegra_tsensor_group tegra132_tsensor_group_gpu = { + .id = TEGRA124_SOCTHERM_SENSOR_GPU, + .name = "gpu", + .sensor_temp_offset = SENSOR_TEMP1, + .sensor_temp_mask = SENSOR_TEMP1_GPU_TEMP_MASK, + .pdiv = 8, + .pdiv_ate = 8, + .pdiv_mask = SENSOR_PDIV_GPU_MASK, + .pllx_hotspot_diff = 5, + .pllx_hotspot_mask = SENSOR_HOTSPOT_GPU_MASK, + .thermtrip_any_en_mask = TEGRA132_THERMTRIP_ANY_EN_MASK, + .thermtrip_enable_mask = TEGRA132_THERMTRIP_GPU_EN_MASK, + .thermtrip_threshold_mask = TEGRA132_THERMTRIP_GPUMEM_THRESH_MASK, +}; + +static const struct tegra_tsensor_group tegra132_tsensor_group_pll = { + .id = TEGRA124_SOCTHERM_SENSOR_PLLX, + .name = "pll", + .sensor_temp_offset = SENSOR_TEMP2, + .sensor_temp_mask = SENSOR_TEMP2_PLLX_TEMP_MASK, + .pdiv = 8, + .pdiv_ate = 8, + .pdiv_mask = SENSOR_PDIV_PLLX_MASK, + .thermtrip_any_en_mask = TEGRA132_THERMTRIP_ANY_EN_MASK, + .thermtrip_enable_mask = TEGRA132_THERMTRIP_TSENSE_EN_MASK, + .thermtrip_threshold_mask = TEGRA132_THERMTRIP_TSENSE_THRESH_MASK, +}; + +static const struct tegra_tsensor_group tegra132_tsensor_group_mem = { + .id = TEGRA124_SOCTHERM_SENSOR_MEM, + .name = "mem", + .sensor_temp_offset = SENSOR_TEMP2, + .sensor_temp_mask = SENSOR_TEMP2_MEM_TEMP_MASK, + .pdiv = 8, + .pdiv_ate = 8, + .pdiv_mask = SENSOR_PDIV_MEM_MASK, + .pllx_hotspot_diff = 0, + .pllx_hotspot_mask = SENSOR_HOTSPOT_MEM_MASK, + .thermtrip_any_en_mask = TEGRA132_THERMTRIP_ANY_EN_MASK, + .thermtrip_enable_mask = TEGRA132_THERMTRIP_MEM_EN_MASK, + .thermtrip_threshold_mask = TEGRA132_THERMTRIP_GPUMEM_THRESH_MASK, +}; + +static const struct tegra_tsensor_group *tegra132_tsensor_groups[] = { + &tegra132_tsensor_group_cpu, + &tegra132_tsensor_group_gpu, + &tegra132_tsensor_group_pll, + &tegra132_tsensor_group_mem, +}; + +static struct tegra_tsensor tegra132_tsensors[] = { + { + .name = "cpu0", + .base = 0xc0, + .config = &tegra132_tsensor_config, + .calib_fuse_offset = 0x098, + .fuse_corr_alpha = 1126600, + .fuse_corr_beta = -9433500, + .group = &tegra132_tsensor_group_cpu, + }, { + .name = "cpu1", + .base = 0xe0, + .config = &tegra132_tsensor_config, + .calib_fuse_offset = 0x084, + .fuse_corr_alpha = 1110800, + .fuse_corr_beta = -7383000, + .group = &tegra132_tsensor_group_cpu, + }, { + .name = "cpu2", + .base = 0x100, + .config = &tegra132_tsensor_config, + .calib_fuse_offset = 0x088, + .fuse_corr_alpha = 1113800, + .fuse_corr_beta = -6215200, + .group = &tegra132_tsensor_group_cpu, + }, { + .name = "cpu3", + .base = 0x120, + .config = &tegra132_tsensor_config, + .calib_fuse_offset = 0x12c, + .fuse_corr_alpha = 1129600, + .fuse_corr_beta = -8196100, + .group = &tegra132_tsensor_group_cpu, + }, { + .name = "mem0", + .base = 0x140, + .config = &tegra132_tsensor_config, + .calib_fuse_offset = 0x158, + .fuse_corr_alpha = 1132900, + .fuse_corr_beta = -6755300, + .group = &tegra132_tsensor_group_mem, + }, { + .name = "mem1", + .base = 0x160, + .config = &tegra132_tsensor_config, + .calib_fuse_offset = 0x15c, + .fuse_corr_alpha = 1142300, + .fuse_corr_beta = -7374200, + .group = &tegra132_tsensor_group_mem, + }, { + .name = "gpu", + .base = 0x180, + .config = &tegra132_tsensor_config, + .calib_fuse_offset = 0x154, + .fuse_corr_alpha = 1125100, + .fuse_corr_beta = -6350400, + .group = &tegra132_tsensor_group_gpu, + }, { + .name = "pllx", + .base = 0x1a0, + .config = &tegra132_tsensor_config, + .calib_fuse_offset = 0x160, + .fuse_corr_alpha = 1118100, + .fuse_corr_beta = -8208800, + .group = &tegra132_tsensor_group_pll, + }, +}; + +/* + * Mask/shift bits in FUSE_TSENSOR_COMMON and + * FUSE_TSENSOR_COMMON, which are described in + * tegra_soctherm_fuse.c + */ +static const struct tegra_soctherm_fuse tegra132_soctherm_fuse = { + .fuse_base_cp_mask = 0x3ff, + .fuse_base_cp_shift = 0, + .fuse_base_ft_mask = 0x7ff << 10, + .fuse_base_ft_shift = 10, + .fuse_shift_ft_mask = 0x1f << 21, + .fuse_shift_ft_shift = 21, + .fuse_spare_realignment = 0x1fc, +}; + +const struct tegra_soctherm_soc tegra132_soctherm = { + .tsensors = tegra132_tsensors, + .num_tsensors = ARRAY_SIZE(tegra132_tsensors), + .ttgs = tegra132_tsensor_groups, + .num_ttgs = ARRAY_SIZE(tegra132_tsensor_groups), + .tfuse = &tegra132_soctherm_fuse, + .thresh_grain = TEGRA132_THRESH_GRAIN, +}; diff --git a/drivers/thermal/tegra/tegra210-soctherm.c b/drivers/thermal/tegra/tegra210-soctherm.c new file mode 100644 index 000000000000..19cc0ab66f0e --- /dev/null +++ b/drivers/thermal/tegra/tegra210-soctherm.c @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2014-2016, NVIDIA CORPORATION. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/module.h> +#include <linux/platform_device.h> +#include <soc/tegra/fuse.h> + +#include <dt-bindings/thermal/tegra124-soctherm.h> + +#include "soctherm.h" + +#define TEGRA210_THERMTRIP_ANY_EN_MASK (0x1 << 31) +#define TEGRA210_THERMTRIP_MEM_EN_MASK (0x1 << 30) +#define TEGRA210_THERMTRIP_GPU_EN_MASK (0x1 << 29) +#define TEGRA210_THERMTRIP_CPU_EN_MASK (0x1 << 28) +#define TEGRA210_THERMTRIP_TSENSE_EN_MASK (0x1 << 27) +#define TEGRA210_THERMTRIP_GPUMEM_THRESH_MASK (0x1ff << 18) +#define TEGRA210_THERMTRIP_CPU_THRESH_MASK (0x1ff << 9) +#define TEGRA210_THERMTRIP_TSENSE_THRESH_MASK 0x1ff + +#define TEGRA210_THRESH_GRAIN 500 + +static const struct tegra_tsensor_configuration tegra210_tsensor_config = { + .tall = 16300, + .tiddq_en = 1, + .ten_count = 1, + .tsample = 120, + .tsample_ate = 480, +}; + +static const struct tegra_tsensor_group tegra210_tsensor_group_cpu = { + .id = TEGRA124_SOCTHERM_SENSOR_CPU, + .name = "cpu", + .sensor_temp_offset = SENSOR_TEMP1, + .sensor_temp_mask = SENSOR_TEMP1_CPU_TEMP_MASK, + .pdiv = 8, + .pdiv_ate = 8, + .pdiv_mask = SENSOR_PDIV_CPU_MASK, + .pllx_hotspot_diff = 10, + .pllx_hotspot_mask = SENSOR_HOTSPOT_CPU_MASK, + .thermtrip_any_en_mask = TEGRA210_THERMTRIP_ANY_EN_MASK, + .thermtrip_enable_mask = TEGRA210_THERMTRIP_CPU_EN_MASK, + .thermtrip_threshold_mask = TEGRA210_THERMTRIP_CPU_THRESH_MASK, +}; + +static const struct tegra_tsensor_group tegra210_tsensor_group_gpu = { + .id = TEGRA124_SOCTHERM_SENSOR_GPU, + .name = "gpu", + .sensor_temp_offset = SENSOR_TEMP1, + .sensor_temp_mask = SENSOR_TEMP1_GPU_TEMP_MASK, + .pdiv = 8, + .pdiv_ate = 8, + .pdiv_mask = SENSOR_PDIV_GPU_MASK, + .pllx_hotspot_diff = 5, + .pllx_hotspot_mask = SENSOR_HOTSPOT_GPU_MASK, + .thermtrip_any_en_mask = TEGRA210_THERMTRIP_ANY_EN_MASK, + .thermtrip_enable_mask = TEGRA210_THERMTRIP_GPU_EN_MASK, + .thermtrip_threshold_mask = TEGRA210_THERMTRIP_GPUMEM_THRESH_MASK, +}; + +static const struct tegra_tsensor_group tegra210_tsensor_group_pll = { + .id = TEGRA124_SOCTHERM_SENSOR_PLLX, + .name = "pll", + .sensor_temp_offset = SENSOR_TEMP2, + .sensor_temp_mask = SENSOR_TEMP2_PLLX_TEMP_MASK, + .pdiv = 8, + .pdiv_ate = 8, + .pdiv_mask = SENSOR_PDIV_PLLX_MASK, + .thermtrip_any_en_mask = TEGRA210_THERMTRIP_ANY_EN_MASK, + .thermtrip_enable_mask = TEGRA210_THERMTRIP_TSENSE_EN_MASK, + .thermtrip_threshold_mask = TEGRA210_THERMTRIP_TSENSE_THRESH_MASK, +}; + +static const struct tegra_tsensor_group tegra210_tsensor_group_mem = { + .id = TEGRA124_SOCTHERM_SENSOR_MEM, + .name = "mem", + .sensor_temp_offset = SENSOR_TEMP2, + .sensor_temp_mask = SENSOR_TEMP2_MEM_TEMP_MASK, + .pdiv = 8, + .pdiv_ate = 8, + .pdiv_mask = SENSOR_PDIV_MEM_MASK, + .pllx_hotspot_diff = 0, + .pllx_hotspot_mask = SENSOR_HOTSPOT_MEM_MASK, + .thermtrip_any_en_mask = TEGRA210_THERMTRIP_ANY_EN_MASK, + .thermtrip_enable_mask = TEGRA210_THERMTRIP_MEM_EN_MASK, + .thermtrip_threshold_mask = TEGRA210_THERMTRIP_GPUMEM_THRESH_MASK, +}; + +static const struct tegra_tsensor_group *tegra210_tsensor_groups[] = { + &tegra210_tsensor_group_cpu, + &tegra210_tsensor_group_gpu, + &tegra210_tsensor_group_pll, + &tegra210_tsensor_group_mem, +}; + +static const struct tegra_tsensor tegra210_tsensors[] = { + { + .name = "cpu0", + .base = 0xc0, + .config = &tegra210_tsensor_config, + .calib_fuse_offset = 0x098, + .fuse_corr_alpha = 1085000, + .fuse_corr_beta = 3244200, + .group = &tegra210_tsensor_group_cpu, + }, { + .name = "cpu1", + .base = 0xe0, + .config = &tegra210_tsensor_config, + .calib_fuse_offset = 0x084, + .fuse_corr_alpha = 1126200, + .fuse_corr_beta = -67500, + .group = &tegra210_tsensor_group_cpu, + }, { + .name = "cpu2", + .base = 0x100, + .config = &tegra210_tsensor_config, + .calib_fuse_offset = 0x088, + .fuse_corr_alpha = 1098400, + .fuse_corr_beta = 2251100, + .group = &tegra210_tsensor_group_cpu, + }, { + .name = "cpu3", + .base = 0x120, + .config = &tegra210_tsensor_config, + .calib_fuse_offset = 0x12c, + .fuse_corr_alpha = 1108000, + .fuse_corr_beta = 602700, + .group = &tegra210_tsensor_group_cpu, + }, { + .name = "mem0", + .base = 0x140, + .config = &tegra210_tsensor_config, + .calib_fuse_offset = 0x158, + .fuse_corr_alpha = 1069200, + .fuse_corr_beta = 3549900, + .group = &tegra210_tsensor_group_mem, + }, { + .name = "mem1", + .base = 0x160, + .config = &tegra210_tsensor_config, + .calib_fuse_offset = 0x15c, + .fuse_corr_alpha = 1173700, + .fuse_corr_beta = -6263600, + .group = &tegra210_tsensor_group_mem, + }, { + .name = "gpu", + .base = 0x180, + .config = &tegra210_tsensor_config, + .calib_fuse_offset = 0x154, + .fuse_corr_alpha = 1074300, + .fuse_corr_beta = 2734900, + .group = &tegra210_tsensor_group_gpu, + }, { + .name = "pllx", + .base = 0x1a0, + .config = &tegra210_tsensor_config, + .calib_fuse_offset = 0x160, + .fuse_corr_alpha = 1039700, + .fuse_corr_beta = 6829100, + .group = &tegra210_tsensor_group_pll, + }, +}; + +/* + * Mask/shift bits in FUSE_TSENSOR_COMMON and + * FUSE_TSENSOR_COMMON, which are described in + * tegra_soctherm_fuse.c + */ +static const struct tegra_soctherm_fuse tegra210_soctherm_fuse = { + .fuse_base_cp_mask = 0x3ff << 11, + .fuse_base_cp_shift = 11, + .fuse_base_ft_mask = 0x7ff << 21, + .fuse_base_ft_shift = 21, + .fuse_shift_ft_mask = 0x1f << 6, + .fuse_shift_ft_shift = 6, + .fuse_spare_realignment = 0, +}; + +const struct tegra_soctherm_soc tegra210_soctherm = { + .tsensors = tegra210_tsensors, + .num_tsensors = ARRAY_SIZE(tegra210_tsensors), + .ttgs = tegra210_tsensor_groups, + .num_ttgs = ARRAY_SIZE(tegra210_tsensor_groups), + .tfuse = &tegra210_soctherm_fuse, + .thresh_grain = TEGRA210_THRESH_GRAIN, +}; diff --git a/drivers/thermal/tegra_soctherm.c b/drivers/thermal/tegra_soctherm.c deleted file mode 100644 index 136975220c92..000000000000 --- a/drivers/thermal/tegra_soctherm.c +++ /dev/null @@ -1,476 +0,0 @@ -/* - * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. - * - * Author: - * Mikko Perttunen <mperttunen@nvidia.com> - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * 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/bitops.h> -#include <linux/clk.h> -#include <linux/delay.h> -#include <linux/err.h> -#include <linux/interrupt.h> -#include <linux/io.h> -#include <linux/module.h> -#include <linux/of.h> -#include <linux/platform_device.h> -#include <linux/reset.h> -#include <linux/thermal.h> - -#include <soc/tegra/fuse.h> - -#define SENSOR_CONFIG0 0 -#define SENSOR_CONFIG0_STOP BIT(0) -#define SENSOR_CONFIG0_TALL_SHIFT 8 -#define SENSOR_CONFIG0_TCALC_OVER BIT(4) -#define SENSOR_CONFIG0_OVER BIT(3) -#define SENSOR_CONFIG0_CPTR_OVER BIT(2) - -#define SENSOR_CONFIG1 4 -#define SENSOR_CONFIG1_TSAMPLE_SHIFT 0 -#define SENSOR_CONFIG1_TIDDQ_EN_SHIFT 15 -#define SENSOR_CONFIG1_TEN_COUNT_SHIFT 24 -#define SENSOR_CONFIG1_TEMP_ENABLE BIT(31) - -#define SENSOR_CONFIG2 8 -#define SENSOR_CONFIG2_THERMA_SHIFT 16 -#define SENSOR_CONFIG2_THERMB_SHIFT 0 - -#define SENSOR_PDIV 0x1c0 -#define SENSOR_PDIV_T124 0x8888 -#define SENSOR_HOTSPOT_OFF 0x1c4 -#define SENSOR_HOTSPOT_OFF_T124 0x00060600 -#define SENSOR_TEMP1 0x1c8 -#define SENSOR_TEMP2 0x1cc - -#define SENSOR_TEMP_MASK 0xffff -#define READBACK_VALUE_MASK 0xff00 -#define READBACK_VALUE_SHIFT 8 -#define READBACK_ADD_HALF BIT(7) -#define READBACK_NEGATE BIT(0) - -#define FUSE_TSENSOR8_CALIB 0x180 -#define FUSE_SPARE_REALIGNMENT_REG_0 0x1fc - -#define FUSE_TSENSOR_CALIB_CP_TS_BASE_MASK 0x1fff -#define FUSE_TSENSOR_CALIB_FT_TS_BASE_MASK (0x1fff << 13) -#define FUSE_TSENSOR_CALIB_FT_TS_BASE_SHIFT 13 - -#define FUSE_TSENSOR8_CALIB_CP_TS_BASE_MASK 0x3ff -#define FUSE_TSENSOR8_CALIB_FT_TS_BASE_MASK (0x7ff << 10) -#define FUSE_TSENSOR8_CALIB_FT_TS_BASE_SHIFT 10 - -#define FUSE_SPARE_REALIGNMENT_REG_SHIFT_CP_MASK 0x3f -#define FUSE_SPARE_REALIGNMENT_REG_SHIFT_FT_MASK (0x1f << 21) -#define FUSE_SPARE_REALIGNMENT_REG_SHIFT_FT_SHIFT 21 - -#define NOMINAL_CALIB_FT_T124 105 -#define NOMINAL_CALIB_CP_T124 25 - -struct tegra_tsensor_configuration { - u32 tall, tsample, tiddq_en, ten_count, pdiv, tsample_ate, pdiv_ate; -}; - -struct tegra_tsensor { - const struct tegra_tsensor_configuration *config; - u32 base, calib_fuse_offset; - /* Correction values used to modify values read from calibration fuses */ - s32 fuse_corr_alpha, fuse_corr_beta; -}; - -struct tegra_thermctl_zone { - void __iomem *reg; - unsigned int shift; -}; - -static const struct tegra_tsensor_configuration t124_tsensor_config = { - .tall = 16300, - .tsample = 120, - .tiddq_en = 1, - .ten_count = 1, - .pdiv = 8, - .tsample_ate = 480, - .pdiv_ate = 8 -}; - -static const struct tegra_tsensor t124_tsensors[] = { - { - .config = &t124_tsensor_config, - .base = 0xc0, - .calib_fuse_offset = 0x098, - .fuse_corr_alpha = 1135400, - .fuse_corr_beta = -6266900, - }, - { - .config = &t124_tsensor_config, - .base = 0xe0, - .calib_fuse_offset = 0x084, - .fuse_corr_alpha = 1122220, - .fuse_corr_beta = -5700700, - }, - { - .config = &t124_tsensor_config, - .base = 0x100, - .calib_fuse_offset = 0x088, - .fuse_corr_alpha = 1127000, - .fuse_corr_beta = -6768200, - }, - { - .config = &t124_tsensor_config, - .base = 0x120, - .calib_fuse_offset = 0x12c, - .fuse_corr_alpha = 1110900, - .fuse_corr_beta = -6232000, - }, - { - .config = &t124_tsensor_config, - .base = 0x140, - .calib_fuse_offset = 0x158, - .fuse_corr_alpha = 1122300, - .fuse_corr_beta = -5936400, - }, - { - .config = &t124_tsensor_config, - .base = 0x160, - .calib_fuse_offset = 0x15c, - .fuse_corr_alpha = 1145700, - .fuse_corr_beta = -7124600, - }, - { - .config = &t124_tsensor_config, - .base = 0x180, - .calib_fuse_offset = 0x154, - .fuse_corr_alpha = 1120100, - .fuse_corr_beta = -6000500, - }, - { - .config = &t124_tsensor_config, - .base = 0x1a0, - .calib_fuse_offset = 0x160, - .fuse_corr_alpha = 1106500, - .fuse_corr_beta = -6729300, - }, -}; - -struct tegra_soctherm { - struct reset_control *reset; - struct clk *clock_tsensor; - struct clk *clock_soctherm; - void __iomem *regs; - - struct thermal_zone_device *thermctl_tzs[4]; -}; - -struct tsensor_shared_calibration { - u32 base_cp, base_ft; - u32 actual_temp_cp, actual_temp_ft; -}; - -static int calculate_shared_calibration(struct tsensor_shared_calibration *r) -{ - u32 val, shifted_cp, shifted_ft; - int err; - - err = tegra_fuse_readl(FUSE_TSENSOR8_CALIB, &val); - if (err) - return err; - r->base_cp = val & FUSE_TSENSOR8_CALIB_CP_TS_BASE_MASK; - r->base_ft = (val & FUSE_TSENSOR8_CALIB_FT_TS_BASE_MASK) - >> FUSE_TSENSOR8_CALIB_FT_TS_BASE_SHIFT; - val = ((val & FUSE_SPARE_REALIGNMENT_REG_SHIFT_FT_MASK) - >> FUSE_SPARE_REALIGNMENT_REG_SHIFT_FT_SHIFT); - shifted_ft = sign_extend32(val, 4); - - err = tegra_fuse_readl(FUSE_SPARE_REALIGNMENT_REG_0, &val); - if (err) - return err; - shifted_cp = sign_extend32(val, 5); - - r->actual_temp_cp = 2 * NOMINAL_CALIB_CP_T124 + shifted_cp; - r->actual_temp_ft = 2 * NOMINAL_CALIB_FT_T124 + shifted_ft; - - return 0; -} - -static s64 div64_s64_precise(s64 a, s64 b) -{ - s64 r, al; - - /* Scale up for increased precision division */ - al = a << 16; - - r = div64_s64(al * 2 + 1, 2 * b); - return r >> 16; -} - -static int -calculate_tsensor_calibration(const struct tegra_tsensor *sensor, - const struct tsensor_shared_calibration *shared, - u32 *calib) -{ - u32 val; - s32 actual_tsensor_ft, actual_tsensor_cp, delta_sens, delta_temp, - mult, div; - s16 therma, thermb; - s64 tmp; - int err; - - err = tegra_fuse_readl(sensor->calib_fuse_offset, &val); - if (err) - return err; - - actual_tsensor_cp = (shared->base_cp * 64) + sign_extend32(val, 12); - val = (val & FUSE_TSENSOR_CALIB_FT_TS_BASE_MASK) - >> FUSE_TSENSOR_CALIB_FT_TS_BASE_SHIFT; - actual_tsensor_ft = (shared->base_ft * 32) + sign_extend32(val, 12); - - delta_sens = actual_tsensor_ft - actual_tsensor_cp; - delta_temp = shared->actual_temp_ft - shared->actual_temp_cp; - - mult = sensor->config->pdiv * sensor->config->tsample_ate; - div = sensor->config->tsample * sensor->config->pdiv_ate; - - therma = div64_s64_precise((s64) delta_temp * (1LL << 13) * mult, - (s64) delta_sens * div); - - tmp = (s64)actual_tsensor_ft * shared->actual_temp_cp - - (s64)actual_tsensor_cp * shared->actual_temp_ft; - thermb = div64_s64_precise(tmp, (s64)delta_sens); - - therma = div64_s64_precise((s64)therma * sensor->fuse_corr_alpha, - (s64)1000000LL); - thermb = div64_s64_precise((s64)thermb * sensor->fuse_corr_alpha + - sensor->fuse_corr_beta, (s64)1000000LL); - - *calib = ((u16)therma << SENSOR_CONFIG2_THERMA_SHIFT) | - ((u16)thermb << SENSOR_CONFIG2_THERMB_SHIFT); - - return 0; -} - -static int enable_tsensor(struct tegra_soctherm *tegra, - const struct tegra_tsensor *sensor, - const struct tsensor_shared_calibration *shared) -{ - void __iomem *base = tegra->regs + sensor->base; - unsigned int val; - u32 calib; - int err; - - err = calculate_tsensor_calibration(sensor, shared, &calib); - if (err) - return err; - - val = sensor->config->tall << SENSOR_CONFIG0_TALL_SHIFT; - writel(val, base + SENSOR_CONFIG0); - - val = (sensor->config->tsample - 1) << SENSOR_CONFIG1_TSAMPLE_SHIFT; - val |= sensor->config->tiddq_en << SENSOR_CONFIG1_TIDDQ_EN_SHIFT; - val |= sensor->config->ten_count << SENSOR_CONFIG1_TEN_COUNT_SHIFT; - val |= SENSOR_CONFIG1_TEMP_ENABLE; - writel(val, base + SENSOR_CONFIG1); - - writel(calib, base + SENSOR_CONFIG2); - - return 0; -} - -/* - * Translate from soctherm readback format to millicelsius. - * The soctherm readback format in bits is as follows: - * TTTTTTTT H______N - * where T's contain the temperature in Celsius, - * H denotes an addition of 0.5 Celsius and N denotes negation - * of the final value. - */ -static int translate_temp(u16 val) -{ - long t; - - t = ((val & READBACK_VALUE_MASK) >> READBACK_VALUE_SHIFT) * 1000; - if (val & READBACK_ADD_HALF) - t += 500; - if (val & READBACK_NEGATE) - t *= -1; - - return t; -} - -static int tegra_thermctl_get_temp(void *data, int *out_temp) -{ - struct tegra_thermctl_zone *zone = data; - u32 val; - - val = (readl(zone->reg) >> zone->shift) & SENSOR_TEMP_MASK; - *out_temp = translate_temp(val); - - return 0; -} - -static const struct thermal_zone_of_device_ops tegra_of_thermal_ops = { - .get_temp = tegra_thermctl_get_temp, -}; - -static const struct of_device_id tegra_soctherm_of_match[] = { - { .compatible = "nvidia,tegra124-soctherm" }, - { }, -}; -MODULE_DEVICE_TABLE(of, tegra_soctherm_of_match); - -struct thermctl_zone_desc { - unsigned int offset; - unsigned int shift; -}; - -static const struct thermctl_zone_desc t124_thermctl_temp_zones[] = { - { SENSOR_TEMP1, 16 }, - { SENSOR_TEMP2, 16 }, - { SENSOR_TEMP1, 0 }, - { SENSOR_TEMP2, 0 } -}; - -static int tegra_soctherm_probe(struct platform_device *pdev) -{ - struct tegra_soctherm *tegra; - struct thermal_zone_device *tz; - struct tsensor_shared_calibration shared_calib; - struct resource *res; - unsigned int i; - int err; - - const struct tegra_tsensor *tsensors = t124_tsensors; - - tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL); - if (!tegra) - return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - tegra->regs = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(tegra->regs)) - return PTR_ERR(tegra->regs); - - tegra->reset = devm_reset_control_get(&pdev->dev, "soctherm"); - if (IS_ERR(tegra->reset)) { - dev_err(&pdev->dev, "can't get soctherm reset\n"); - return PTR_ERR(tegra->reset); - } - - tegra->clock_tsensor = devm_clk_get(&pdev->dev, "tsensor"); - if (IS_ERR(tegra->clock_tsensor)) { - dev_err(&pdev->dev, "can't get tsensor clock\n"); - return PTR_ERR(tegra->clock_tsensor); - } - - tegra->clock_soctherm = devm_clk_get(&pdev->dev, "soctherm"); - if (IS_ERR(tegra->clock_soctherm)) { - dev_err(&pdev->dev, "can't get soctherm clock\n"); - return PTR_ERR(tegra->clock_soctherm); - } - - reset_control_assert(tegra->reset); - - err = clk_prepare_enable(tegra->clock_soctherm); - if (err) - return err; - - err = clk_prepare_enable(tegra->clock_tsensor); - if (err) { - clk_disable_unprepare(tegra->clock_soctherm); - return err; - } - - reset_control_deassert(tegra->reset); - - /* Initialize raw sensors */ - - err = calculate_shared_calibration(&shared_calib); - if (err) - goto disable_clocks; - - for (i = 0; i < ARRAY_SIZE(t124_tsensors); ++i) { - err = enable_tsensor(tegra, tsensors + i, &shared_calib); - if (err) - goto disable_clocks; - } - - writel(SENSOR_PDIV_T124, tegra->regs + SENSOR_PDIV); - writel(SENSOR_HOTSPOT_OFF_T124, tegra->regs + SENSOR_HOTSPOT_OFF); - - /* Initialize thermctl sensors */ - - for (i = 0; i < ARRAY_SIZE(tegra->thermctl_tzs); ++i) { - struct tegra_thermctl_zone *zone = - devm_kzalloc(&pdev->dev, sizeof(*zone), GFP_KERNEL); - if (!zone) { - err = -ENOMEM; - goto unregister_tzs; - } - - zone->reg = tegra->regs + t124_thermctl_temp_zones[i].offset; - zone->shift = t124_thermctl_temp_zones[i].shift; - - tz = thermal_zone_of_sensor_register(&pdev->dev, i, zone, - &tegra_of_thermal_ops); - if (IS_ERR(tz)) { - err = PTR_ERR(tz); - dev_err(&pdev->dev, "failed to register sensor: %d\n", - err); - goto unregister_tzs; - } - - tegra->thermctl_tzs[i] = tz; - } - - return 0; - -unregister_tzs: - while (i--) - thermal_zone_of_sensor_unregister(&pdev->dev, - tegra->thermctl_tzs[i]); - -disable_clocks: - clk_disable_unprepare(tegra->clock_tsensor); - clk_disable_unprepare(tegra->clock_soctherm); - - return err; -} - -static int tegra_soctherm_remove(struct platform_device *pdev) -{ - struct tegra_soctherm *tegra = platform_get_drvdata(pdev); - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(tegra->thermctl_tzs); ++i) { - thermal_zone_of_sensor_unregister(&pdev->dev, - tegra->thermctl_tzs[i]); - } - - clk_disable_unprepare(tegra->clock_tsensor); - clk_disable_unprepare(tegra->clock_soctherm); - - return 0; -} - -static struct platform_driver tegra_soctherm_driver = { - .probe = tegra_soctherm_probe, - .remove = tegra_soctherm_remove, - .driver = { - .name = "tegra-soctherm", - .of_match_table = tegra_soctherm_of_match, - }, -}; -module_platform_driver(tegra_soctherm_driver); - -MODULE_AUTHOR("Mikko Perttunen <mperttunen@nvidia.com>"); -MODULE_DESCRIPTION("NVIDIA Tegra SOCTHERM thermal management driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/thermal/thermal-generic-adc.c b/drivers/thermal/thermal-generic-adc.c new file mode 100644 index 000000000000..73f55d6a1721 --- /dev/null +++ b/drivers/thermal/thermal-generic-adc.c @@ -0,0 +1,182 @@ +/* + * Generic ADC thermal driver + * + * Copyright (C) 2016 NVIDIA CORPORATION. All rights reserved. + * + * Author: Laxman Dewangan <ldewangan@nvidia.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/iio/consumer.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/thermal.h> + +struct gadc_thermal_info { + struct device *dev; + struct thermal_zone_device *tz_dev; + struct iio_channel *channel; + s32 *lookup_table; + int nlookup_table; +}; + +static int gadc_thermal_adc_to_temp(struct gadc_thermal_info *gti, int val) +{ + int temp, adc_hi, adc_lo; + int i; + + for (i = 0; i < gti->nlookup_table; i++) { + if (val >= gti->lookup_table[2 * i + 1]) + break; + } + + if (i == 0) { + temp = gti->lookup_table[0]; + } else if (i >= (gti->nlookup_table - 1)) { + temp = gti->lookup_table[2 * (gti->nlookup_table - 1)]; + } else { + adc_hi = gti->lookup_table[2 * i - 1]; + adc_lo = gti->lookup_table[2 * i + 1]; + temp = gti->lookup_table[2 * i]; + temp -= ((val - adc_lo) * 1000) / (adc_hi - adc_lo); + } + + return temp; +} + +static int gadc_thermal_get_temp(void *data, int *temp) +{ + struct gadc_thermal_info *gti = data; + int val; + int ret; + + ret = iio_read_channel_processed(gti->channel, &val); + if (ret < 0) { + dev_err(gti->dev, "IIO channel read failed %d\n", ret); + return ret; + } + *temp = gadc_thermal_adc_to_temp(gti, val); + + return 0; +} + +static const struct thermal_zone_of_device_ops gadc_thermal_ops = { + .get_temp = gadc_thermal_get_temp, +}; + +static int gadc_thermal_read_linear_lookup_table(struct device *dev, + struct gadc_thermal_info *gti) +{ + struct device_node *np = dev->of_node; + int ntable; + int ret; + + ntable = of_property_count_elems_of_size(np, "temperature-lookup-table", + sizeof(u32)); + if (ntable < 0) { + dev_err(dev, "Lookup table is not provided\n"); + return ntable; + } + + if (ntable % 2) { + dev_err(dev, "Pair of temperature vs ADC read value missing\n"); + return -EINVAL; + } + + gti->lookup_table = devm_kzalloc(dev, sizeof(*gti->lookup_table) * + ntable, GFP_KERNEL); + if (!gti->lookup_table) + return -ENOMEM; + + ret = of_property_read_u32_array(np, "temperature-lookup-table", + (u32 *)gti->lookup_table, ntable); + if (ret < 0) { + dev_err(dev, "Failed to read temperature lookup table: %d\n", + ret); + return ret; + } + + gti->nlookup_table = ntable / 2; + + return 0; +} + +static int gadc_thermal_probe(struct platform_device *pdev) +{ + struct gadc_thermal_info *gti; + int ret; + + if (!pdev->dev.of_node) { + dev_err(&pdev->dev, "Only DT based supported\n"); + return -ENODEV; + } + + gti = devm_kzalloc(&pdev->dev, sizeof(*gti), GFP_KERNEL); + if (!gti) + return -ENOMEM; + + ret = gadc_thermal_read_linear_lookup_table(&pdev->dev, gti); + if (ret < 0) + return ret; + + gti->dev = &pdev->dev; + platform_set_drvdata(pdev, gti); + + gti->channel = iio_channel_get(&pdev->dev, "sensor-channel"); + if (IS_ERR(gti->channel)) { + ret = PTR_ERR(gti->channel); + dev_err(&pdev->dev, "IIO channel not found: %d\n", ret); + return ret; + } + + gti->tz_dev = thermal_zone_of_sensor_register(&pdev->dev, 0, + gti, &gadc_thermal_ops); + if (IS_ERR(gti->tz_dev)) { + ret = PTR_ERR(gti->tz_dev); + dev_err(&pdev->dev, "Thermal zone sensor register failed: %d\n", + ret); + goto sensor_fail; + } + + return 0; + +sensor_fail: + iio_channel_release(gti->channel); + + return ret; +} + +static int gadc_thermal_remove(struct platform_device *pdev) +{ + struct gadc_thermal_info *gti = platform_get_drvdata(pdev); + + thermal_zone_of_sensor_unregister(&pdev->dev, gti->tz_dev); + iio_channel_release(gti->channel); + + return 0; +} + +static const struct of_device_id of_adc_thermal_match[] = { + { .compatible = "generic-adc-thermal", }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_adc_thermal_match); + +static struct platform_driver gadc_thermal_driver = { + .driver = { + .name = "generic-adc-thermal", + .of_match_table = of_adc_thermal_match, + }, + .probe = gadc_thermal_probe, + .remove = gadc_thermal_remove, +}; + +module_platform_driver(gadc_thermal_driver); + +MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>"); +MODULE_DESCRIPTION("Generic ADC thermal driver using IIO framework with DT"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c index b213a1222295..15c0a9ac2209 100644 --- a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c +++ b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c @@ -337,7 +337,7 @@ int ti_thermal_expose_sensor(struct ti_bandgap *bgp, int id, return -EINVAL; /* in case this is specified by DT */ - data->ti_thermal = thermal_zone_of_sensor_register(bgp->dev, id, + data->ti_thermal = devm_thermal_zone_of_sensor_register(bgp->dev, id, data, &ti_of_thermal_ops); if (IS_ERR(data->ti_thermal)) { /* Create thermal zone */ @@ -368,9 +368,6 @@ int ti_thermal_remove_sensor(struct ti_bandgap *bgp, int id) if (data && data->ti_thermal) { if (data->our_zone) thermal_zone_device_unregister(data->ti_thermal); - else - thermal_zone_of_sensor_unregister(bgp->dev, - data->ti_thermal); } return 0; diff --git a/drivers/thermal/x86_pkg_temp_thermal.c b/drivers/thermal/x86_pkg_temp_thermal.c index 7fc919f7da4d..97f0a2bd93ed 100644 --- a/drivers/thermal/x86_pkg_temp_thermal.c +++ b/drivers/thermal/x86_pkg_temp_thermal.c @@ -555,7 +555,7 @@ static int pkg_temp_thermal_cpu_callback(struct notifier_block *nfb, { unsigned int cpu = (unsigned long) hcpu; - switch (action) { + switch (action & ~CPU_TASKS_FROZEN) { case CPU_ONLINE: case CPU_DOWN_FAILED: get_core_online(cpu); |