linux/drivers/thermal/armada_thermal.c
<<
>>
Prefs
   1/*
   2 * Marvell Armada 370/XP thermal sensor driver
   3 *
   4 * Copyright (C) 2013 Marvell
   5 *
   6 * This software is licensed under the terms of the GNU General Public
   7 * License version 2, as published by the Free Software Foundation, and
   8 * may be copied, distributed, and modified under those terms.
   9 *
  10 * This program is distributed in the hope that it will be useful,
  11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 * GNU General Public License for more details.
  14 *
  15 */
  16#include <linux/device.h>
  17#include <linux/err.h>
  18#include <linux/io.h>
  19#include <linux/kernel.h>
  20#include <linux/of.h>
  21#include <linux/module.h>
  22#include <linux/delay.h>
  23#include <linux/platform_device.h>
  24#include <linux/of_device.h>
  25#include <linux/thermal.h>
  26
  27#define THERMAL_VALID_MASK              0x1
  28
  29/* Thermal Manager Control and Status Register */
  30#define PMU_TDC0_SW_RST_MASK            (0x1 << 1)
  31#define PMU_TM_DISABLE_OFFS             0
  32#define PMU_TM_DISABLE_MASK             (0x1 << PMU_TM_DISABLE_OFFS)
  33#define PMU_TDC0_REF_CAL_CNT_OFFS       11
  34#define PMU_TDC0_REF_CAL_CNT_MASK       (0x1ff << PMU_TDC0_REF_CAL_CNT_OFFS)
  35#define PMU_TDC0_OTF_CAL_MASK           (0x1 << 30)
  36#define PMU_TDC0_START_CAL_MASK         (0x1 << 25)
  37
  38#define A375_UNIT_CONTROL_SHIFT         27
  39#define A375_UNIT_CONTROL_MASK          0x7
  40#define A375_READOUT_INVERT             BIT(15)
  41#define A375_HW_RESETn                  BIT(8)
  42#define A380_HW_RESET                   BIT(8)
  43
  44struct armada_thermal_data;
  45
  46/* Marvell EBU Thermal Sensor Dev Structure */
  47struct armada_thermal_priv {
  48        void __iomem *sensor;
  49        void __iomem *control;
  50        struct armada_thermal_data *data;
  51};
  52
  53struct armada_thermal_data {
  54        /* Initialize the sensor */
  55        void (*init_sensor)(struct platform_device *pdev,
  56                            struct armada_thermal_priv *);
  57
  58        /* Test for a valid sensor value (optional) */
  59        bool (*is_valid)(struct armada_thermal_priv *);
  60
  61        /* Formula coeficients: temp = (b + m * reg) / div */
  62        unsigned long coef_b;
  63        unsigned long coef_m;
  64        unsigned long coef_div;
  65        bool inverted;
  66
  67        /* Register shift and mask to access the sensor temperature */
  68        unsigned int temp_shift;
  69        unsigned int temp_mask;
  70        unsigned int is_valid_shift;
  71};
  72
  73static void armadaxp_init_sensor(struct platform_device *pdev,
  74                                 struct armada_thermal_priv *priv)
  75{
  76        unsigned long reg;
  77
  78        reg = readl_relaxed(priv->control);
  79        reg |= PMU_TDC0_OTF_CAL_MASK;
  80        writel(reg, priv->control);
  81
  82        /* Reference calibration value */
  83        reg &= ~PMU_TDC0_REF_CAL_CNT_MASK;
  84        reg |= (0xf1 << PMU_TDC0_REF_CAL_CNT_OFFS);
  85        writel(reg, priv->control);
  86
  87        /* Reset the sensor */
  88        reg = readl_relaxed(priv->control);
  89        writel((reg | PMU_TDC0_SW_RST_MASK), priv->control);
  90
  91        writel(reg, priv->control);
  92
  93        /* Enable the sensor */
  94        reg = readl_relaxed(priv->sensor);
  95        reg &= ~PMU_TM_DISABLE_MASK;
  96        writel(reg, priv->sensor);
  97}
  98
  99static void armada370_init_sensor(struct platform_device *pdev,
 100                                  struct armada_thermal_priv *priv)
 101{
 102        unsigned long reg;
 103
 104        reg = readl_relaxed(priv->control);
 105        reg |= PMU_TDC0_OTF_CAL_MASK;
 106        writel(reg, priv->control);
 107
 108        /* Reference calibration value */
 109        reg &= ~PMU_TDC0_REF_CAL_CNT_MASK;
 110        reg |= (0xf1 << PMU_TDC0_REF_CAL_CNT_OFFS);
 111        writel(reg, priv->control);
 112
 113        reg &= ~PMU_TDC0_START_CAL_MASK;
 114        writel(reg, priv->control);
 115
 116        mdelay(10);
 117}
 118
 119static void armada375_init_sensor(struct platform_device *pdev,
 120                                  struct armada_thermal_priv *priv)
 121{
 122        unsigned long reg;
 123
 124        reg = readl(priv->control + 4);
 125        reg &= ~(A375_UNIT_CONTROL_MASK << A375_UNIT_CONTROL_SHIFT);
 126        reg &= ~A375_READOUT_INVERT;
 127        reg &= ~A375_HW_RESETn;
 128
 129        writel(reg, priv->control + 4);
 130        mdelay(20);
 131
 132        reg |= A375_HW_RESETn;
 133        writel(reg, priv->control + 4);
 134        mdelay(50);
 135}
 136
 137static void armada380_init_sensor(struct platform_device *pdev,
 138                                  struct armada_thermal_priv *priv)
 139{
 140        unsigned long reg = readl_relaxed(priv->control);
 141
 142        /* Reset hardware once */
 143        if (!(reg & A380_HW_RESET)) {
 144                reg |= A380_HW_RESET;
 145                writel(reg, priv->control);
 146                mdelay(10);
 147        }
 148}
 149
 150static bool armada_is_valid(struct armada_thermal_priv *priv)
 151{
 152        unsigned long reg = readl_relaxed(priv->sensor);
 153
 154        return (reg >> priv->data->is_valid_shift) & THERMAL_VALID_MASK;
 155}
 156
 157static int armada_get_temp(struct thermal_zone_device *thermal,
 158                          int *temp)
 159{
 160        struct armada_thermal_priv *priv = thermal->devdata;
 161        unsigned long reg;
 162        unsigned long m, b, div;
 163
 164        /* Valid check */
 165        if (priv->data->is_valid && !priv->data->is_valid(priv)) {
 166                dev_err(&thermal->device,
 167                        "Temperature sensor reading not valid\n");
 168                return -EIO;
 169        }
 170
 171        reg = readl_relaxed(priv->sensor);
 172        reg = (reg >> priv->data->temp_shift) & priv->data->temp_mask;
 173
 174        /* Get formula coeficients */
 175        b = priv->data->coef_b;
 176        m = priv->data->coef_m;
 177        div = priv->data->coef_div;
 178
 179        if (priv->data->inverted)
 180                *temp = ((m * reg) - b) / div;
 181        else
 182                *temp = (b - (m * reg)) / div;
 183        return 0;
 184}
 185
 186static struct thermal_zone_device_ops ops = {
 187        .get_temp = armada_get_temp,
 188};
 189
 190static const struct armada_thermal_data armadaxp_data = {
 191        .init_sensor = armadaxp_init_sensor,
 192        .temp_shift = 10,
 193        .temp_mask = 0x1ff,
 194        .coef_b = 3153000000UL,
 195        .coef_m = 10000000UL,
 196        .coef_div = 13825,
 197};
 198
 199static const struct armada_thermal_data armada370_data = {
 200        .is_valid = armada_is_valid,
 201        .init_sensor = armada370_init_sensor,
 202        .is_valid_shift = 9,
 203        .temp_shift = 10,
 204        .temp_mask = 0x1ff,
 205        .coef_b = 3153000000UL,
 206        .coef_m = 10000000UL,
 207        .coef_div = 13825,
 208};
 209
 210static const struct armada_thermal_data armada375_data = {
 211        .is_valid = armada_is_valid,
 212        .init_sensor = armada375_init_sensor,
 213        .is_valid_shift = 10,
 214        .temp_shift = 0,
 215        .temp_mask = 0x1ff,
 216        .coef_b = 3171900000UL,
 217        .coef_m = 10000000UL,
 218        .coef_div = 13616,
 219};
 220
 221static const struct armada_thermal_data armada380_data = {
 222        .is_valid = armada_is_valid,
 223        .init_sensor = armada380_init_sensor,
 224        .is_valid_shift = 10,
 225        .temp_shift = 0,
 226        .temp_mask = 0x3ff,
 227        .coef_b = 1172499100UL,
 228        .coef_m = 2000096UL,
 229        .coef_div = 4201,
 230        .inverted = true,
 231};
 232
 233static const struct of_device_id armada_thermal_id_table[] = {
 234        {
 235                .compatible = "marvell,armadaxp-thermal",
 236                .data       = &armadaxp_data,
 237        },
 238        {
 239                .compatible = "marvell,armada370-thermal",
 240                .data       = &armada370_data,
 241        },
 242        {
 243                .compatible = "marvell,armada375-thermal",
 244                .data       = &armada375_data,
 245        },
 246        {
 247                .compatible = "marvell,armada380-thermal",
 248                .data       = &armada380_data,
 249        },
 250        {
 251                /* sentinel */
 252        },
 253};
 254MODULE_DEVICE_TABLE(of, armada_thermal_id_table);
 255
 256static int armada_thermal_probe(struct platform_device *pdev)
 257{
 258        struct thermal_zone_device *thermal;
 259        const struct of_device_id *match;
 260        struct armada_thermal_priv *priv;
 261        struct resource *res;
 262
 263        match = of_match_device(armada_thermal_id_table, &pdev->dev);
 264        if (!match)
 265                return -ENODEV;
 266
 267        priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
 268        if (!priv)
 269                return -ENOMEM;
 270
 271        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 272        priv->sensor = devm_ioremap_resource(&pdev->dev, res);
 273        if (IS_ERR(priv->sensor))
 274                return PTR_ERR(priv->sensor);
 275
 276        res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
 277        priv->control = devm_ioremap_resource(&pdev->dev, res);
 278        if (IS_ERR(priv->control))
 279                return PTR_ERR(priv->control);
 280
 281        priv->data = (struct armada_thermal_data *)match->data;
 282        priv->data->init_sensor(pdev, priv);
 283
 284        thermal = thermal_zone_device_register("armada_thermal", 0, 0,
 285                                               priv, &ops, NULL, 0, 0);
 286        if (IS_ERR(thermal)) {
 287                dev_err(&pdev->dev,
 288                        "Failed to register thermal zone device\n");
 289                return PTR_ERR(thermal);
 290        }
 291
 292        platform_set_drvdata(pdev, thermal);
 293
 294        return 0;
 295}
 296
 297static int armada_thermal_exit(struct platform_device *pdev)
 298{
 299        struct thermal_zone_device *armada_thermal =
 300                platform_get_drvdata(pdev);
 301
 302        thermal_zone_device_unregister(armada_thermal);
 303
 304        return 0;
 305}
 306
 307static struct platform_driver armada_thermal_driver = {
 308        .probe = armada_thermal_probe,
 309        .remove = armada_thermal_exit,
 310        .driver = {
 311                .name = "armada_thermal",
 312                .of_match_table = armada_thermal_id_table,
 313        },
 314};
 315
 316module_platform_driver(armada_thermal_driver);
 317
 318MODULE_AUTHOR("Ezequiel Garcia <ezequiel.garcia@free-electrons.com>");
 319MODULE_DESCRIPTION("Armada 370/XP thermal driver");
 320MODULE_LICENSE("GPL v2");
 321