From patchwork Mon Jun 5 21:09:30 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Markus Mayer X-Patchwork-Id: 9767577 X-Patchwork-Delegate: rui.zhang@intel.com Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id C7FD260353 for ; Mon, 5 Jun 2017 21:11:08 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id B9002283AF for ; Mon, 5 Jun 2017 21:11:08 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id AD8522842E; Mon, 5 Jun 2017 21:11:08 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.9 required=2.0 tests=BAYES_00,RCVD_IN_DNSWL_HI autolearn=unavailable version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id E7B85283CF for ; Mon, 5 Jun 2017 21:11:07 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751379AbdFEVKy (ORCPT ); Mon, 5 Jun 2017 17:10:54 -0400 Received: from smtp-out-so.shaw.ca ([64.59.136.137]:54792 "EHLO smtp-out-so.shaw.ca" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751321AbdFEVKw (ORCPT ); Mon, 5 Jun 2017 17:10:52 -0400 Received: from triton.mmayer.net ([96.48.165.49]) by shaw.ca with SMTP id HzGudeRweyd2DHzGvdc1v9; Mon, 05 Jun 2017 15:10:46 -0600 X-Authority-Analysis: v=2.2 cv=F5wnTupN c=1 sm=1 tr=0 a=k5HOQ6ZN7M0zyjl8M7O1NA==:117 a=k5HOQ6ZN7M0zyjl8M7O1NA==:17 a=LWSFodeU3zMA:10 a=pGLkceISAAAA:8 a=Q-fNiiVtAAAA:8 a=-fvZxFZip4yRNGWWyFMA:9 a=6kGIvZw6iX1k4Y-7sg4_:22 a=Fp8MccfUoT0GBdDC_Lng:22 Received: by triton.mmayer.net (Postfix, from userid 501) id 9E3E44882695; Mon, 5 Jun 2017 14:10:44 -0700 (PDT) From: Markus Mayer To: Zhang Rui , Eduardo Valentin , Rob Herring , Mark Rutland , Doug Berger , Brian Norris , Gregory Fong , Florian Fainelli , =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= Cc: Broadcom Kernel List , Power Management List , Device Tree List , ARM Kernel List , Linux Kernel Mailing List , Markus Mayer Subject: [PATCH 2/2] thermal: add brcmstb AVS TMON driver Date: Mon, 5 Jun 2017 14:09:30 -0700 Message-Id: <20170605210930.65432-3-code@mmayer.net> X-Mailer: git-send-email 2.13.0 In-Reply-To: <20170605210930.65432-1-code@mmayer.net> References: <20170605210930.65432-1-code@mmayer.net> X-CMAE-Envelope: MS4wfDxTHkRpvNglK2maUhtg7HubXH4rqZWJnHCMyURqQdpAiUw/YjrAYO0GxlSDk8cMY9NHeF8zK6clGBhY8wnvavZaIAREVdaCjCdTbxxQeZvICdIeqjNC ZZ5rq3GSsDb9okyL69gBFzmPV4Fji9h1mJ52i8LolKkAgFYtiKvRKqtKerqy3l5Ko3Db6BBcooIFKfrkm2OquelmJK0hyUU1cXYEnPrZ+A+f3UI/yaNz/Sqj iYf0uL/vnyupHbpKEcMoaQzhV8EQKyZzikdpg3NdxKy8RxZ4Wh2AsaCqAOZhXp5r0iqhmHqy+1dkJ73ZhUb/Hn7rn/7tOaQsefyTDGSzf1hGfkXynseuqUoc VrFmvejvLhJ8n1J13z2/UQwHP95T9HtayiXXz/SKJrUENWczEq+h2ylyyfuVitko+G3Pxi+wdNP4Qc2taqapwKQdXiIeAmeLiBSC6XknQ5y5lG8eGSUU6NkH V2eWCKIqjXqOKaNRTcSIDg1axPdD6HcQm+lRvIFHDjXMWSW7y81VMLmUK2ddtu0/vGlFGe8KEXVbhzIdQXygss6QIvgPecgmSY7g2TrRw1CGwmKTYnhRTaAD D49tW0aC6mgDIrx6wmgFPsZDl5ACm1Gubt6efaYylxuQmkq8naXez4OuFa0/ekkSkMUT/cv15Dzi6dssT1BiDAq1 Sender: linux-pm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP 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/broadcom/Kconfig | 7 + drivers/thermal/broadcom/Makefile | 1 + drivers/thermal/broadcom/brcmstb_thermal.c | 361 +++++++++++++++++++++++++++++ 3 files changed, 369 insertions(+) create mode 100644 drivers/thermal/broadcom/brcmstb_thermal.c diff --git a/drivers/thermal/broadcom/Kconfig b/drivers/thermal/broadcom/Kconfig index ab08af4..12e621d 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..027648c --- /dev/null +++ b/drivers/thermal/broadcom/brcmstb_thermal.c @@ -0,0 +1,361 @@ +/* + * 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 + +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; +}; + +/* Convert a HW code to a temperature reading (millidegree celsius) */ +static inline int avs_tmon_code_to_temp(u32 code) +{ + return (410040 - (int)((code & 0x3FF) * 487)); +} + +/* + * 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(int temp, bool low) +{ + if (temp < -88161) + return 0x3FF; /* Maximum code value */ + + if (temp >= 410040) + return 0; /* Minimum code value */ + + if (low) + return (u32)(DIV_ROUND_UP(410040 - temp, 487)); + else + return (u32)((410040 - temp) / 487); +} + +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(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); + + pr_debug("%s trip, type %d\n", en ? "enable" : "disable", 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(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; + + pr_debug("set temp %d to %d\n", type, temp); + + /* round toward low temp for the low interrupt */ + val = avs_tmon_temp_to_code(temp, type == TMON_TRIP_TYPE_LOW); + + /* TODO: Check for overflow? */ + 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(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; + + pr_debug("set trips %d <--> %d\n", low, high); + + if (low) { + if (low > INT_MAX) + low = INT_MAX; + avs_tmon_set_trip_temp(priv, TMON_TRIP_TYPE_LOW, (int)low); + avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_LOW, 1); + } else { + avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_LOW, 0); + } + + if (high < ULONG_MAX) { + if (high > INT_MAX) + high = INT_MAX; + avs_tmon_set_trip_temp(priv, TMON_TRIP_TYPE_HIGH, (int)high); + avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_HIGH, 1); + } else { + avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_HIGH, 0); + } + + 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); + goto err; + } + + 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, priv->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, + .owner = THIS_MODULE, + .of_match_table = brcmstb_thermal_id_table, + }, +}; +module_platform_driver(brcmstb_thermal_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Brian Norris"); +MODULE_DESCRIPTION("Broadcom STB AVS TMON thermal driver");