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