linux/drivers/thermal/intel_pch_thermal.c
<<
>>
Prefs
   1/* intel_pch_thermal.c - Intel PCH Thermal driver
   2 *
   3 * Copyright (c) 2015, Intel Corporation.
   4 *
   5 * Authors:
   6 *     Tushar Dave <tushar.n.dave@intel.com>
   7 *
   8 * This program is free software; you can redistribute it and/or modify it
   9 * under the terms and conditions of the GNU General Public License,
  10 * version 2, as published by the Free Software Foundation.
  11 *
  12 * This program is distributed in the hope it will be useful, but WITHOUT
  13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  14 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
  15 * more details.
  16 *
  17 */
  18
  19#include <linux/module.h>
  20#include <linux/types.h>
  21#include <linux/init.h>
  22#include <linux/pci.h>
  23#include <linux/thermal.h>
  24
  25/* Intel PCH thermal Device IDs */
  26#define PCH_THERMAL_DID_WPT     0x9CA4 /* Wildcat Point */
  27#define PCH_THERMAL_DID_SKL     0x9D31 /* Skylake PCH */
  28
  29/* Wildcat Point-LP  PCH Thermal registers */
  30#define WPT_TEMP        0x0000  /* Temperature */
  31#define WPT_TSC 0x04    /* Thermal Sensor Control */
  32#define WPT_TSS 0x06    /* Thermal Sensor Status */
  33#define WPT_TSEL        0x08    /* Thermal Sensor Enable and Lock */
  34#define WPT_TSREL       0x0A    /* Thermal Sensor Report Enable and Lock */
  35#define WPT_TSMIC       0x0C    /* Thermal Sensor SMI Control */
  36#define WPT_CTT 0x0010  /* Catastrophic Trip Point */
  37#define WPT_TAHV        0x0014  /* Thermal Alert High Value */
  38#define WPT_TALV        0x0018  /* Thermal Alert Low Value */
  39#define WPT_TL          0x00000040      /* Throttle Value */
  40#define WPT_PHL 0x0060  /* PCH Hot Level */
  41#define WPT_PHLC        0x62    /* PHL Control */
  42#define WPT_TAS 0x80    /* Thermal Alert Status */
  43#define WPT_TSPIEN      0x82    /* PCI Interrupt Event Enables */
  44#define WPT_TSGPEN      0x84    /* General Purpose Event Enables */
  45
  46/*  Wildcat Point-LP  PCH Thermal Register bit definitions */
  47#define WPT_TEMP_TSR    0x00ff  /* Temp TS Reading */
  48#define WPT_TSC_CPDE    0x01    /* Catastrophic Power-Down Enable */
  49#define WPT_TSS_TSDSS   0x10    /* Thermal Sensor Dynamic Shutdown Status */
  50#define WPT_TSS_GPES    0x08    /* GPE status */
  51#define WPT_TSEL_ETS    0x01    /* Enable TS */
  52#define WPT_TSEL_PLDB   0x80    /* TSEL Policy Lock-Down Bit */
  53#define WPT_TL_TOL      0x000001FF      /* T0 Level */
  54#define WPT_TL_T1L      0x1ff00000      /* T1 Level */
  55#define WPT_TL_TTEN     0x20000000      /* TT Enable */
  56
  57static char driver_name[] = "Intel PCH thermal driver";
  58
  59struct pch_thermal_device {
  60        void __iomem *hw_base;
  61        const struct pch_dev_ops *ops;
  62        struct pci_dev *pdev;
  63        struct thermal_zone_device *tzd;
  64        int crt_trip_id;
  65        unsigned long crt_temp;
  66        int hot_trip_id;
  67        unsigned long hot_temp;
  68};
  69
  70static int pch_wpt_init(struct pch_thermal_device *ptd, int *nr_trips)
  71{
  72        u8 tsel;
  73        u16 trip_temp;
  74
  75        *nr_trips = 0;
  76
  77        /* Check if BIOS has already enabled thermal sensor */
  78        if (WPT_TSS_TSDSS & readb(ptd->hw_base + WPT_TSS))
  79                goto read_trips;
  80
  81        tsel = readb(ptd->hw_base + WPT_TSEL);
  82        /*
  83         * When TSEL's Policy Lock-Down bit is 1, TSEL become RO.
  84         * If so, thermal sensor cannot enable. Bail out.
  85         */
  86        if (tsel & WPT_TSEL_PLDB) {
  87                dev_err(&ptd->pdev->dev, "Sensor can't be enabled\n");
  88                return -ENODEV;
  89        }
  90
  91        writeb(tsel|WPT_TSEL_ETS, ptd->hw_base + WPT_TSEL);
  92        if (!(WPT_TSS_TSDSS & readb(ptd->hw_base + WPT_TSS))) {
  93                dev_err(&ptd->pdev->dev, "Sensor can't be enabled\n");
  94                return -ENODEV;
  95        }
  96
  97read_trips:
  98        ptd->crt_trip_id = -1;
  99        trip_temp = readw(ptd->hw_base + WPT_CTT);
 100        trip_temp &= 0x1FF;
 101        if (trip_temp) {
 102                /* Resolution of 1/2 degree C and an offset of -50C */
 103                ptd->crt_temp = trip_temp * 1000 / 2 - 50000;
 104                ptd->crt_trip_id = 0;
 105                ++(*nr_trips);
 106        }
 107
 108        ptd->hot_trip_id = -1;
 109        trip_temp = readw(ptd->hw_base + WPT_PHL);
 110        trip_temp &= 0x1FF;
 111        if (trip_temp) {
 112                /* Resolution of 1/2 degree C and an offset of -50C */
 113                ptd->hot_temp = trip_temp * 1000 / 2 - 50000;
 114                ptd->hot_trip_id = *nr_trips;
 115                ++(*nr_trips);
 116        }
 117
 118        return 0;
 119}
 120
 121static int pch_wpt_get_temp(struct pch_thermal_device *ptd, int *temp)
 122{
 123        u8 wpt_temp;
 124
 125        wpt_temp = WPT_TEMP_TSR & readl(ptd->hw_base + WPT_TEMP);
 126
 127        /* Resolution of 1/2 degree C and an offset of -50C */
 128        *temp = (wpt_temp * 1000 / 2 - 50000);
 129
 130        return 0;
 131}
 132
 133struct pch_dev_ops {
 134        int (*hw_init)(struct pch_thermal_device *ptd, int *nr_trips);
 135        int (*get_temp)(struct pch_thermal_device *ptd, int *temp);
 136};
 137
 138
 139/* dev ops for Wildcat Point */
 140static const struct pch_dev_ops pch_dev_ops_wpt = {
 141        .hw_init = pch_wpt_init,
 142        .get_temp = pch_wpt_get_temp,
 143};
 144
 145static int pch_thermal_get_temp(struct thermal_zone_device *tzd, int *temp)
 146{
 147        struct pch_thermal_device *ptd = tzd->devdata;
 148
 149        return  ptd->ops->get_temp(ptd, temp);
 150}
 151
 152static int pch_get_trip_type(struct thermal_zone_device *tzd, int trip,
 153                             enum thermal_trip_type *type)
 154{
 155        struct pch_thermal_device *ptd = tzd->devdata;
 156
 157        if (ptd->crt_trip_id == trip)
 158                *type = THERMAL_TRIP_CRITICAL;
 159        else if (ptd->hot_trip_id == trip)
 160                *type = THERMAL_TRIP_HOT;
 161        else
 162                return -EINVAL;
 163
 164        return 0;
 165}
 166
 167static int pch_get_trip_temp(struct thermal_zone_device *tzd, int trip, int *temp)
 168{
 169        struct pch_thermal_device *ptd = tzd->devdata;
 170
 171        if (ptd->crt_trip_id == trip)
 172                *temp = ptd->crt_temp;
 173        else if (ptd->hot_trip_id == trip)
 174                *temp = ptd->hot_temp;
 175        else
 176                return -EINVAL;
 177
 178        return 0;
 179}
 180
 181static struct thermal_zone_device_ops tzd_ops = {
 182        .get_temp = pch_thermal_get_temp,
 183        .get_trip_type = pch_get_trip_type,
 184        .get_trip_temp = pch_get_trip_temp,
 185};
 186
 187
 188static int intel_pch_thermal_probe(struct pci_dev *pdev,
 189                                   const struct pci_device_id *id)
 190{
 191        struct pch_thermal_device *ptd;
 192        int err;
 193        int nr_trips;
 194        char *dev_name;
 195
 196        ptd = devm_kzalloc(&pdev->dev, sizeof(*ptd), GFP_KERNEL);
 197        if (!ptd)
 198                return -ENOMEM;
 199
 200        switch (pdev->device) {
 201        case PCH_THERMAL_DID_WPT:
 202                ptd->ops = &pch_dev_ops_wpt;
 203                dev_name = "pch_wildcat_point";
 204                break;
 205        case PCH_THERMAL_DID_SKL:
 206                ptd->ops = &pch_dev_ops_wpt;
 207                dev_name = "pch_skylake";
 208                break;
 209        default:
 210                dev_err(&pdev->dev, "unknown pch thermal device\n");
 211                return -ENODEV;
 212        }
 213
 214        pci_set_drvdata(pdev, ptd);
 215        ptd->pdev = pdev;
 216
 217        err = pci_enable_device(pdev);
 218        if (err) {
 219                dev_err(&pdev->dev, "failed to enable pci device\n");
 220                return err;
 221        }
 222
 223        err = pci_request_regions(pdev, driver_name);
 224        if (err) {
 225                dev_err(&pdev->dev, "failed to request pci region\n");
 226                goto error_disable;
 227        }
 228
 229        ptd->hw_base = pci_ioremap_bar(pdev, 0);
 230        if (!ptd->hw_base) {
 231                err = -ENOMEM;
 232                dev_err(&pdev->dev, "failed to map mem base\n");
 233                goto error_release;
 234        }
 235
 236        err = ptd->ops->hw_init(ptd, &nr_trips);
 237        if (err)
 238                goto error_cleanup;
 239
 240        ptd->tzd = thermal_zone_device_register(dev_name, nr_trips, 0, ptd,
 241                                                &tzd_ops, NULL, 0, 0);
 242        if (IS_ERR(ptd->tzd)) {
 243                dev_err(&pdev->dev, "Failed to register thermal zone %s\n",
 244                        dev_name);
 245                err = PTR_ERR(ptd->tzd);
 246                goto error_cleanup;
 247        }
 248
 249        return 0;
 250
 251error_cleanup:
 252        iounmap(ptd->hw_base);
 253error_release:
 254        pci_release_regions(pdev);
 255error_disable:
 256        pci_disable_device(pdev);
 257        dev_err(&pdev->dev, "pci device failed to probe\n");
 258        return err;
 259}
 260
 261static void intel_pch_thermal_remove(struct pci_dev *pdev)
 262{
 263        struct pch_thermal_device *ptd = pci_get_drvdata(pdev);
 264
 265        thermal_zone_device_unregister(ptd->tzd);
 266        iounmap(ptd->hw_base);
 267        pci_set_drvdata(pdev, NULL);
 268        pci_release_region(pdev, 0);
 269        pci_disable_device(pdev);
 270}
 271
 272static struct pci_device_id intel_pch_thermal_id[] = {
 273        { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_WPT) },
 274        { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_SKL) },
 275        { 0, },
 276};
 277MODULE_DEVICE_TABLE(pci, intel_pch_thermal_id);
 278
 279static struct pci_driver intel_pch_thermal_driver = {
 280        .name           = "intel_pch_thermal",
 281        .id_table       = intel_pch_thermal_id,
 282        .probe          = intel_pch_thermal_probe,
 283        .remove         = intel_pch_thermal_remove,
 284};
 285
 286module_pci_driver(intel_pch_thermal_driver);
 287
 288MODULE_LICENSE("GPL v2");
 289MODULE_DESCRIPTION("Intel PCH Thermal driver");
 290