linux/drivers/thermal/tegra/soctherm-fuse.c
<<
>>
Prefs
   1/*
   2 * Copyright (c) 2014-2016, NVIDIA CORPORATION.  All rights reserved.
   3 *
   4 * This software is licensed under the terms of the GNU General Public
   5 * License version 2, as published by the Free Software Foundation, and
   6 * may be copied, distributed, and modified under those terms.
   7 *
   8 * This program is distributed in the hope that it will be useful,
   9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  11 * GNU General Public License for more details.
  12 *
  13 */
  14
  15#include <linux/module.h>
  16#include <linux/platform_device.h>
  17#include <soc/tegra/fuse.h>
  18
  19#include "soctherm.h"
  20
  21#define NOMINAL_CALIB_FT                        105
  22#define NOMINAL_CALIB_CP                        25
  23
  24#define FUSE_TSENSOR_CALIB_CP_TS_BASE_MASK      0x1fff
  25#define FUSE_TSENSOR_CALIB_FT_TS_BASE_MASK      (0x1fff << 13)
  26#define FUSE_TSENSOR_CALIB_FT_TS_BASE_SHIFT     13
  27
  28#define FUSE_TSENSOR_COMMON                     0x180
  29
  30/*
  31 * Tegra210: Layout of bits in FUSE_TSENSOR_COMMON:
  32 *    3                   2                   1                   0
  33 *  1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
  34 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  35 * |       BASE_FT       |      BASE_CP      | SHFT_FT | SHIFT_CP  |
  36 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  37 *
  38 * Tegra12x, etc:
  39 * In chips prior to Tegra210, this fuse was incorrectly sized as 26 bits,
  40 * and didn't hold SHIFT_CP in [31:26]. Therefore these missing six bits
  41 * were obtained via the FUSE_SPARE_REALIGNMENT_REG register [5:0].
  42 *
  43 * FUSE_TSENSOR_COMMON:
  44 *    3                   2                   1                   0
  45 *  1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
  46 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  47 * |-----------| SHFT_FT |       BASE_FT       |      BASE_CP      |
  48 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  49 *
  50 * FUSE_SPARE_REALIGNMENT_REG:
  51 *    3                   2                   1                   0
  52 *  1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
  53 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  54 * |---------------------------------------------------| SHIFT_CP  |
  55 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  56 */
  57
  58#define CALIB_COEFFICIENT 1000000LL
  59
  60/**
  61 * div64_s64_precise() - wrapper for div64_s64()
  62 * @a:  the dividend
  63 * @b:  the divisor
  64 *
  65 * Implements division with fairly accurate rounding instead of truncation by
  66 * shifting the dividend to the left by 16 so that the quotient has a
  67 * much higher precision.
  68 *
  69 * Return: the quotient of a / b.
  70 */
  71static s64 div64_s64_precise(s64 a, s32 b)
  72{
  73        s64 r, al;
  74
  75        /* Scale up for increased precision division */
  76        al = a << 16;
  77
  78        r = div64_s64(al * 2 + 1, 2 * b);
  79        return r >> 16;
  80}
  81
  82int tegra_calc_shared_calib(const struct tegra_soctherm_fuse *tfuse,
  83                            struct tsensor_shared_calib *shared)
  84{
  85        u32 val;
  86        s32 shifted_cp, shifted_ft;
  87        int err;
  88
  89        err = tegra_fuse_readl(FUSE_TSENSOR_COMMON, &val);
  90        if (err)
  91                return err;
  92
  93        shared->base_cp = (val & tfuse->fuse_base_cp_mask) >>
  94                          tfuse->fuse_base_cp_shift;
  95        shared->base_ft = (val & tfuse->fuse_base_ft_mask) >>
  96                          tfuse->fuse_base_ft_shift;
  97
  98        shifted_ft = (val & tfuse->fuse_shift_ft_mask) >>
  99                     tfuse->fuse_shift_ft_shift;
 100        shifted_ft = sign_extend32(shifted_ft, 4);
 101
 102        if (tfuse->fuse_spare_realignment) {
 103                err = tegra_fuse_readl(tfuse->fuse_spare_realignment, &val);
 104                if (err)
 105                        return err;
 106        }
 107
 108        shifted_cp = sign_extend32(val, 5);
 109
 110        shared->actual_temp_cp = 2 * NOMINAL_CALIB_CP + shifted_cp;
 111        shared->actual_temp_ft = 2 * NOMINAL_CALIB_FT + shifted_ft;
 112
 113        return 0;
 114}
 115
 116int tegra_calc_tsensor_calib(const struct tegra_tsensor *sensor,
 117                             const struct tsensor_shared_calib *shared,
 118                             u32 *calibration)
 119{
 120        const struct tegra_tsensor_group *sensor_group;
 121        u32 val, calib;
 122        s32 actual_tsensor_ft, actual_tsensor_cp;
 123        s32 delta_sens, delta_temp;
 124        s32 mult, div;
 125        s16 therma, thermb;
 126        s64 temp;
 127        int err;
 128
 129        sensor_group = sensor->group;
 130
 131        err = tegra_fuse_readl(sensor->calib_fuse_offset, &val);
 132        if (err)
 133                return err;
 134
 135        actual_tsensor_cp = (shared->base_cp * 64) + sign_extend32(val, 12);
 136        val = (val & FUSE_TSENSOR_CALIB_FT_TS_BASE_MASK) >>
 137              FUSE_TSENSOR_CALIB_FT_TS_BASE_SHIFT;
 138        actual_tsensor_ft = (shared->base_ft * 32) + sign_extend32(val, 12);
 139
 140        delta_sens = actual_tsensor_ft - actual_tsensor_cp;
 141        delta_temp = shared->actual_temp_ft - shared->actual_temp_cp;
 142
 143        mult = sensor_group->pdiv * sensor->config->tsample_ate;
 144        div = sensor->config->tsample * sensor_group->pdiv_ate;
 145
 146        temp = (s64)delta_temp * (1LL << 13) * mult;
 147        therma = div64_s64_precise(temp, (s64)delta_sens * div);
 148
 149        temp = ((s64)actual_tsensor_ft * shared->actual_temp_cp) -
 150                ((s64)actual_tsensor_cp * shared->actual_temp_ft);
 151        thermb = div64_s64_precise(temp, delta_sens);
 152
 153        temp = (s64)therma * sensor->fuse_corr_alpha;
 154        therma = div64_s64_precise(temp, CALIB_COEFFICIENT);
 155
 156        temp = (s64)thermb * sensor->fuse_corr_alpha + sensor->fuse_corr_beta;
 157        thermb = div64_s64_precise(temp, CALIB_COEFFICIENT);
 158
 159        calib = ((u16)therma << SENSOR_CONFIG2_THERMA_SHIFT) |
 160                ((u16)thermb << SENSOR_CONFIG2_THERMB_SHIFT);
 161
 162        *calibration = calib;
 163
 164        return 0;
 165}
 166
 167MODULE_AUTHOR("Wei Ni <wni@nvidia.com>");
 168MODULE_DESCRIPTION("Tegra SOCTHERM fuse management");
 169MODULE_LICENSE("GPL v2");
 170