linux/drivers/thermal/uniphier_thermal.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/**
   3 * uniphier_thermal.c - Socionext UniPhier thermal driver
   4 * Copyright 2014      Panasonic Corporation
   5 * Copyright 2016-2017 Socionext Inc.
   6 * Author:
   7 *      Kunihiko Hayashi <hayashi.kunihiko@socionext.com>
   8 */
   9
  10#include <linux/bitops.h>
  11#include <linux/interrupt.h>
  12#include <linux/mfd/syscon.h>
  13#include <linux/module.h>
  14#include <linux/of.h>
  15#include <linux/of_device.h>
  16#include <linux/platform_device.h>
  17#include <linux/regmap.h>
  18#include <linux/thermal.h>
  19
  20#include "thermal_core.h"
  21
  22/*
  23 * block registers
  24 * addresses are the offset from .block_base
  25 */
  26#define PVTCTLEN                        0x0000
  27#define PVTCTLEN_EN                     BIT(0)
  28
  29#define PVTCTLMODE                      0x0004
  30#define PVTCTLMODE_MASK                 0xf
  31#define PVTCTLMODE_TEMPMON              0x5
  32
  33#define EMONREPEAT                      0x0040
  34#define EMONREPEAT_ENDLESS              BIT(24)
  35#define EMONREPEAT_PERIOD               GENMASK(3, 0)
  36#define EMONREPEAT_PERIOD_1000000       0x9
  37
  38/*
  39 * common registers
  40 * addresses are the offset from .map_base
  41 */
  42#define PVTCTLSEL                       0x0900
  43#define PVTCTLSEL_MASK                  GENMASK(2, 0)
  44#define PVTCTLSEL_MONITOR               0
  45
  46#define SETALERT0                       0x0910
  47#define SETALERT1                       0x0914
  48#define SETALERT2                       0x0918
  49#define SETALERT_TEMP_OVF               (GENMASK(7, 0) << 16)
  50#define SETALERT_TEMP_OVF_VALUE(val)    (((val) & GENMASK(7, 0)) << 16)
  51#define SETALERT_EN                     BIT(0)
  52
  53#define PMALERTINTCTL                   0x0920
  54#define PMALERTINTCTL_CLR(ch)           BIT(4 * (ch) + 2)
  55#define PMALERTINTCTL_SET(ch)           BIT(4 * (ch) + 1)
  56#define PMALERTINTCTL_EN(ch)            BIT(4 * (ch) + 0)
  57#define PMALERTINTCTL_MASK              (GENMASK(10, 8) | GENMASK(6, 4) | \
  58                                         GENMASK(2, 0))
  59
  60#define TMOD                            0x0928
  61#define TMOD_WIDTH                      9
  62
  63#define TMODCOEF                        0x0e5c
  64
  65#define TMODSETUP0_EN                   BIT(30)
  66#define TMODSETUP0_VAL(val)             (((val) & GENMASK(13, 0)) << 16)
  67#define TMODSETUP1_EN                   BIT(15)
  68#define TMODSETUP1_VAL(val)             ((val) & GENMASK(14, 0))
  69
  70/* SoC critical temperature */
  71#define CRITICAL_TEMP_LIMIT             (120 * 1000)
  72
  73/* Max # of alert channels */
  74#define ALERT_CH_NUM                    3
  75
  76/* SoC specific thermal sensor data */
  77struct uniphier_tm_soc_data {
  78        u32 map_base;
  79        u32 block_base;
  80        u32 tmod_setup_addr;
  81};
  82
  83struct uniphier_tm_dev {
  84        struct regmap *regmap;
  85        struct device *dev;
  86        bool alert_en[ALERT_CH_NUM];
  87        struct thermal_zone_device *tz_dev;
  88        const struct uniphier_tm_soc_data *data;
  89};
  90
  91static int uniphier_tm_initialize_sensor(struct uniphier_tm_dev *tdev)
  92{
  93        struct regmap *map = tdev->regmap;
  94        u32 val;
  95        u32 tmod_calib[2];
  96        int ret;
  97
  98        /* stop PVT */
  99        regmap_write_bits(map, tdev->data->block_base + PVTCTLEN,
 100                          PVTCTLEN_EN, 0);
 101
 102        /*
 103         * Since SoC has a calibrated value that was set in advance,
 104         * TMODCOEF shows non-zero and PVT refers the value internally.
 105         *
 106         * If TMODCOEF shows zero, the boards don't have the calibrated
 107         * value, and the driver has to set default value from DT.
 108         */
 109        ret = regmap_read(map, tdev->data->map_base + TMODCOEF, &val);
 110        if (ret)
 111                return ret;
 112        if (!val) {
 113                /* look for the default values in DT */
 114                ret = of_property_read_u32_array(tdev->dev->of_node,
 115                                                 "socionext,tmod-calibration",
 116                                                 tmod_calib,
 117                                                 ARRAY_SIZE(tmod_calib));
 118                if (ret)
 119                        return ret;
 120
 121                regmap_write(map, tdev->data->tmod_setup_addr,
 122                        TMODSETUP0_EN | TMODSETUP0_VAL(tmod_calib[0]) |
 123                        TMODSETUP1_EN | TMODSETUP1_VAL(tmod_calib[1]));
 124        }
 125
 126        /* select temperature mode */
 127        regmap_write_bits(map, tdev->data->block_base + PVTCTLMODE,
 128                          PVTCTLMODE_MASK, PVTCTLMODE_TEMPMON);
 129
 130        /* set monitoring period */
 131        regmap_write_bits(map, tdev->data->block_base + EMONREPEAT,
 132                          EMONREPEAT_ENDLESS | EMONREPEAT_PERIOD,
 133                          EMONREPEAT_ENDLESS | EMONREPEAT_PERIOD_1000000);
 134
 135        /* set monitor mode */
 136        regmap_write_bits(map, tdev->data->map_base + PVTCTLSEL,
 137                          PVTCTLSEL_MASK, PVTCTLSEL_MONITOR);
 138
 139        return 0;
 140}
 141
 142static void uniphier_tm_set_alert(struct uniphier_tm_dev *tdev, u32 ch,
 143                                  u32 temp)
 144{
 145        struct regmap *map = tdev->regmap;
 146
 147        /* set alert temperature */
 148        regmap_write_bits(map, tdev->data->map_base + SETALERT0 + (ch << 2),
 149                          SETALERT_EN | SETALERT_TEMP_OVF,
 150                          SETALERT_EN |
 151                          SETALERT_TEMP_OVF_VALUE(temp / 1000));
 152}
 153
 154static void uniphier_tm_enable_sensor(struct uniphier_tm_dev *tdev)
 155{
 156        struct regmap *map = tdev->regmap;
 157        int i;
 158        u32 bits = 0;
 159
 160        for (i = 0; i < ALERT_CH_NUM; i++)
 161                if (tdev->alert_en[i])
 162                        bits |= PMALERTINTCTL_EN(i);
 163
 164        /* enable alert interrupt */
 165        regmap_write_bits(map, tdev->data->map_base + PMALERTINTCTL,
 166                          PMALERTINTCTL_MASK, bits);
 167
 168        /* start PVT */
 169        regmap_write_bits(map, tdev->data->block_base + PVTCTLEN,
 170                          PVTCTLEN_EN, PVTCTLEN_EN);
 171
 172        usleep_range(700, 1500);        /* The spec note says at least 700us */
 173}
 174
 175static void uniphier_tm_disable_sensor(struct uniphier_tm_dev *tdev)
 176{
 177        struct regmap *map = tdev->regmap;
 178
 179        /* disable alert interrupt */
 180        regmap_write_bits(map, tdev->data->map_base + PMALERTINTCTL,
 181                          PMALERTINTCTL_MASK, 0);
 182
 183        /* stop PVT */
 184        regmap_write_bits(map, tdev->data->block_base + PVTCTLEN,
 185                          PVTCTLEN_EN, 0);
 186
 187        usleep_range(1000, 2000);       /* The spec note says at least 1ms */
 188}
 189
 190static int uniphier_tm_get_temp(void *data, int *out_temp)
 191{
 192        struct uniphier_tm_dev *tdev = data;
 193        struct regmap *map = tdev->regmap;
 194        int ret;
 195        u32 temp;
 196
 197        ret = regmap_read(map, tdev->data->map_base + TMOD, &temp);
 198        if (ret)
 199                return ret;
 200
 201        /* MSB of the TMOD field is a sign bit */
 202        *out_temp = sign_extend32(temp, TMOD_WIDTH - 1) * 1000;
 203
 204        return 0;
 205}
 206
 207static const struct thermal_zone_of_device_ops uniphier_of_thermal_ops = {
 208        .get_temp = uniphier_tm_get_temp,
 209};
 210
 211static void uniphier_tm_irq_clear(struct uniphier_tm_dev *tdev)
 212{
 213        u32 mask = 0, bits = 0;
 214        int i;
 215
 216        for (i = 0; i < ALERT_CH_NUM; i++) {
 217                mask |= (PMALERTINTCTL_CLR(i) | PMALERTINTCTL_SET(i));
 218                bits |= PMALERTINTCTL_CLR(i);
 219        }
 220
 221        /* clear alert interrupt */
 222        regmap_write_bits(tdev->regmap,
 223                          tdev->data->map_base + PMALERTINTCTL, mask, bits);
 224}
 225
 226static irqreturn_t uniphier_tm_alarm_irq(int irq, void *_tdev)
 227{
 228        struct uniphier_tm_dev *tdev = _tdev;
 229
 230        disable_irq_nosync(irq);
 231        uniphier_tm_irq_clear(tdev);
 232
 233        return IRQ_WAKE_THREAD;
 234}
 235
 236static irqreturn_t uniphier_tm_alarm_irq_thread(int irq, void *_tdev)
 237{
 238        struct uniphier_tm_dev *tdev = _tdev;
 239
 240        thermal_zone_device_update(tdev->tz_dev, THERMAL_EVENT_UNSPECIFIED);
 241
 242        return IRQ_HANDLED;
 243}
 244
 245static int uniphier_tm_probe(struct platform_device *pdev)
 246{
 247        struct device *dev = &pdev->dev;
 248        struct regmap *regmap;
 249        struct device_node *parent;
 250        struct uniphier_tm_dev *tdev;
 251        const struct thermal_trip *trips;
 252        int i, ret, irq, ntrips, crit_temp = INT_MAX;
 253
 254        tdev = devm_kzalloc(dev, sizeof(*tdev), GFP_KERNEL);
 255        if (!tdev)
 256                return -ENOMEM;
 257        tdev->dev = dev;
 258
 259        tdev->data = of_device_get_match_data(dev);
 260        if (WARN_ON(!tdev->data))
 261                return -EINVAL;
 262
 263        irq = platform_get_irq(pdev, 0);
 264        if (irq < 0)
 265                return irq;
 266
 267        /* get regmap from syscon node */
 268        parent = of_get_parent(dev->of_node); /* parent should be syscon node */
 269        regmap = syscon_node_to_regmap(parent);
 270        of_node_put(parent);
 271        if (IS_ERR(regmap)) {
 272                dev_err(dev, "failed to get regmap (error %ld)\n",
 273                        PTR_ERR(regmap));
 274                return PTR_ERR(regmap);
 275        }
 276        tdev->regmap = regmap;
 277
 278        ret = uniphier_tm_initialize_sensor(tdev);
 279        if (ret) {
 280                dev_err(dev, "failed to initialize sensor\n");
 281                return ret;
 282        }
 283
 284        ret = devm_request_threaded_irq(dev, irq, uniphier_tm_alarm_irq,
 285                                        uniphier_tm_alarm_irq_thread,
 286                                        0, "thermal", tdev);
 287        if (ret)
 288                return ret;
 289
 290        platform_set_drvdata(pdev, tdev);
 291
 292        tdev->tz_dev = devm_thermal_zone_of_sensor_register(dev, 0, tdev,
 293                                                &uniphier_of_thermal_ops);
 294        if (IS_ERR(tdev->tz_dev)) {
 295                dev_err(dev, "failed to register sensor device\n");
 296                return PTR_ERR(tdev->tz_dev);
 297        }
 298
 299        /* get trip points */
 300        trips = of_thermal_get_trip_points(tdev->tz_dev);
 301        ntrips = of_thermal_get_ntrips(tdev->tz_dev);
 302        if (ntrips > ALERT_CH_NUM) {
 303                dev_err(dev, "thermal zone has too many trips\n");
 304                return -E2BIG;
 305        }
 306
 307        /* set alert temperatures */
 308        for (i = 0; i < ntrips; i++) {
 309                if (trips[i].type == THERMAL_TRIP_CRITICAL &&
 310                    trips[i].temperature < crit_temp)
 311                        crit_temp = trips[i].temperature;
 312                uniphier_tm_set_alert(tdev, i, trips[i].temperature);
 313                tdev->alert_en[i] = true;
 314        }
 315        if (crit_temp > CRITICAL_TEMP_LIMIT) {
 316                dev_err(dev, "critical trip is over limit(>%d), or not set\n",
 317                        CRITICAL_TEMP_LIMIT);
 318                return -EINVAL;
 319        }
 320
 321        uniphier_tm_enable_sensor(tdev);
 322
 323        return 0;
 324}
 325
 326static int uniphier_tm_remove(struct platform_device *pdev)
 327{
 328        struct uniphier_tm_dev *tdev = platform_get_drvdata(pdev);
 329
 330        /* disable sensor */
 331        uniphier_tm_disable_sensor(tdev);
 332
 333        return 0;
 334}
 335
 336static const struct uniphier_tm_soc_data uniphier_pxs2_tm_data = {
 337        .map_base        = 0xe000,
 338        .block_base      = 0xe000,
 339        .tmod_setup_addr = 0xe904,
 340};
 341
 342static const struct uniphier_tm_soc_data uniphier_ld20_tm_data = {
 343        .map_base        = 0xe000,
 344        .block_base      = 0xe800,
 345        .tmod_setup_addr = 0xe938,
 346};
 347
 348static const struct of_device_id uniphier_tm_dt_ids[] = {
 349        {
 350                .compatible = "socionext,uniphier-pxs2-thermal",
 351                .data       = &uniphier_pxs2_tm_data,
 352        },
 353        {
 354                .compatible = "socionext,uniphier-ld20-thermal",
 355                .data       = &uniphier_ld20_tm_data,
 356        },
 357        {
 358                .compatible = "socionext,uniphier-pxs3-thermal",
 359                .data       = &uniphier_ld20_tm_data,
 360        },
 361        { /* sentinel */ }
 362};
 363MODULE_DEVICE_TABLE(of, uniphier_tm_dt_ids);
 364
 365static struct platform_driver uniphier_tm_driver = {
 366        .probe = uniphier_tm_probe,
 367        .remove = uniphier_tm_remove,
 368        .driver = {
 369                .name = "uniphier-thermal",
 370                .of_match_table = uniphier_tm_dt_ids,
 371        },
 372};
 373module_platform_driver(uniphier_tm_driver);
 374
 375MODULE_AUTHOR("Kunihiko Hayashi <hayashi.kunihiko@socionext.com>");
 376MODULE_DESCRIPTION("UniPhier thermal driver");
 377MODULE_LICENSE("GPL v2");
 378