diff options
| author | Zhang Rui <rui.zhang@intel.com> | 2017-05-06 19:30:53 +0800 |
|---|---|---|
| committer | Zhang Rui <rui.zhang@intel.com> | 2017-05-06 19:30:53 +0800 |
| commit | e4cb5bb286af409d814e4d13c650a736769b854b (patch) | |
| tree | 10fe0c97da03e6365df883d5c8ffed52a47f8d45 | |
| parent | 39da7c509acff13fc8cb12ec1bb20337c988ed36 (diff) | |
| parent | 05d7839aa290901429d8edcd8f7974c9df2bcaa5 (diff) | |
| download | linux-e4cb5bb286af409d814e4d13c650a736769b854b.tar.gz linux-e4cb5bb286af409d814e4d13c650a736769b854b.tar.bz2 linux-e4cb5bb286af409d814e4d13c650a736769b854b.zip | |
Merge branch 'linus' of git://git.kernel.org/pub/scm/linux/kernel/git/evalenti/linux-soc-thermal into thermal-soc
19 files changed, 1061 insertions, 217 deletions
diff --git a/Documentation/devicetree/bindings/thermal/brcm,bcm2835-thermal.txt b/Documentation/devicetree/bindings/thermal/brcm,bcm2835-thermal.txt index 474531d2b2c5..da8c5b73ad10 100644 --- a/Documentation/devicetree/bindings/thermal/brcm,bcm2835-thermal.txt +++ b/Documentation/devicetree/bindings/thermal/brcm,bcm2835-thermal.txt @@ -3,15 +3,39 @@ Binding for Thermal Sensor driver for BCM2835 SoCs. Required parameters: ------------------- -compatible: should be one of: "brcm,bcm2835-thermal", - "brcm,bcm2836-thermal" or "brcm,bcm2837-thermal" -reg: Address range of the thermal registers. -clocks: Phandle of the clock used by the thermal sensor. +compatible: should be one of: "brcm,bcm2835-thermal", + "brcm,bcm2836-thermal" or "brcm,bcm2837-thermal" +reg: Address range of the thermal registers. +clocks: Phandle of the clock used by the thermal sensor. +#thermal-sensor-cells: should be 0 (see thermal.txt) Example: +thermal-zones { + cpu_thermal: cpu-thermal { + polling-delay-passive = <0>; + polling-delay = <1000>; + + thermal-sensors = <&thermal>; + + trips { + cpu-crit { + temperature = <80000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + coefficients = <(-538) 407000>; + + cooling-maps { + }; + }; +}; + thermal: thermal@7e212000 { compatible = "brcm,bcm2835-thermal"; reg = <0x7e212000 0x8>; clocks = <&clocks BCM2835_CLOCK_TSENS>; + #thermal-sensor-cells = <0>; }; diff --git a/Documentation/devicetree/bindings/thermal/brcm,ns-thermal b/Documentation/devicetree/bindings/thermal/brcm,ns-thermal new file mode 100644 index 000000000000..68e047170039 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/brcm,ns-thermal @@ -0,0 +1,37 @@ +* Broadcom Northstar Thermal + +This binding describes thermal sensor that is part of Northstar's DMU (Device +Management Unit). + +Required properties: +- compatible : Must be "brcm,ns-thermal" +- reg : iomem address range of PVTMON registers +- #thermal-sensor-cells : Should be <0> + +Example: + +thermal: thermal@1800c2c0 { + compatible = "brcm,ns-thermal"; + reg = <0x1800c2c0 0x10>; + #thermal-sensor-cells = <0>; +}; + +thermal-zones { + cpu_thermal: cpu-thermal { + polling-delay-passive = <0>; + polling-delay = <1000>; + coefficients = <(-556) 418000>; + thermal-sensors = <&thermal>; + + trips { + cpu-crit { + temperature = <125000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + }; + }; +}; diff --git a/Documentation/devicetree/bindings/thermal/da9062-thermal.txt b/Documentation/devicetree/bindings/thermal/da9062-thermal.txt new file mode 100644 index 000000000000..e241bb5a5584 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/da9062-thermal.txt @@ -0,0 +1,36 @@ +* Dialog DA9062/61 TJUNC Thermal Module + +This module is part of the DA9061/DA9062. For more details about entire +DA9062 and DA9061 chips see Documentation/devicetree/bindings/mfd/da9062.txt + +Junction temperature thermal module uses an interrupt signal to identify +high THERMAL_TRIP_HOT temperatures for the PMIC device. + +Required properties: + +- compatible: should be one of the following valid compatible string lines: + "dlg,da9061-thermal", "dlg,da9062-thermal" + "dlg,da9062-thermal" + +Optional properties: + +- polling-delay-passive : Specify the polling period, measured in + milliseconds, between thermal zone device update checks. + +Example: DA9062 + + pmic0: da9062@58 { + thermal { + compatible = "dlg,da9062-thermal"; + polling-delay-passive = <3000>; + }; + }; + +Example: DA9061 using a fall-back compatible for the DA9062 onkey driver + + pmic0: da9061@58 { + thermal { + compatible = "dlg,da9061-thermal", "dlg,da9062-thermal"; + polling-delay-passive = <3000>; + }; + }; diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 776b34396144..f786ae433032 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -303,6 +303,16 @@ config DB8500_CPUFREQ_COOLING bound cpufreq cooling device turns active to set CPU frequency low to cool down the CPU. +config DA9062_THERMAL + tristate "DA9062/DA9061 Dialog Semiconductor thermal driver" + depends on MFD_DA9062 || COMPILE_TEST + depends on OF + help + Enable this for the Dialog Semiconductor thermal sensor driver. + This will report PMIC junction over-temperature for one thermal trip + zone. + Compatible with the DA9062 and DA9061 PMICs. + config INTEL_POWERCLAMP tristate "Intel PowerClamp idle injection driver" depends on THERMAL @@ -392,6 +402,11 @@ config MTK_THERMAL Enable this option if you want to have support for thermal management controller present in Mediatek SoCs +menu "Broadcom thermal drivers" +depends on ARCH_BCM || COMPILE_TEST +source "drivers/thermal/broadcom/Kconfig" +endmenu + menu "Texas Instruments thermal drivers" depends on ARCH_HAS_BANDGAP || COMPILE_TEST depends on HAS_IOMEM diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 7adae2029355..e6834061da28 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -27,6 +27,7 @@ thermal_sys-$(CONFIG_CLOCK_THERMAL) += clock_cooling.o thermal_sys-$(CONFIG_DEVFREQ_THERMAL) += devfreq_cooling.o # platform thermal drivers +obj-y += broadcom/ obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o obj-$(CONFIG_ROCKCHIP_THERMAL) += rockchip_thermal.o @@ -42,6 +43,7 @@ obj-$(CONFIG_IMX_THERMAL) += imx_thermal.o obj-$(CONFIG_MAX77620_THERMAL) += max77620_thermal.o obj-$(CONFIG_QORIQ_THERMAL) += qoriq_thermal.o obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.o +obj-$(CONFIG_DA9062_THERMAL) += da9062-thermal.o obj-$(CONFIG_INTEL_POWERCLAMP) += intel_powerclamp.o obj-$(CONFIG_X86_PKG_TEMP_THERMAL) += x86_pkg_temp_thermal.o obj-$(CONFIG_INTEL_SOC_DTS_IOSF_CORE) += intel_soc_dts_iosf.o diff --git a/drivers/thermal/broadcom/Kconfig b/drivers/thermal/broadcom/Kconfig new file mode 100644 index 000000000000..ab08af4654ef --- /dev/null +++ b/drivers/thermal/broadcom/Kconfig @@ -0,0 +1,16 @@ +config BCM2835_THERMAL + tristate "Thermal sensors on bcm2835 SoC" + depends on ARCH_BCM2835 || COMPILE_TEST + depends on HAS_IOMEM + depends on THERMAL_OF + help + Support for thermal sensors on Broadcom bcm2835 SoCs. + +config BCM_NS_THERMAL + tristate "Northstar thermal driver" + depends on ARCH_BCM_IPROC || COMPILE_TEST + help + Northstar is a family of SoCs that includes e.g. BCM4708, BCM47081, + BCM4709 and BCM47094. It contains DMU (Device Management Unit) block + with a thermal sensor that allows checking CPU temperature. This + driver provides support for it. diff --git a/drivers/thermal/broadcom/Makefile b/drivers/thermal/broadcom/Makefile new file mode 100644 index 000000000000..c6f62e4fd0ee --- /dev/null +++ b/drivers/thermal/broadcom/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_BCM2835_THERMAL) += bcm2835_thermal.o +obj-$(CONFIG_BCM_NS_THERMAL) += ns-thermal.o diff --git a/drivers/thermal/broadcom/bcm2835_thermal.c b/drivers/thermal/broadcom/bcm2835_thermal.c new file mode 100644 index 000000000000..0ecf80890c84 --- /dev/null +++ b/drivers/thermal/broadcom/bcm2835_thermal.c @@ -0,0 +1,314 @@ +/* + * Driver for Broadcom BCM2835 SoC temperature sensor + * + * Copyright (C) 2016 Martin Sperl + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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/clk.h> +#include <linux/debugfs.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/thermal.h> + +#define BCM2835_TS_TSENSCTL 0x00 +#define BCM2835_TS_TSENSSTAT 0x04 + +#define BCM2835_TS_TSENSCTL_PRWDW BIT(0) +#define BCM2835_TS_TSENSCTL_RSTB BIT(1) + +/* + * bandgap reference voltage in 6 mV increments + * 000b = 1178 mV, 001b = 1184 mV, ... 111b = 1220 mV + */ +#define BCM2835_TS_TSENSCTL_CTRL_BITS 3 +#define BCM2835_TS_TSENSCTL_CTRL_SHIFT 2 +#define BCM2835_TS_TSENSCTL_CTRL_MASK \ + GENMASK(BCM2835_TS_TSENSCTL_CTRL_BITS + \ + BCM2835_TS_TSENSCTL_CTRL_SHIFT - 1, \ + BCM2835_TS_TSENSCTL_CTRL_SHIFT) +#define BCM2835_TS_TSENSCTL_CTRL_DEFAULT 1 +#define BCM2835_TS_TSENSCTL_EN_INT BIT(5) +#define BCM2835_TS_TSENSCTL_DIRECT BIT(6) +#define BCM2835_TS_TSENSCTL_CLR_INT BIT(7) +#define BCM2835_TS_TSENSCTL_THOLD_SHIFT 8 +#define BCM2835_TS_TSENSCTL_THOLD_BITS 10 +#define BCM2835_TS_TSENSCTL_THOLD_MASK \ + GENMASK(BCM2835_TS_TSENSCTL_THOLD_BITS + \ + BCM2835_TS_TSENSCTL_THOLD_SHIFT - 1, \ + BCM2835_TS_TSENSCTL_THOLD_SHIFT) +/* + * time how long the block to be asserted in reset + * which based on a clock counter (TSENS clock assumed) + */ +#define BCM2835_TS_TSENSCTL_RSTDELAY_SHIFT 18 +#define BCM2835_TS_TSENSCTL_RSTDELAY_BITS 8 +#define BCM2835_TS_TSENSCTL_REGULEN BIT(26) + +#define BCM2835_TS_TSENSSTAT_DATA_BITS 10 +#define BCM2835_TS_TSENSSTAT_DATA_SHIFT 0 +#define BCM2835_TS_TSENSSTAT_DATA_MASK \ + GENMASK(BCM2835_TS_TSENSSTAT_DATA_BITS + \ + BCM2835_TS_TSENSSTAT_DATA_SHIFT - 1, \ + BCM2835_TS_TSENSSTAT_DATA_SHIFT) +#define BCM2835_TS_TSENSSTAT_VALID BIT(10) +#define BCM2835_TS_TSENSSTAT_INTERRUPT BIT(11) + +struct bcm2835_thermal_data { + struct thermal_zone_device *tz; + void __iomem *regs; + struct clk *clk; + struct dentry *debugfsdir; +}; + +static int bcm2835_thermal_adc2temp(u32 adc, int offset, int slope) +{ + return offset + slope * adc; +} + +static int bcm2835_thermal_temp2adc(int temp, int offset, int slope) +{ + temp -= offset; + temp /= slope; + + if (temp < 0) + temp = 0; + if (temp >= BIT(BCM2835_TS_TSENSSTAT_DATA_BITS)) + temp = BIT(BCM2835_TS_TSENSSTAT_DATA_BITS) - 1; + + return temp; +} + +static int bcm2835_thermal_get_temp(void *d, int *temp) +{ + struct bcm2835_thermal_data *data = d; + u32 val = readl(data->regs + BCM2835_TS_TSENSSTAT); + + if (!(val & BCM2835_TS_TSENSSTAT_VALID)) + return -EIO; + + val &= BCM2835_TS_TSENSSTAT_DATA_MASK; + + *temp = bcm2835_thermal_adc2temp( + val, + thermal_zone_get_offset(data->tz), + thermal_zone_get_slope(data->tz)); + + return 0; +} + +static const struct debugfs_reg32 bcm2835_thermal_regs[] = { + { + .name = "ctl", + .offset = 0 + }, + { + .name = "stat", + .offset = 4 + } +}; + +static void bcm2835_thermal_debugfs(struct platform_device *pdev) +{ + struct thermal_zone_device *tz = platform_get_drvdata(pdev); + struct bcm2835_thermal_data *data = tz->devdata; + struct debugfs_regset32 *regset; + + data->debugfsdir = debugfs_create_dir("bcm2835_thermal", NULL); + if (!data->debugfsdir) + return; + + regset = devm_kzalloc(&pdev->dev, sizeof(*regset), GFP_KERNEL); + if (!regset) + return; + + regset->regs = bcm2835_thermal_regs; + regset->nregs = ARRAY_SIZE(bcm2835_thermal_regs); + regset->base = data->regs; + + debugfs_create_regset32("regset", 0444, data->debugfsdir, regset); +} + +static struct thermal_zone_of_device_ops bcm2835_thermal_ops = { + .get_temp = bcm2835_thermal_get_temp, +}; + +/* + * Note: as per Raspberry Foundation FAQ + * (https://www.raspberrypi.org/help/faqs/#performanceOperatingTemperature) + * the recommended temperature range for the SoC -40C to +85C + * so the trip limit is set to 80C. + * this applies to all the BCM283X SoC + */ + +static const struct of_device_id bcm2835_thermal_of_match_table[] = { + { + .compatible = "brcm,bcm2835-thermal", + }, + { + .compatible = "brcm,bcm2836-thermal", + }, + { + .compatible = "brcm,bcm2837-thermal", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, bcm2835_thermal_of_match_table); + +static int bcm2835_thermal_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + struct thermal_zone_device *tz; + struct bcm2835_thermal_data *data; + struct resource *res; + int err = 0; + u32 val; + unsigned long rate; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + match = of_match_device(bcm2835_thermal_of_match_table, + &pdev->dev); + if (!match) + return -EINVAL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + data->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(data->regs)) { + err = PTR_ERR(data->regs); + dev_err(&pdev->dev, "Could not get registers: %d\n", err); + return err; + } + + data->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(data->clk)) { + err = PTR_ERR(data->clk); + if (err != -EPROBE_DEFER) + dev_err(&pdev->dev, "Could not get clk: %d\n", err); + return err; + } + + err = clk_prepare_enable(data->clk); + if (err) + return err; + + rate = clk_get_rate(data->clk); + if ((rate < 1920000) || (rate > 5000000)) + dev_warn(&pdev->dev, + "Clock %pCn running at %pCr Hz is outside of the recommended range: 1.92 to 5MHz\n", + data->clk, data->clk); + + /* register of thermal sensor and get info from DT */ + tz = thermal_zone_of_sensor_register(&pdev->dev, 0, data, + &bcm2835_thermal_ops); + if (IS_ERR(tz)) { + err = PTR_ERR(tz); + dev_err(&pdev->dev, + "Failed to register the thermal device: %d\n", + err); + goto err_clk; + } + + /* + * right now the FW does set up the HW-block, so we are not + * touching the configuration registers. + * But if the HW is not enabled, then set it up + * using "sane" values used by the firmware right now. + */ + val = readl(data->regs + BCM2835_TS_TSENSCTL); + if (!(val & BCM2835_TS_TSENSCTL_RSTB)) { + int trip_temp, offset, slope; + + slope = thermal_zone_get_slope(tz); + offset = thermal_zone_get_offset(tz); + /* + * For now we deal only with critical, otherwise + * would need to iterate + */ + err = tz->ops->get_trip_temp(tz, 0, &trip_temp); + if (err < 0) { + err = PTR_ERR(tz); + dev_err(&pdev->dev, + "Not able to read trip_temp: %d\n", + err); + goto err_tz; + } + + /* set bandgap reference voltage and enable voltage regulator */ + val = (BCM2835_TS_TSENSCTL_CTRL_DEFAULT << + BCM2835_TS_TSENSCTL_CTRL_SHIFT) | + BCM2835_TS_TSENSCTL_REGULEN; + + /* use the recommended reset duration */ + val |= (0xFE << BCM2835_TS_TSENSCTL_RSTDELAY_SHIFT); + + /* trip_adc value from info */ + val |= bcm2835_thermal_temp2adc(trip_temp, + offset, + slope) + << BCM2835_TS_TSENSCTL_THOLD_SHIFT; + + /* write the value back to the register as 2 steps */ + writel(val, data->regs + BCM2835_TS_TSENSCTL); + val |= BCM2835_TS_TSENSCTL_RSTB; + writel(val, data->regs + BCM2835_TS_TSENSCTL); + } + + data->tz = tz; + + platform_set_drvdata(pdev, tz); + + bcm2835_thermal_debugfs(pdev); + + return 0; +err_tz: + thermal_zone_of_sensor_unregister(&pdev->dev, tz); +err_clk: + clk_disable_unprepare(data->clk); + + return err; +} + +static int bcm2835_thermal_remove(struct platform_device *pdev) +{ + struct thermal_zone_device *tz = platform_get_drvdata(pdev); + struct bcm2835_thermal_data *data = tz->devdata; + + debugfs_remove_recursive(data->debugfsdir); + thermal_zone_of_sensor_unregister(&pdev->dev, tz); + clk_disable_unprepare(data->clk); + + return 0; +} + +static struct platform_driver bcm2835_thermal_driver = { + .probe = bcm2835_thermal_probe, + .remove = bcm2835_thermal_remove, + .driver = { + .name = "bcm2835_thermal", + .of_match_table = bcm2835_thermal_of_match_table, + }, +}; +module_platform_driver(bcm2835_thermal_driver); + +MODULE_AUTHOR("Martin Sperl"); +MODULE_DESCRIPTION("Thermal driver for bcm2835 chip"); +MODULE_LICENSE("GPL"); diff --git a/drivers/thermal/broadcom/ns-thermal.c b/drivers/thermal/broadcom/ns-thermal.c new file mode 100644 index 000000000000..322e741a2463 --- /dev/null +++ b/drivers/thermal/broadcom/ns-thermal.c @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2017 Rafał Miłecki <rafal@milecki.pl> + * + * 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/module.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/thermal.h> + +#define PVTMON_CONTROL0 0x00 +#define PVTMON_CONTROL0_SEL_MASK 0x0000000e +#define PVTMON_CONTROL0_SEL_TEMP_MONITOR 0x00000000 +#define PVTMON_CONTROL0_SEL_TEST_MODE 0x0000000e +#define PVTMON_STATUS 0x08 + +struct ns_thermal { + struct thermal_zone_device *tz; + void __iomem *pvtmon; +}; + +static int ns_thermal_get_temp(void *data, int *temp) +{ + struct ns_thermal *ns_thermal = data; + int offset = thermal_zone_get_offset(ns_thermal->tz); + int slope = thermal_zone_get_slope(ns_thermal->tz); + u32 val; + + val = readl(ns_thermal->pvtmon + PVTMON_CONTROL0); + if ((val & PVTMON_CONTROL0_SEL_MASK) != PVTMON_CONTROL0_SEL_TEMP_MONITOR) { + /* Clear current mode selection */ + val &= ~PVTMON_CONTROL0_SEL_MASK; + + /* Set temp monitor mode (it's the default actually) */ + val |= PVTMON_CONTROL0_SEL_TEMP_MONITOR; + + writel(val, ns_thermal->pvtmon + PVTMON_CONTROL0); + } + + val = readl(ns_thermal->pvtmon + PVTMON_STATUS); + *temp = slope * val + offset; + + return 0; +} + +static const struct thermal_zone_of_device_ops ns_thermal_ops = { + .get_temp = ns_thermal_get_temp, +}; + +static int ns_thermal_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ns_thermal *ns_thermal; + + ns_thermal = devm_kzalloc(dev, sizeof(*ns_thermal), GFP_KERNEL); + if (!ns_thermal) + return -ENOMEM; + + ns_thermal->pvtmon = of_iomap(dev_of_node(dev), 0); + if (WARN_ON(!ns_thermal->pvtmon)) + return -ENOENT; + + ns_thermal->tz = devm_thermal_zone_of_sensor_register(dev, 0, + ns_thermal, + &ns_thermal_ops); + if (IS_ERR(ns_thermal->tz)) { + iounmap(ns_thermal->pvtmon); + return PTR_ERR(ns_thermal->tz); + } + + platform_set_drvdata(pdev, ns_thermal); + + return 0; +} + +static int ns_thermal_remove(struct platform_device *pdev) +{ + struct ns_thermal *ns_thermal = platform_get_drvdata(pdev); + + iounmap(ns_thermal->pvtmon); + + return 0; +} + +static const struct of_device_id ns_thermal_of_match[] = { + { .compatible = "brcm,ns-thermal", }, + {}, +}; +MODULE_DEVICE_TABLE(of, ns_thermal_of_match); + +static struct platform_driver ns_thermal_driver = { + .probe = ns_thermal_probe, + .remove = ns_thermal_remove, + .driver = { + .name = "ns-thermal", + .of_match_table = ns_thermal_of_match, + }, +}; +module_platform_driver(ns_thermal_driver); + +MODULE_AUTHOR("Rafał Miłecki <rafal@milecki.pl>"); +MODULE_DESCRIPTION("Northstar thermal driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/thermal/da9062-thermal.c b/drivers/thermal/da9062-thermal.c new file mode 100644 index 000000000000..dd8dd947b7f0 --- /dev/null +++ b/drivers/thermal/da9062-thermal.c @@ -0,0 +1,315 @@ +/* + * Thermal device driver for DA9062 and DA9061 + * Copyright (C) 2017 Dialog Semiconductor + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * 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. + */ + +/* When over-temperature is reached, an interrupt from the device will be + * triggered. Following this event the interrupt will be disabled and + * periodic transmission of uevents (HOT trip point) should define the + * first level of temperature supervision. It is expected that any final + * implementation of the thermal driver will include a .notify() function + * to implement these uevents to userspace. + * + * These uevents are intended to indicate non-invasive temperature control + * of the system, where the necessary measures for cooling are the + * responsibility of the host software. Once the temperature falls again, + * the IRQ is re-enabled so the start of a new over-temperature event can + * be detected without constant software monitoring. + */ + +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/thermal.h> +#include <linux/workqueue.h> + +#include <linux/mfd/da9062/core.h> +#include <linux/mfd/da9062/registers.h> + +/* Minimum, maximum and default polling millisecond periods are provided + * here as an example. It is expected that any final implementation to also + * include a modification of these settings to match the required + * application. + */ +#define DA9062_DEFAULT_POLLING_MS_PERIOD 3000 +#define DA9062_MAX_POLLING_MS_PERIOD 10000 +#define DA9062_MIN_POLLING_MS_PERIOD 1000 + +#define DA9062_MILLI_CELSIUS(t) ((t) * 1000) + +struct da9062_thermal_config { + const char *name; +}; + +struct da9062_thermal { + struct da9062 *hw; + struct delayed_work work; + struct thermal_zone_device *zone; + enum thermal_device_mode mode; + struct mutex lock; /* protection for da9062_thermal temperature */ + int temperature; + int irq; + const struct da9062_thermal_config *config; + struct device *dev; +}; + +static void da9062_thermal_poll_on(struct work_struct *work) +{ + struct da9062_thermal *thermal = container_of(work, + struct da9062_thermal, + work.work); + unsigned long delay; + unsigned int val; + int ret; + + /* clear E_TEMP */ + ret = regmap_write(thermal->hw->regmap, + DA9062AA_EVENT_B, + DA9062AA_E_TEMP_MASK); + if (ret < 0) { + dev_err(thermal->dev, + "Cannot clear the TJUNC temperature status\n"); + goto err_enable_irq; + } + + /* Now read E_TEMP again: it is acting like a status bit. + * If over-temperature, then this status will be true. + * If not over-temperature, this status will be false. + */ + ret = regmap_read(thermal->hw->regmap, + DA9062AA_EVENT_B, + &val); + if (ret < 0) { + dev_err(thermal->dev, + "Cannot check the TJUNC temperature status\n"); + goto err_enable_irq; + } + + if (val & DA9062AA_E_TEMP_MASK) { + mutex_lock(&thermal->lock); + thermal->temperature = DA9062_MILLI_CELSIUS(125); + mutex_unlock(&thermal->lock); + thermal_zone_device_update(thermal->zone, + THERMAL_EVENT_UNSPECIFIED); + + delay = msecs_to_jiffies(thermal->zone->passive_delay); + schedule_delayed_work(&thermal->work, delay); + return; + } + + mutex_lock(&thermal->lock); + thermal->temperature = DA9062_MILLI_CELSIUS(0); + mutex_unlock(&thermal->lock); + thermal_zone_device_update(thermal->zone, + THERMAL_EVENT_UNSPECIFIED); + +err_enable_irq: + enable_irq(thermal->irq); +} + +static irqreturn_t da9062_thermal_irq_handler(int irq, void *data) +{ + struct da9062_thermal *thermal = data; + + disable_irq_nosync(thermal->irq); + schedule_delayed_work(&thermal->work, 0); + + return IRQ_HANDLED; +} + +static int da9062_thermal_get_mode(struct thermal_zone_device *z, + enum thermal_device_mode *mode) +{ + struct da9062_thermal *thermal = z->devdata; + *mode = thermal->mode; + return 0; +} + +static int da9062_thermal_get_trip_type(struct thermal_zone_device *z, + int trip, + enum thermal_trip_type *type) +{ + struct da9062_thermal *thermal = z->devdata; + + switch (trip) { + case 0: + *type = THERMAL_TRIP_HOT; + break; + default: + dev_err(thermal->dev, + "Driver does not support more than 1 trip-wire\n"); + return -EINVAL; + } + + return 0; +} + +static int da9062_thermal_get_trip_temp(struct thermal_zone_device *z, + int trip, + int *temp) +{ + struct da9062_thermal *thermal = z->devdata; + + switch (trip) { + case 0: + *temp = DA9062_MILLI_CELSIUS(125); + break; + default: + dev_err(thermal->dev, + "Driver does not support more than 1 trip-wire\n"); + return -EINVAL; + } + + return 0; +} + +static int da9062_thermal_get_temp(struct thermal_zone_device *z, + int *temp) +{ + struct da9062_thermal *thermal = z->devdata; + + mutex_lock(&thermal->lock); + *temp = thermal->temperature; + mutex_unlock(&thermal->lock); + + return 0; +} + +static struct thermal_zone_device_ops da9062_thermal_ops = { + .get_temp = da9062_thermal_get_temp, + .get_mode = da9062_thermal_get_mode, + .get_trip_type = da9062_thermal_get_trip_type, + .get_trip_temp = da9062_thermal_get_trip_temp, +}; + +static const struct da9062_thermal_config da9062_config = { + .name = "da9062-thermal", +}; + +static const struct of_device_id da9062_compatible_reg_id_table[] = { + { .compatible = "dlg,da9062-thermal", .data = &da9062_config }, + { }, +}; + +MODULE_DEVICE_TABLE(of, da9062_compatible_reg_id_table); + +static int da9062_thermal_probe(struct platform_device *pdev) +{ + struct da9062 *chip = dev_get_drvdata(pdev->dev.parent); + struct da9062_thermal *thermal; + unsigned int pp_tmp = DA9062_DEFAULT_POLLING_MS_PERIOD; + const struct of_device_id *match; + int ret = 0; + + match = of_match_node(da9062_compatible_reg_id_table, + pdev->dev.of_node); + if (!match) + return -ENXIO; + + if (pdev->dev.of_node) { + if (!of_property_read_u32(pdev->dev.of_node, + "polling-delay-passive", + &pp_tmp)) { + if (pp_tmp < DA9062_MIN_POLLING_MS_PERIOD || + pp_tmp > DA9062_MAX_POLLING_MS_PERIOD) { + dev_warn(&pdev->dev, + "Out-of-range polling period %d ms\n", + pp_tmp); + pp_tmp = DA9062_DEFAULT_POLLING_MS_PERIOD; + } + } + } + + thermal = devm_kzalloc(&pdev->dev, sizeof(struct da9062_thermal), + GFP_KERNEL); + if (!thermal) { + ret = -ENOMEM; + goto err; + } + + thermal->config = match->data; + thermal->hw = chip; + thermal->mode = THERMAL_DEVICE_ENABLED; + thermal->dev = &pdev->dev; + + INIT_DELAYED_WORK(&thermal->work, da9062_thermal_poll_on); + mutex_init(&thermal->lock); + + thermal->zone = thermal_zone_device_register(thermal->config->name, + 1, 0, thermal, + &da9062_thermal_ops, NULL, pp_tmp, + 0); + if (IS_ERR(thermal->zone)) { + dev_err(&pdev->dev, "Cannot register thermal zone device\n"); + ret = PTR_ERR(thermal->zone); + goto err; + } + + dev_dbg(&pdev->dev, + "TJUNC temperature polling period set at %d ms\n", + thermal->zone->passive_delay); + + ret = platform_get_irq_byname(pdev, "THERMAL"); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to get platform IRQ.\n"); + goto err_zone; + } + thermal->irq = ret; + + ret = request_threaded_irq(thermal->irq, NULL, + da9062_thermal_irq_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "THERMAL", thermal); + if (ret) { + dev |
