Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S937562AbdIZWIh (ORCPT ); Tue, 26 Sep 2017 18:08:37 -0400 Received: from mail-qt0-f178.google.com ([209.85.216.178]:55213 "EHLO mail-qt0-f178.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S935403AbdIZWIf (ORCPT ); Tue, 26 Sep 2017 18:08:35 -0400 X-Google-Smtp-Source: AOwi7QBaPeHJPBHk6cp7rv0DeJ3jGc85egoNPj4I8AeoUYjWNulqQs9Yqs/1gAXIAY8kjbM1TCLanA== Subject: Re: [PATCH v5 2/2] thermal: add brcmstb AVS TMON driver To: Markus Mayer References: <20170926212800.4879-1-code@mmayer.net> <20170926212800.4879-3-code@mmayer.net> <4cb98266-6936-7668-fc88-199ac907b437@broadcom.com> Cc: Zhang Rui , Eduardo Valentin , Rob Herring , Mark Rutland , Doug Berger , Brian Norris , Gregory Fong , Florian Fainelli , =?UTF-8?B?UmFmYcWCIE1pxYJlY2tp?= , Broadcom Kernel List , Power Management List , Device Tree List , ARM Kernel List , Linux Kernel Mailing List , Markus Mayer From: Scott Branden Message-ID: <9bdd9959-da47-9adb-e550-31bf06254310@broadcom.com> Date: Tue, 26 Sep 2017 15:08:28 -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: Content-Type: text/plain; charset=utf-8; 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: 18097 Lines: 488 On 17-09-26 02:38 PM, Markus Mayer wrote: > On 26 September 2017 at 14:32, Scott Branden wrote: >> 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. > ARCH_BCM does not exist in arch/arm64/configs/defconfig. ARCH_BRCMSTB > does. So, we do need both or the driver won't be built on ARM64. > (After internal discussions we went with that approach rather than > defining ARCH_BCM on ARM64.) Got it. Looking at our internal iproc tree I see we've done exactly the same with ARCH_BCM_IPROC needing to be added. We haven't upstreamed the thermal driver needing it yet. Perhaps we should add ARCH_BCM to ARM64.... > >>> 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"); >>