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_priv {
 106        void __iomem *tmon_base;
 107        struct device *dev;
 108        struct thermal_zone_device *thermal;
 109};
 110
 111static void avs_tmon_get_coeffs(struct thermal_zone_device *tz, int *slope,
 112                                int *offset)
 113{
 114        *slope = thermal_zone_get_slope(tz);
 115        *offset = thermal_zone_get_offset(tz);
 116}
 117
 118/* Convert a HW code to a temperature reading (millidegree celsius) */
 119static inline int avs_tmon_code_to_temp(struct thermal_zone_device *tz,
 120                                        u32 code)
 121{
 122        const int val = code & AVS_TMON_TEMP_MASK;
 123        int slope, offset;
 124
 125        avs_tmon_get_coeffs(tz, &slope, &offset);
 126
 127        return slope * val + offset;
 128}
 129
 130/*
 131 * Convert a temperature value (millidegree celsius) to a HW code
 132 *
 133 * @temp: temperature to convert
 134 * @low: if true, round toward the low side
 135 */
 136static inline u32 avs_tmon_temp_to_code(struct thermal_zone_device *tz,
 137                                        int temp, bool low)
 138{
 139        int slope, offset;
 140
 141        if (temp < AVS_TMON_TEMP_MIN)
 142                return AVS_TMON_TEMP_MAX; /* Maximum code value */
 143
 144        avs_tmon_get_coeffs(tz, &slope, &offset);
 145
 146        if (temp >= offset)
 147                return 0;       /* Minimum code value */
 148
 149        if (low)
 150                return (u32)(DIV_ROUND_UP(offset - temp, abs(slope)));
 151        else
 152                return (u32)((offset - temp) / abs(slope));
 153}
 154
 155static int brcmstb_get_temp(void *data, int *temp)
 156{
 157        struct brcmstb_thermal_priv *priv = data;
 158        u32 val;
 159        long t;
 160
 161        val = __raw_readl(priv->tmon_base + AVS_TMON_STATUS);
 162
 163        if (!(val & AVS_TMON_STATUS_valid_msk)) {
 164                dev_err(priv->dev, "reading not valid\n");
 165                return -EIO;
 166        }
 167
 168        val = (val & AVS_TMON_STATUS_data_msk) >> AVS_TMON_STATUS_data_shift;
 169
 170        t = avs_tmon_code_to_temp(priv->thermal, val);
 171        if (t < 0)
 172                *temp = 0;
 173        else
 174                *temp = t;
 175
 176        return 0;
 177}
 178
 179static void avs_tmon_trip_enable(struct brcmstb_thermal_priv *priv,
 180                                 enum avs_tmon_trip_type type, int en)
 181{
 182        struct avs_tmon_trip *trip = &avs_tmon_trips[type];
 183        u32 val = __raw_readl(priv->tmon_base + trip->enable_offs);
 184
 185        dev_dbg(priv->dev, "%sable trip, type %d\n", en ? "en" : "dis", type);
 186
 187        if (en)
 188                val |= trip->enable_mask;
 189        else
 190                val &= ~trip->enable_mask;
 191
 192        __raw_writel(val, priv->tmon_base + trip->enable_offs);
 193}
 194
 195static int avs_tmon_get_trip_temp(struct brcmstb_thermal_priv *priv,
 196                                  enum avs_tmon_trip_type type)
 197{
 198        struct avs_tmon_trip *trip = &avs_tmon_trips[type];
 199        u32 val = __raw_readl(priv->tmon_base + trip->reg_offs);
 200
 201        val &= trip->reg_msk;
 202        val >>= trip->reg_shift;
 203
 204        return avs_tmon_code_to_temp(priv->thermal, val);
 205}
 206
 207static void avs_tmon_set_trip_temp(struct brcmstb_thermal_priv *priv,
 208                                   enum avs_tmon_trip_type type,
 209                                   int temp)
 210{
 211        struct avs_tmon_trip *trip = &avs_tmon_trips[type];
 212        u32 val, orig;
 213
 214        dev_dbg(priv->dev, "set temp %d to %d\n", type, temp);
 215
 216        /* round toward low temp for the low interrupt */
 217        val = avs_tmon_temp_to_code(priv->thermal, temp,
 218                                    type == TMON_TRIP_TYPE_LOW);
 219
 220        val <<= trip->reg_shift;
 221        val &= trip->reg_msk;
 222
 223        orig = __raw_readl(priv->tmon_base + trip->reg_offs);
 224        orig &= ~trip->reg_msk;
 225        orig |= val;
 226        __raw_writel(orig, priv->tmon_base + trip->reg_offs);
 227}
 228
 229static int avs_tmon_get_intr_temp(struct brcmstb_thermal_priv *priv)
 230{
 231        u32 val;
 232
 233        val = __raw_readl(priv->tmon_base + AVS_TMON_TEMP_INT_CODE);
 234        return avs_tmon_code_to_temp(priv->thermal, val);
 235}
 236
 237static irqreturn_t brcmstb_tmon_irq_thread(int irq, void *data)
 238{
 239        struct brcmstb_thermal_priv *priv = data;
 240        int low, high, intr;
 241
 242        low = avs_tmon_get_trip_temp(priv, TMON_TRIP_TYPE_LOW);
 243        high = avs_tmon_get_trip_temp(priv, TMON_TRIP_TYPE_HIGH);
 244        intr = avs_tmon_get_intr_temp(priv);
 245
 246        dev_dbg(priv->dev, "low/intr/high: %d/%d/%d\n",
 247                        low, intr, high);
 248
 249        /* Disable high-temp until next threshold shift */
 250        if (intr >= high)
 251                avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_HIGH, 0);
 252        /* Disable low-temp until next threshold shift */
 253        if (intr <= low)
 254                avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_LOW, 0);
 255
 256        /*
 257         * Notify using the interrupt temperature, in case the temperature
 258         * changes before it can next be read out
 259         */
 260        thermal_zone_device_update(priv->thermal, intr);
 261
 262        return IRQ_HANDLED;
 263}
 264
 265static int brcmstb_set_trips(void *data, int low, int high)
 266{
 267        struct brcmstb_thermal_priv *priv = data;
 268
 269        dev_dbg(priv->dev, "set trips %d <--> %d\n", low, high);
 270
 271        /*
 272         * Disable low-temp if "low" is too small. As per thermal framework
 273         * API, we use -INT_MAX rather than INT_MIN.
 274         */
 275        if (low <= -INT_MAX) {
 276                avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_LOW, 0);
 277        } else {
 278                avs_tmon_set_trip_temp(priv, TMON_TRIP_TYPE_LOW, low);
 279                avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_LOW, 1);
 280        }
 281
 282        /* Disable high-temp if "high" is too big. */
 283        if (high == INT_MAX) {
 284                avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_HIGH, 0);
 285        } else {
 286                avs_tmon_set_trip_temp(priv, TMON_TRIP_TYPE_HIGH, high);
 287                avs_tmon_trip_enable(priv, TMON_TRIP_TYPE_HIGH, 1);
 288        }
 289
 290        return 0;
 291}
 292
 293static const struct thermal_zone_of_device_ops of_ops = {
 294        .get_temp       = brcmstb_get_temp,
 295        .set_trips      = brcmstb_set_trips,
 296};
 297
 298static const struct of_device_id brcmstb_thermal_id_table[] = {
 299        { .compatible = "brcm,avs-tmon" },
 300        {},
 301};
 302MODULE_DEVICE_TABLE(of, brcmstb_thermal_id_table);
 303
 304static int brcmstb_thermal_probe(struct platform_device *pdev)
 305{
 306        struct thermal_zone_device *thermal;
 307        struct brcmstb_thermal_priv *priv;
 308        struct resource *res;
 309        int irq, ret;
 310
 311        priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
 312        if (!priv)
 313                return -ENOMEM;
 314
 315        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 316        priv->tmon_base = devm_ioremap_resource(&pdev->dev, res);
 317        if (IS_ERR(priv->tmon_base))
 318                return PTR_ERR(priv->tmon_base);
 319
 320        priv->dev = &pdev->dev;
 321        platform_set_drvdata(pdev, priv);
 322
 323        thermal = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, priv,
 324                                                       &of_ops);
 325        if (IS_ERR(thermal)) {
 326                ret = PTR_ERR(thermal);
 327                dev_err(&pdev->dev, "could not register sensor: %d\n", ret);
 328                return ret;
 329        }
 330
 331        priv->thermal = thermal;
 332
 333        irq = platform_get_irq(pdev, 0);
 334        if (irq < 0) {
 335                dev_err(&pdev->dev, "could not get IRQ\n");
 336                return irq;
 337        }
 338        ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
 339                                        brcmstb_tmon_irq_thread, IRQF_ONESHOT,
 340                                        DRV_NAME, priv);
 341        if (ret < 0) {
 342                dev_err(&pdev->dev, "could not request IRQ: %d\n", ret);
 343                return ret;
 344        }
 345
 346        dev_info(&pdev->dev, "registered AVS TMON of-sensor driver\n");
 347
 348        return 0;
 349}
 350
 351static struct platform_driver brcmstb_thermal_driver = {
 352        .probe = brcmstb_thermal_probe,
 353        .driver = {
 354                .name = DRV_NAME,
 355                .of_match_table = brcmstb_thermal_id_table,
 356        },
 357};
 358module_platform_driver(brcmstb_thermal_driver);
 359
 360MODULE_LICENSE("GPL v2");
 361MODULE_AUTHOR("Brian Norris");
 362MODULE_DESCRIPTION("Broadcom STB AVS TMON thermal driver");
 363