Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1032170AbdIZVc4 (ORCPT ); Tue, 26 Sep 2017 17:32:56 -0400 Received: from mail-qk0-f169.google.com ([209.85.220.169]:57127 "EHLO mail-qk0-f169.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S967113AbdIZVcy (ORCPT ); Tue, 26 Sep 2017 17:32:54 -0400 X-Google-Smtp-Source: AOwi7QAvPLDCW4VHiyObFPevsH4nL3Hq48tZZ/9rExt1h1q9+THw09q3cyx2ZGAu01YUrlgWTPgytA== Subject: Re: [PATCH v5 2/2] thermal: add brcmstb AVS TMON driver To: Markus Mayer , Zhang Rui , Eduardo Valentin , Rob Herring , Mark Rutland , Doug Berger , Brian Norris , Gregory Fong , Florian Fainelli , =?UTF-8?B?UmFmYcWCIE1pxYJlY2tp?= References: <20170926212800.4879-1-code@mmayer.net> <20170926212800.4879-3-code@mmayer.net> Cc: Broadcom Kernel List , Power Management List , Device Tree List , ARM Kernel List , Linux Kernel Mailing List , Markus Mayer From: Scott Branden Message-ID: <4cb98266-6936-7668-fc88-199ac907b437@broadcom.com> Date: Tue, 26 Sep 2017 14:32:47 -0700 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.8.0 MIME-Version: 1.0 In-Reply-To: <20170926212800.4879-3-code@mmayer.net> Content-Type: text/plain; charset=windows-1252; format=flowed Content-Transfer-Encoding: 7bit Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 14427 Lines: 462 Hi Markus, On 17-09-26 02:27 PM, Markus Mayer wrote: > From: Brian Norris > > The AVS TMON core provides temperature readings, a pair of configurable > high- and low-temperature threshold interrupts, and an emergency > over-temperature chip reset. The driver utilizes the first two to > provide temperature readings and high-temperature notifications to > applications. The over-temperature reset is not exposed to > applications; this reset threshold is critical to the system and should > be set with care within the bootloader. > > Applications may choose to utilize the notification mechanism, the > temperature reading mechanism (e.g., through polling), or both. > > Signed-off-by: Brian Norris > Signed-off-by: Doug Berger > Signed-off-by: Markus Mayer > --- > drivers/thermal/Kconfig | 2 +- > drivers/thermal/broadcom/Kconfig | 7 + > drivers/thermal/broadcom/Makefile | 1 + > drivers/thermal/broadcom/brcmstb_thermal.c | 387 +++++++++++++++++++++++++++++ > 4 files changed, 396 insertions(+), 1 deletion(-) > create mode 100644 drivers/thermal/broadcom/brcmstb_thermal.c > > diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig > index 07002df..96774a7 100644 > --- a/drivers/thermal/Kconfig > +++ b/drivers/thermal/Kconfig > @@ -408,7 +408,7 @@ config MTK_THERMAL > controller present in Mediatek SoCs > > menu "Broadcom thermal drivers" > -depends on ARCH_BCM || COMPILE_TEST > +depends on ARCH_BCM || ARCH_BRCMSTB || COMPILE_TEST No need for this additional depends. ARCH_BCM is always defined before ARCH_BRCMSTB can be selected. > source "drivers/thermal/broadcom/Kconfig" > endmenu > > diff --git a/drivers/thermal/broadcom/Kconfig b/drivers/thermal/broadcom/Kconfig > index 42c098e..c106a15 100644 > --- a/drivers/thermal/broadcom/Kconfig > +++ b/drivers/thermal/broadcom/Kconfig > @@ -6,6 +6,13 @@ config BCM2835_THERMAL > help > Support for thermal sensors on Broadcom bcm2835 SoCs. > > +config BRCMSTB_THERMAL > + tristate "Broadcom STB AVS TMON thermal driver" > + depends on ARCH_BRCMSTB || COMPILE_TEST > + help > + Enable this driver if you have a Broadcom STB SoC and would like > + thermal framework support. > + > config BCM_NS_THERMAL > tristate "Northstar thermal driver" > depends on ARCH_BCM_IPROC || COMPILE_TEST > diff --git a/drivers/thermal/broadcom/Makefile b/drivers/thermal/broadcom/Makefile > index c6f62e4..fae10ec 100644 > --- a/drivers/thermal/broadcom/Makefile > +++ b/drivers/thermal/broadcom/Makefile > @@ -1,2 +1,3 @@ > obj-$(CONFIG_BCM2835_THERMAL) += bcm2835_thermal.o > +obj-$(CONFIG_BRCMSTB_THERMAL) += brcmstb_thermal.o > obj-$(CONFIG_BCM_NS_THERMAL) += ns-thermal.o > diff --git a/drivers/thermal/broadcom/brcmstb_thermal.c b/drivers/thermal/broadcom/brcmstb_thermal.c > new file mode 100644 > index 0000000..1919f91 > --- /dev/null > +++ b/drivers/thermal/broadcom/brcmstb_thermal.c > @@ -0,0 +1,387 @@ > +/* > + * Broadcom STB AVS TMON thermal sensor driver > + * > + * Copyright (c) 2015-2017 Broadcom > + * > + * 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. > + * > + */ > + > +#define DRV_NAME "brcmstb_thermal" > + > +#define pr_fmt(fmt) DRV_NAME ": " fmt > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#define AVS_TMON_STATUS 0x00 > + #define AVS_TMON_STATUS_valid_msk BIT(11) > + #define AVS_TMON_STATUS_data_msk GENMASK(10, 1) > + #define AVS_TMON_STATUS_data_shift 1 > + > +#define AVS_TMON_EN_OVERTEMP_RESET 0x04 > + #define AVS_TMON_EN_OVERTEMP_RESET_msk BIT(0) > + > +#define AVS_TMON_RESET_THRESH 0x08 > + #define AVS_TMON_RESET_THRESH_msk GENMASK(10, 1) > + #define AVS_TMON_RESET_THRESH_shift 1 > + > +#define AVS_TMON_INT_IDLE_TIME 0x10 > + > +#define AVS_TMON_EN_TEMP_INT_SRCS 0x14 > + #define AVS_TMON_EN_TEMP_INT_SRCS_high BIT(1) > + #define AVS_TMON_EN_TEMP_INT_SRCS_low BIT(0) > + > +#define AVS_TMON_INT_THRESH 0x18 > + #define AVS_TMON_INT_THRESH_high_msk GENMASK(26, 17) > + #define AVS_TMON_INT_THRESH_high_shift 17 > + #define AVS_TMON_INT_THRESH_low_msk GENMASK(10, 1) > + #define AVS_TMON_INT_THRESH_low_shift 1 > + > +#define AVS_TMON_TEMP_INT_CODE 0x1c > +#define AVS_TMON_TP_TEST_ENABLE 0x20 > + > +/* Default coefficients */ > +#define AVS_TMON_TEMP_SLOPE -487 > +#define AVS_TMON_TEMP_OFFSET 410040 > + > +/* HW related temperature constants */ > +#define AVS_TMON_TEMP_MAX 0x3ff > +#define AVS_TMON_TEMP_MIN -88161 > +#define AVS_TMON_TEMP_MASK AVS_TMON_TEMP_MAX > + > +enum avs_tmon_trip_type { > + TMON_TRIP_TYPE_LOW = 0, > + TMON_TRIP_TYPE_HIGH, > + TMON_TRIP_TYPE_RESET, > + TMON_TRIP_TYPE_MAX, > +}; > + > +struct avs_tmon_trip { > + /* HW bit to enable the trip */ > + u32 enable_offs; > + u32 enable_mask; > + > + /* HW field to read the trip temperature */ > + u32 reg_offs; > + u32 reg_msk; > + int reg_shift; > +}; > + > +static struct avs_tmon_trip avs_tmon_trips[] = { > + /* Trips when temperature is below threshold */ > + [TMON_TRIP_TYPE_LOW] = { > + .enable_offs = AVS_TMON_EN_TEMP_INT_SRCS, > + .enable_mask = AVS_TMON_EN_TEMP_INT_SRCS_low, > + .reg_offs = AVS_TMON_INT_THRESH, > + .reg_msk = AVS_TMON_INT_THRESH_low_msk, > + .reg_shift = AVS_TMON_INT_THRESH_low_shift, > + }, > + /* Trips when temperature is above threshold */ > + [TMON_TRIP_TYPE_HIGH] = { > + .enable_offs = AVS_TMON_EN_TEMP_INT_SRCS, > + .enable_mask = AVS_TMON_EN_TEMP_INT_SRCS_high, > + .reg_offs = AVS_TMON_INT_THRESH, > + .reg_msk = AVS_TMON_INT_THRESH_high_msk, > + .reg_shift = AVS_TMON_INT_THRESH_high_shift, > + }, > + /* Automatically resets chip when above threshold */ > + [TMON_TRIP_TYPE_RESET] = { > + .enable_offs = AVS_TMON_EN_OVERTEMP_RESET, > + .enable_mask = AVS_TMON_EN_OVERTEMP_RESET_msk, > + .reg_offs = AVS_TMON_RESET_THRESH, > + .reg_msk = AVS_TMON_RESET_THRESH_msk, > + .reg_shift = AVS_TMON_RESET_THRESH_shift, > + }, > +}; > + > +struct brcmstb_thermal_priv { > + void __iomem *tmon_base; > + struct device *dev; > + struct thermal_zone_device *thermal; > +}; > + > +static void avs_tmon_get_coeffs(struct thermal_zone_device *tz, int *slope, > + int *offset) > +{ > + *slope = thermal_zone_get_slope(tz); > + *offset = thermal_zone_get_offset(tz); > +} > + > +/* Convert a HW code to a temperature reading (millidegree celsius) */ > +static inline int avs_tmon_code_to_temp(struct thermal_zone_device *tz, > + u32 code) > +{ > + const int val = code & AVS_TMON_TEMP_MASK; > + int slope, offset; > + > + avs_tmon_get_coeffs(tz, &slope, &offset); > + > + return slope * val + offset; > +} > + > +/* > + * Convert a temperature value (millidegree celsius) to a HW code > + * > + * @temp: temperature to convert > + * @low: if true, round toward the low side > + */ > +static inline u32 avs_tmon_temp_to_code(struct thermal_zone_device *tz, > + int temp, bool low) > +{ > + int slope, offset; > + > + if (temp < AVS_TMON_TEMP_MIN) > + return AVS_TMON_TEMP_MAX; /* Maximum code value */ > + > + avs_tmon_get_coeffs(tz, &slope, &offset); > + > + if (temp >= offset) > + return 0; /* Minimum code value */ > + > + if (low) > + return (u32)(DIV_ROUND_UP(offset - temp, abs(slope))); > + else > + return (u32)((offset - temp) / abs(slope)); > +} > + > +static int brcmstb_get_temp(void *data, int *temp) > +{ > + struct brcmstb_thermal_priv *priv = data; > + u32 val; > + long t; > + > + val = __raw_readl(priv->tmon_base + AVS_TMON_STATUS); > + > + if (!(val & AVS_TMON_STATUS_valid_msk)) { > + dev_err(priv->dev, "reading not valid\n"); > + return -EIO; > + } > + > + val = (val & AVS_TMON_STATUS_data_msk) >> AVS_TMON_STATUS_data_shift; > + > + t = avs_tmon_code_to_temp(priv->thermal, val); > + if (t < 0) > + *temp = 0; > + else > + *temp = t; > + > + return 0; > +} > + > +static void avs_tmon_trip_enable(struct brcmstb_thermal_priv *priv, > + enum avs_tmon_trip_type type, int en) > +{ > + struct avs_tmon_trip *trip = &avs_tmon_trips[type]; > + u32 val = __raw_readl(priv->tmon_base + trip->enable_offs); > + > + dev_dbg(priv->dev, "%sable trip, type %d\n", en ? "en" : "dis", type); > + > + if (en) > + val |= trip->enable_mask; > + else > + val &= ~trip->enable_mask; > + > + __raw_writel(val, priv->tmon_base + trip->enable_offs); > +} > + > +static int avs_tmon_get_trip_temp(struct brcmstb_thermal_priv *priv, > + enum avs_tmon_trip_type type) > +{ > + struct avs_tmon_trip *trip = &avs_tmon_trips[type]; > + u32 val = __raw_readl(priv->tmon_base + trip->reg_offs); > + > + val &= trip->reg_msk; > + val >>= trip->reg_shift; > + > + return avs_tmon_code_to_temp(priv->thermal, val); > +} > + > +static void avs_tmon_set_trip_temp(struct brcmstb_thermal_priv *priv, > + enum avs_tmon_trip_type type, > + int temp) > +{ > + struct avs_tmon_trip *trip = &avs_tmon_trips[type]; > + u32 val, orig; > + > + dev_dbg(priv->dev, "set temp %d to %d\n", type, temp); > + > + /* round toward low temp for the low interrupt */ > + val = avs_tmon_temp_to_code(priv->thermal, temp, > + type == TMON_TRIP_TYPE_LOW); > + > + val <<= trip->reg_shift; > + val &= trip->reg_msk; > + > + orig = __raw_readl(priv->tmon_base + trip->reg_offs); > + orig &= ~trip->reg_msk; > + orig |= val; > + __raw_writel(orig, priv->tmon_base + trip->reg_offs); > +} > + > +static int avs_tmon_get_intr_temp(struct brcmstb_thermal_priv *priv) > +{ > + u32 val; > + > + val = __raw_readl(priv->tmon_base + AVS_TMON_TEMP_INT_CODE); > + return avs_tmon_code_to_temp(priv->thermal, val); > +} > + > +static irqreturn_t brcmstb_tmon_irq_thread(int irq, void *data) > +{ > + struct brcmstb_thermal_priv *priv = data; > + int low, high, intr; > + > + low = avs_tmon_get_trip_temp(priv, TMON_TRIP_TYPE_LOW); > + high = avs_tmon_get_trip_temp(priv, TMON_TRIP_TYPE_HIGH); > + intr = avs_tmon_get_intr_temp(priv); > + > + dev_dbg(priv->dev, "low/intr/high: %d/%d/%d\n", > + low, intr, high); > + > + /* Disable high-temp until next threshold shift */ > + if (intr >= high) > + avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_HIGH, 0); > + /* Disable low-temp until next threshold shift */ > + if (intr <= low) > + avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_LOW, 0); > + > + /* > + * Notify using the interrupt temperature, in case the temperature > + * changes before it can next be read out > + */ > + thermal_zone_device_update(priv->thermal, intr); > + > + return IRQ_HANDLED; > +} > + > +static int brcmstb_set_trips(void *data, int low, int high) > +{ > + struct brcmstb_thermal_priv *priv = data; > + > + dev_dbg(priv->dev, "set trips %d <--> %d\n", low, high); > + > + /* > + * Disable low-temp if "low" is too small. As per thermal framework > + * API, we use -INT_MAX rather than INT_MIN. > + */ > + if (low <= -INT_MAX) { > + avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_LOW, 0); > + } else { > + avs_tmon_set_trip_temp(priv, TMON_TRIP_TYPE_LOW, low); > + avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_LOW, 1); > + } > + > + /* Disable high-temp if "high" is too big. */ > + if (high == INT_MAX) { > + avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_HIGH, 0); > + } else { > + avs_tmon_set_trip_temp(priv, TMON_TRIP_TYPE_HIGH, high); > + avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_HIGH, 1); > + } > + > + return 0; > +} > + > +static struct thermal_zone_of_device_ops of_ops = { > + .get_temp = brcmstb_get_temp, > + .set_trips = brcmstb_set_trips, > +}; > + > +static const struct of_device_id brcmstb_thermal_id_table[] = { > + { .compatible = "brcm,avs-tmon" }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, brcmstb_thermal_id_table); > + > +static int brcmstb_thermal_probe(struct platform_device *pdev) > +{ > + struct thermal_zone_device *thermal; > + struct brcmstb_thermal_priv *priv; > + struct resource *res; > + int irq, ret; > + > + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); > + if (!priv) > + return -ENOMEM; > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + priv->tmon_base = devm_ioremap_resource(&pdev->dev, res); > + if (IS_ERR(priv->tmon_base)) > + return PTR_ERR(priv->tmon_base); > + > + priv->dev = &pdev->dev; > + platform_set_drvdata(pdev, priv); > + > + thermal = thermal_zone_of_sensor_register(&pdev->dev, 0, priv, &of_ops); > + if (IS_ERR(thermal)) { > + ret = PTR_ERR(thermal); > + dev_err(&pdev->dev, "could not register sensor: %d\n", ret); > + return ret; > + } > + > + priv->thermal = thermal; > + > + irq = platform_get_irq(pdev, 0); > + if (irq < 0) { > + dev_err(&pdev->dev, "could not get IRQ\n"); > + ret = irq; > + goto err; > + } > + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, > + brcmstb_tmon_irq_thread, IRQF_ONESHOT, > + DRV_NAME, priv); > + if (ret < 0) { > + dev_err(&pdev->dev, "could not request IRQ: %d\n", ret); > + goto err; > + } > + > + dev_info(&pdev->dev, "registered AVS TMON of-sensor driver\n"); > + > + return 0; > + > +err: > + thermal_zone_of_sensor_unregister(&pdev->dev, thermal); > + return ret; > +} > + > +static int brcmstb_thermal_exit(struct platform_device *pdev) > +{ > + struct brcmstb_thermal_priv *priv = platform_get_drvdata(pdev); > + struct thermal_zone_device *thermal = priv->thermal; > + > + if (thermal) > + thermal_zone_of_sensor_unregister(&pdev->dev, priv->thermal); > + > + return 0; > +} > + > +static struct platform_driver brcmstb_thermal_driver = { > + .probe = brcmstb_thermal_probe, > + .remove = brcmstb_thermal_exit, > + .driver = { > + .name = DRV_NAME, > + .of_match_table = brcmstb_thermal_id_table, > + }, > +}; > +module_platform_driver(brcmstb_thermal_driver); > + > +MODULE_LICENSE("GPL v2"); > +MODULE_AUTHOR("Brian Norris"); > +MODULE_DESCRIPTION("Broadcom STB AVS TMON thermal driver");