|  | /* | 
|  | * Copyright (c) 2014-2016, NVIDIA CORPORATION.  All rights reserved. | 
|  | * | 
|  | * This software is licensed under the terms of the GNU General Public | 
|  | * License version 2, as published by the Free Software Foundation, and | 
|  | * may be copied, distributed, and modified under those terms. | 
|  | * | 
|  | * This program is distributed in the hope that it will be useful, | 
|  | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | * GNU General Public License for more details. | 
|  | * | 
|  | */ | 
|  |  | 
|  | #include <linux/module.h> | 
|  | #include <linux/platform_device.h> | 
|  | #include <soc/tegra/fuse.h> | 
|  |  | 
|  | #include "soctherm.h" | 
|  |  | 
|  | #define NOMINAL_CALIB_FT			105 | 
|  | #define NOMINAL_CALIB_CP			25 | 
|  |  | 
|  | #define FUSE_TSENSOR_CALIB_CP_TS_BASE_MASK	0x1fff | 
|  | #define FUSE_TSENSOR_CALIB_FT_TS_BASE_MASK	(0x1fff << 13) | 
|  | #define FUSE_TSENSOR_CALIB_FT_TS_BASE_SHIFT	13 | 
|  |  | 
|  | #define FUSE_TSENSOR_COMMON			0x180 | 
|  |  | 
|  | /* | 
|  | * Tegra210: Layout of bits in FUSE_TSENSOR_COMMON: | 
|  | *    3                   2                   1                   0 | 
|  | *  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 | 
|  | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 
|  | * |       BASE_FT       |      BASE_CP      | SHFT_FT | SHIFT_CP  | | 
|  | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 
|  | * | 
|  | * Tegra12x, etc: | 
|  | * In chips prior to Tegra210, this fuse was incorrectly sized as 26 bits, | 
|  | * and didn't hold SHIFT_CP in [31:26]. Therefore these missing six bits | 
|  | * were obtained via the FUSE_SPARE_REALIGNMENT_REG register [5:0]. | 
|  | * | 
|  | * FUSE_TSENSOR_COMMON: | 
|  | *    3                   2                   1                   0 | 
|  | *  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 | 
|  | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 
|  | * |-----------| SHFT_FT |       BASE_FT       |      BASE_CP      | | 
|  | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 
|  | * | 
|  | * FUSE_SPARE_REALIGNMENT_REG: | 
|  | *    3                   2                   1                   0 | 
|  | *  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 | 
|  | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 
|  | * |---------------------------------------------------| SHIFT_CP  | | 
|  | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 
|  | */ | 
|  |  | 
|  | #define CALIB_COEFFICIENT 1000000LL | 
|  |  | 
|  | /** | 
|  | * div64_s64_precise() - wrapper for div64_s64() | 
|  | * @a:  the dividend | 
|  | * @b:  the divisor | 
|  | * | 
|  | * Implements division with fairly accurate rounding instead of truncation by | 
|  | * shifting the dividend to the left by 16 so that the quotient has a | 
|  | * much higher precision. | 
|  | * | 
|  | * Return: the quotient of a / b. | 
|  | */ | 
|  | static s64 div64_s64_precise(s64 a, s32 b) | 
|  | { | 
|  | s64 r, al; | 
|  |  | 
|  | /* Scale up for increased precision division */ | 
|  | al = a << 16; | 
|  |  | 
|  | r = div64_s64(al * 2 + 1, 2 * b); | 
|  | return r >> 16; | 
|  | } | 
|  |  | 
|  | int tegra_calc_shared_calib(const struct tegra_soctherm_fuse *tfuse, | 
|  | struct tsensor_shared_calib *shared) | 
|  | { | 
|  | u32 val; | 
|  | s32 shifted_cp, shifted_ft; | 
|  | int err; | 
|  |  | 
|  | err = tegra_fuse_readl(FUSE_TSENSOR_COMMON, &val); | 
|  | if (err) | 
|  | return err; | 
|  |  | 
|  | shared->base_cp = (val & tfuse->fuse_base_cp_mask) >> | 
|  | tfuse->fuse_base_cp_shift; | 
|  | shared->base_ft = (val & tfuse->fuse_base_ft_mask) >> | 
|  | tfuse->fuse_base_ft_shift; | 
|  |  | 
|  | shifted_ft = (val & tfuse->fuse_shift_ft_mask) >> | 
|  | tfuse->fuse_shift_ft_shift; | 
|  | shifted_ft = sign_extend32(shifted_ft, 4); | 
|  |  | 
|  | if (tfuse->fuse_spare_realignment) { | 
|  | err = tegra_fuse_readl(tfuse->fuse_spare_realignment, &val); | 
|  | if (err) | 
|  | return err; | 
|  | } | 
|  |  | 
|  | shifted_cp = sign_extend32(val, 5); | 
|  |  | 
|  | shared->actual_temp_cp = 2 * NOMINAL_CALIB_CP + shifted_cp; | 
|  | shared->actual_temp_ft = 2 * NOMINAL_CALIB_FT + shifted_ft; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int tegra_calc_tsensor_calib(const struct tegra_tsensor *sensor, | 
|  | const struct tsensor_shared_calib *shared, | 
|  | u32 *calibration) | 
|  | { | 
|  | const struct tegra_tsensor_group *sensor_group; | 
|  | u32 val, calib; | 
|  | s32 actual_tsensor_ft, actual_tsensor_cp; | 
|  | s32 delta_sens, delta_temp; | 
|  | s32 mult, div; | 
|  | s16 therma, thermb; | 
|  | s64 temp; | 
|  | int err; | 
|  |  | 
|  | sensor_group = sensor->group; | 
|  |  | 
|  | err = tegra_fuse_readl(sensor->calib_fuse_offset, &val); | 
|  | if (err) | 
|  | return err; | 
|  |  | 
|  | actual_tsensor_cp = (shared->base_cp * 64) + sign_extend32(val, 12); | 
|  | val = (val & FUSE_TSENSOR_CALIB_FT_TS_BASE_MASK) >> | 
|  | FUSE_TSENSOR_CALIB_FT_TS_BASE_SHIFT; | 
|  | actual_tsensor_ft = (shared->base_ft * 32) + sign_extend32(val, 12); | 
|  |  | 
|  | delta_sens = actual_tsensor_ft - actual_tsensor_cp; | 
|  | delta_temp = shared->actual_temp_ft - shared->actual_temp_cp; | 
|  |  | 
|  | mult = sensor_group->pdiv * sensor->config->tsample_ate; | 
|  | div = sensor->config->tsample * sensor_group->pdiv_ate; | 
|  |  | 
|  | temp = (s64)delta_temp * (1LL << 13) * mult; | 
|  | therma = div64_s64_precise(temp, (s64)delta_sens * div); | 
|  |  | 
|  | temp = ((s64)actual_tsensor_ft * shared->actual_temp_cp) - | 
|  | ((s64)actual_tsensor_cp * shared->actual_temp_ft); | 
|  | thermb = div64_s64_precise(temp, delta_sens); | 
|  |  | 
|  | temp = (s64)therma * sensor->fuse_corr_alpha; | 
|  | therma = div64_s64_precise(temp, CALIB_COEFFICIENT); | 
|  |  | 
|  | temp = (s64)thermb * sensor->fuse_corr_alpha + sensor->fuse_corr_beta; | 
|  | thermb = div64_s64_precise(temp, CALIB_COEFFICIENT); | 
|  |  | 
|  | calib = ((u16)therma << SENSOR_CONFIG2_THERMA_SHIFT) | | 
|  | ((u16)thermb << SENSOR_CONFIG2_THERMB_SHIFT); | 
|  |  | 
|  | *calibration = calib; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | MODULE_AUTHOR("Wei Ni <wni@nvidia.com>"); | 
|  | MODULE_DESCRIPTION("Tegra SOCTHERM fuse management"); | 
|  | MODULE_LICENSE("GPL v2"); |