linux/drivers/thermal/broadcom/brcmstb_thermal.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Broadcom STB AVS TMON thermal sensor driver
   4 *
   5 * Copyright (c) 2015-2017 Broadcom
   6 */
   7
   8#define DRV_NAME        "brcmstb_thermal"
   9
  10#define pr_fmt(fmt)     DRV_NAME ": " fmt
  11
  12#include <linux/bitops.h>
  13#include <linux/device.h>
  14#include <linux/err.h>
  15#include <linux/io.h>
  16#include <linux/irqreturn.h>
  17#include <linux/interrupt.h>
  18#include <linux/kernel.h>
  19#include <linux/module.h>
  20#include <linux/platform_device.h>
  21#include <linux/of_device.h>
  22#include <linux/thermal.h>
  23
  24#define AVS_TMON_STATUS                 0x00
  25 #define AVS_TMON_STATUS_valid_msk      BIT(11)
  26 #define AVS_TMON_STATUS_data_msk       GENMASK(10, 1)
  27 #define AVS_TMON_STATUS_data_shift     1
  28
  29#define AVS_TMON_EN_OVERTEMP_RESET      0x04
  30 #define AVS_TMON_EN_OVERTEMP_RESET_msk BIT(0)
  31
  32#define AVS_TMON_RESET_THRESH           0x08
  33 #define AVS_TMON_RESET_THRESH_msk      GENMASK(10, 1)
  34 #define AVS_TMON_RESET_THRESH_shift    1
  35
  36#define AVS_TMON_INT_IDLE_TIME          0x10
  37
  38#define AVS_TMON_EN_TEMP_INT_SRCS       0x14
  39 #define AVS_TMON_EN_TEMP_INT_SRCS_high BIT(1)
  40 #define AVS_TMON_EN_TEMP_INT_SRCS_low  BIT(0)
  41
  42#define AVS_TMON_INT_THRESH             0x18
  43 #define AVS_TMON_INT_THRESH_high_msk   GENMASK(26, 17)
  44 #define AVS_TMON_INT_THRESH_high_shift 17
  45 #define AVS_TMON_INT_THRESH_low_msk    GENMASK(10, 1)
  46 #define AVS_TMON_INT_THRESH_low_shift  1
  47
  48#define AVS_TMON_TEMP_INT_CODE          0x1c
  49#define AVS_TMON_TP_TEST_ENABLE         0x20
  50
  51/* Default coefficients */
  52#define AVS_TMON_TEMP_SLOPE             487
  53#define AVS_TMON_TEMP_OFFSET            410040
  54
  55/* HW related temperature constants */
  56#define AVS_TMON_TEMP_MAX               0x3ff
  57#define AVS_TMON_TEMP_MIN               -88161
  58#define AVS_TMON_TEMP_MASK              AVS_TMON_TEMP_MAX
  59
  60enum avs_tmon_trip_type {
  61        TMON_TRIP_TYPE_LOW = 0,
  62        TMON_TRIP_TYPE_HIGH,
  63        TMON_TRIP_TYPE_RESET,
  64        TMON_TRIP_TYPE_MAX,
  65};
  66
  67struct avs_tmon_trip {
  68        /* HW bit to enable the trip */
  69        u32 enable_offs;
  70        u32 enable_mask;
  71
  72        /* HW field to read the trip temperature */
  73        u32 reg_offs;
  74        u32 reg_msk;
  75        int reg_shift;
  76};
  77
  78static struct avs_tmon_trip avs_tmon_trips[] = {
  79        /* Trips when temperature is below threshold */
  80        [TMON_TRIP_TYPE_LOW] = {
  81                .enable_offs    = AVS_TMON_EN_TEMP_INT_SRCS,
  82                .enable_mask    = AVS_TMON_EN_TEMP_INT_SRCS_low,
  83                .reg_offs       = AVS_TMON_INT_THRESH,
  84                .reg_msk        = AVS_TMON_INT_THRESH_low_msk,
  85                .reg_shift      = AVS_TMON_INT_THRESH_low_shift,
  86        },
  87        /* Trips when temperature is above threshold */
  88        [TMON_TRIP_TYPE_HIGH] = {
  89                .enable_offs    = AVS_TMON_EN_TEMP_INT_SRCS,
  90                .enable_mask    = AVS_TMON_EN_TEMP_INT_SRCS_high,
  91                .reg_offs       = AVS_TMON_INT_THRESH,
  92                .reg_msk        = AVS_TMON_INT_THRESH_high_msk,
  93                .reg_shift      = AVS_TMON_INT_THRESH_high_shift,
  94        },
  95        /* Automatically resets chip when above threshold */
  96        [TMON_TRIP_TYPE_RESET] = {
  97                .enable_offs    = AVS_TMON_EN_OVERTEMP_RESET,
  98                .enable_mask    = AVS_TMON_EN_OVERTEMP_RESET_msk,
  99                .reg_offs       = AVS_TMON_RESET_THRESH,
 100                .reg_msk        = AVS_TMON_RESET_THRESH_msk,
 101                .reg_shift      = AVS_TMON_RESET_THRESH_shift,
 102        },
 103};
 104
 105struct brcmstb_thermal_params {
 106        unsigned int offset;
 107        unsigned int mult;
 108        const struct thermal_zone_of_device_ops *of_ops;
 109};
 110
 111struct brcmstb_thermal_priv {
 112        void __iomem *tmon_base;
 113        struct device *dev;
 114        struct thermal_zone_device *thermal;
 115        /* Process specific thermal parameters used for calculations */
 116        const struct brcmstb_thermal_params *temp_params;
 117};
 118
 119/* Convert a HW code to a temperature reading (millidegree celsius) */
 120static inline int avs_tmon_code_to_temp(struct brcmstb_thermal_priv *priv,
 121                                        u32 code)
 122{
 123        int offset = priv->temp_params->offset;
 124        int mult = priv->temp_params->mult;
 125
 126        return (offset - (int)((code & AVS_TMON_TEMP_MASK) * mult));
 127}
 128
 129/*
 130 * Convert a temperature value (millidegree celsius) to a HW code
 131 *
 132 * @temp: temperature to convert
 133 * @low: if true, round toward the low side
 134 */
 135static inline u32 avs_tmon_temp_to_code(struct brcmstb_thermal_priv *priv,
 136                                        int temp, bool low)
 137{
 138        int offset = priv->temp_params->offset;
 139        int mult = priv->temp_params->mult;
 140
 141        if (temp < AVS_TMON_TEMP_MIN)
 142                return AVS_TMON_TEMP_MAX;       /* Maximum code value */
 143
 144        if (temp >= offset)
 145                return 0;       /* Minimum code value */
 146
 147        if (low)
 148                return (u32)(DIV_ROUND_UP(offset - temp, mult));
 149        else
 150                return (u32)((offset - temp) / mult);
 151}
 152
 153static int brcmstb_get_temp(void *data, int *temp)
 154{
 155        struct brcmstb_thermal_priv *priv = data;
 156        u32 val;
 157        long t;
 158
 159        val = __raw_readl(priv->tmon_base + AVS_TMON_STATUS);
 160
 161        if (!(val & AVS_TMON_STATUS_valid_msk)) {
 162                dev_err(priv->dev, "reading not valid\n");
 163                return -EIO;
 164        }
 165
 166        val = (val & AVS_TMON_STATUS_data_msk) >> AVS_TMON_STATUS_data_shift;
 167
 168        t = avs_tmon_code_to_temp(priv, val);
 169        if (t < 0)
 170                *temp = 0;
 171        else
 172                *temp = t;
 173
 174        return 0;
 175}
 176
 177static void avs_tmon_trip_enable(struct brcmstb_thermal_priv *priv,
 178                                 enum avs_tmon_trip_type type, int en)
 179{
 180        struct avs_tmon_trip *trip = &avs_tmon_trips[type];
 181        u32 val = __raw_readl(priv->tmon_base + trip->enable_offs);
 182
 183        dev_dbg(priv->dev, "%sable trip, type %d\n", en ? "en" : "dis", type);
 184
 185        if (en)
 186                val |= trip->enable_mask;
 187        else
 188                val &= ~trip->enable_mask;
 189
 190        __raw_writel(val, priv->tmon_base + trip->enable_offs);
 191}
 192
 193static int avs_tmon_get_trip_temp(struct brcmstb_thermal_priv *priv,
 194                                  enum avs_tmon_trip_type type)
 195{
 196        struct avs_tmon_trip *trip = &avs_tmon_trips[type];
 197        u32 val = __raw_readl(priv->tmon_base + trip->reg_offs);
 198
 199        val &= trip->reg_msk;
 200        val >>= trip->reg_shift;
 201
 202        return avs_tmon_code_to_temp(priv, val);
 203}
 204
 205static void avs_tmon_set_trip_temp(struct brcmstb_thermal_priv *priv,
 206                                   enum avs_tmon_trip_type type,
 207                                   int temp)
 208{
 209        struct avs_tmon_trip *trip = &avs_tmon_trips[type];
 210        u32 val, orig;
 211
 212        dev_dbg(priv->dev, "set temp %d to %d\n", type, temp);
 213
 214        /* round toward low temp for the low interrupt */
 215        val = avs_tmon_temp_to_code(priv, temp,
 216                                    type == TMON_TRIP_TYPE_LOW);
 217
 218        val <<= trip->reg_shift;
 219        val &= trip->reg_msk;
 220
 221        orig = __raw_readl(priv->tmon_base + trip->reg_offs);
 222        orig &= ~trip->reg_msk;
 223        orig |= val;
 224        __raw_writel(orig, priv->tmon_base + trip->reg_offs);
 225}
 226
 227static int avs_tmon_get_intr_temp(struct brcmstb_thermal_priv *priv)
 228{
 229        u32 val;
 230
 231        val = __raw_readl(priv->tmon_base + AVS_TMON_TEMP_INT_CODE);
 232        return avs_tmon_code_to_temp(priv, val);
 233}
 234
 235static irqreturn_t brcmstb_tmon_irq_thread(int irq, void *data)
 236{
 237        struct brcmstb_thermal_priv *priv = data;
 238        int low, high, intr;
 239
 240        low = avs_tmon_get_trip_temp(priv, TMON_TRIP_TYPE_LOW);
 241        high = avs_tmon_get_trip_temp(priv, TMON_TRIP_TYPE_HIGH);
 242        intr = avs_tmon_get_intr_temp(priv);
 243
 244        dev_dbg(priv->dev, "low/intr/high: %d/%d/%d\n",
 245                        low, intr, high);
 246
 247        /* Disable high-temp until next threshold shift */
 248        if (intr >= high)
 249                avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_HIGH, 0);
 250        /* Disable low-temp until next threshold shift */
 251        if (intr <= low)
 252                avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_LOW, 0);
 253
 254        /*
 255         * Notify using the interrupt temperature, in case the temperature
 256         * changes before it can next be read out
 257         */
 258        thermal_zone_device_update(priv->thermal, intr);
 259
 260        return IRQ_HANDLED;
 261}
 262
 263static int brcmstb_set_trips(void *data, int low, int high)
 264{
 265        struct brcmstb_thermal_priv *priv = data;
 266
 267        dev_dbg(priv->dev, "set trips %d <--> %d\n", low, high);
 268
 269        /*
 270         * Disable low-temp if "low" is too small. As per thermal framework
 271         * API, we use -INT_MAX rather than INT_MIN.
 272         */
 273        if (low <= -INT_MAX) {
 274                avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_LOW, 0);
 275        } else {
 276                avs_tmon_set_trip_temp(priv, TMON_TRIP_TYPE_LOW, low);
 277                avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_LOW, 1);
 278        }
 279
 280        /* Disable high-temp if "high" is too big. */
 281        if (high == INT_MAX) {
 282                avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_HIGH, 0);
 283        } else {
 284                avs_tmon_set_trip_temp(priv, TMON_TRIP_TYPE_HIGH, high);
 285                avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_HIGH, 1);
 286        }
 287
 288        return 0;
 289}
 290
 291static const struct thermal_zone_of_device_ops brcmstb_16nm_of_ops = {
 292        .get_temp       = brcmstb_get_temp,
 293};
 294
 295static const struct brcmstb_thermal_params brcmstb_16nm_params = {
 296        .offset = 457829,
 297        .mult   = 557,
 298        .of_ops = &brcmstb_16nm_of_ops,
 299};
 300
 301static const struct thermal_zone_of_device_ops brcmstb_28nm_of_ops = {
 302        .get_temp       = brcmstb_get_temp,
 303        .set_trips      = brcmstb_set_trips,
 304};
 305
 306static const struct brcmstb_thermal_params brcmstb_28nm_params = {
 307        .offset = 410040,
 308        .mult   = 487,
 309        .of_ops = &brcmstb_28nm_of_ops,
 310};
 311
 312static const struct of_device_id brcmstb_thermal_id_table[] = {
 313        { .compatible = "brcm,avs-tmon-bcm7216", .data = &brcmstb_16nm_params },
 314        { .compatible = "brcm,avs-tmon", .data = &brcmstb_28nm_params },
 315        {},
 316};
 317MODULE_DEVICE_TABLE(of, brcmstb_thermal_id_table);
 318
 319static int brcmstb_thermal_probe(struct platform_device *pdev)
 320{
 321        const struct thermal_zone_of_device_ops *of_ops;
 322        struct thermal_zone_device *thermal;
 323        struct brcmstb_thermal_priv *priv;
 324        struct resource *res;
 325        int irq, ret;
 326
 327        priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
 328        if (!priv)
 329                return -ENOMEM;
 330
 331        priv->temp_params = of_device_get_match_data(&pdev->dev);
 332        if (!priv->temp_params)
 333                return -EINVAL;
 334
 335        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 336        priv->tmon_base = devm_ioremap_resource(&pdev->dev, res);
 337        if (IS_ERR(priv->tmon_base))
 338                return PTR_ERR(priv->tmon_base);
 339
 340        priv->dev = &pdev->dev;
 341        platform_set_drvdata(pdev, priv);
 342        of_ops = priv->temp_params->of_ops;
 343
 344        thermal = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, priv,
 345                                                       of_ops);
 346        if (IS_ERR(thermal)) {
 347                ret = PTR_ERR(thermal);
 348                dev_err(&pdev->dev, "could not register sensor: %d\n", ret);
 349                return ret;
 350        }
 351
 352        priv->thermal = thermal;
 353
 354        irq = platform_get_irq(pdev, 0);
 355        if (irq >= 0) {
 356                ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
 357                                                brcmstb_tmon_irq_thread,
 358                                                IRQF_ONESHOT,
 359                                                DRV_NAME, priv);
 360                if (ret < 0) {
 361                        dev_err(&pdev->dev, "could not request IRQ: %d\n", ret);
 362                        return ret;
 363                }
 364        }
 365
 366        dev_info(&pdev->dev, "registered AVS TMON of-sensor driver\n");
 367
 368        return 0;
 369}
 370
 371static struct platform_driver brcmstb_thermal_driver = {
 372        .probe = brcmstb_thermal_probe,
 373        .driver = {
 374                .name = DRV_NAME,
 375                .of_match_table = brcmstb_thermal_id_table,
 376        },
 377};
 378module_platform_driver(brcmstb_thermal_driver);
 379
 380MODULE_LICENSE("GPL v2");
 381MODULE_AUTHOR("Brian Norris");
 382MODULE_DESCRIPTION("Broadcom STB AVS TMON thermal driver");
 383