18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (c) 2014-2016, NVIDIA CORPORATION. All rights reserved. 48c2ecf20Sopenharmony_ci */ 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci#include <linux/module.h> 78c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 88c2ecf20Sopenharmony_ci#include <soc/tegra/fuse.h> 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include "soctherm.h" 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#define NOMINAL_CALIB_FT 105 138c2ecf20Sopenharmony_ci#define NOMINAL_CALIB_CP 25 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci#define FUSE_TSENSOR_CALIB_CP_TS_BASE_MASK 0x1fff 168c2ecf20Sopenharmony_ci#define FUSE_TSENSOR_CALIB_FT_TS_BASE_MASK (0x1fff << 13) 178c2ecf20Sopenharmony_ci#define FUSE_TSENSOR_CALIB_FT_TS_BASE_SHIFT 13 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#define FUSE_TSENSOR_COMMON 0x180 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci/* 228c2ecf20Sopenharmony_ci * Tegra210: Layout of bits in FUSE_TSENSOR_COMMON: 238c2ecf20Sopenharmony_ci * 3 2 1 0 248c2ecf20Sopenharmony_ci * 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 258c2ecf20Sopenharmony_ci * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 268c2ecf20Sopenharmony_ci * | BASE_FT | BASE_CP | SHFT_FT | SHIFT_CP | 278c2ecf20Sopenharmony_ci * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 288c2ecf20Sopenharmony_ci * 298c2ecf20Sopenharmony_ci * Tegra12x, etc: 308c2ecf20Sopenharmony_ci * In chips prior to Tegra210, this fuse was incorrectly sized as 26 bits, 318c2ecf20Sopenharmony_ci * and didn't hold SHIFT_CP in [31:26]. Therefore these missing six bits 328c2ecf20Sopenharmony_ci * were obtained via the FUSE_SPARE_REALIGNMENT_REG register [5:0]. 338c2ecf20Sopenharmony_ci * 348c2ecf20Sopenharmony_ci * FUSE_TSENSOR_COMMON: 358c2ecf20Sopenharmony_ci * 3 2 1 0 368c2ecf20Sopenharmony_ci * 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 378c2ecf20Sopenharmony_ci * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 388c2ecf20Sopenharmony_ci * |-----------| SHFT_FT | BASE_FT | BASE_CP | 398c2ecf20Sopenharmony_ci * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 408c2ecf20Sopenharmony_ci * 418c2ecf20Sopenharmony_ci * FUSE_SPARE_REALIGNMENT_REG: 428c2ecf20Sopenharmony_ci * 3 2 1 0 438c2ecf20Sopenharmony_ci * 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 448c2ecf20Sopenharmony_ci * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 458c2ecf20Sopenharmony_ci * |---------------------------------------------------| SHIFT_CP | 468c2ecf20Sopenharmony_ci * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 478c2ecf20Sopenharmony_ci */ 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci#define CALIB_COEFFICIENT 1000000LL 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci/** 528c2ecf20Sopenharmony_ci * div64_s64_precise() - wrapper for div64_s64() 538c2ecf20Sopenharmony_ci * @a: the dividend 548c2ecf20Sopenharmony_ci * @b: the divisor 558c2ecf20Sopenharmony_ci * 568c2ecf20Sopenharmony_ci * Implements division with fairly accurate rounding instead of truncation by 578c2ecf20Sopenharmony_ci * shifting the dividend to the left by 16 so that the quotient has a 588c2ecf20Sopenharmony_ci * much higher precision. 598c2ecf20Sopenharmony_ci * 608c2ecf20Sopenharmony_ci * Return: the quotient of a / b. 618c2ecf20Sopenharmony_ci */ 628c2ecf20Sopenharmony_cistatic s64 div64_s64_precise(s64 a, s32 b) 638c2ecf20Sopenharmony_ci{ 648c2ecf20Sopenharmony_ci s64 r, al; 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci /* Scale up for increased precision division */ 678c2ecf20Sopenharmony_ci al = a << 16; 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci r = div64_s64(al * 2 + 1, 2 * b); 708c2ecf20Sopenharmony_ci return r >> 16; 718c2ecf20Sopenharmony_ci} 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ciint tegra_calc_shared_calib(const struct tegra_soctherm_fuse *tfuse, 748c2ecf20Sopenharmony_ci struct tsensor_shared_calib *shared) 758c2ecf20Sopenharmony_ci{ 768c2ecf20Sopenharmony_ci u32 val; 778c2ecf20Sopenharmony_ci s32 shifted_cp, shifted_ft; 788c2ecf20Sopenharmony_ci int err; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci err = tegra_fuse_readl(FUSE_TSENSOR_COMMON, &val); 818c2ecf20Sopenharmony_ci if (err) 828c2ecf20Sopenharmony_ci return err; 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci shared->base_cp = (val & tfuse->fuse_base_cp_mask) >> 858c2ecf20Sopenharmony_ci tfuse->fuse_base_cp_shift; 868c2ecf20Sopenharmony_ci shared->base_ft = (val & tfuse->fuse_base_ft_mask) >> 878c2ecf20Sopenharmony_ci tfuse->fuse_base_ft_shift; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci shifted_ft = (val & tfuse->fuse_shift_ft_mask) >> 908c2ecf20Sopenharmony_ci tfuse->fuse_shift_ft_shift; 918c2ecf20Sopenharmony_ci shifted_ft = sign_extend32(shifted_ft, 4); 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci if (tfuse->fuse_spare_realignment) { 948c2ecf20Sopenharmony_ci err = tegra_fuse_readl(tfuse->fuse_spare_realignment, &val); 958c2ecf20Sopenharmony_ci if (err) 968c2ecf20Sopenharmony_ci return err; 978c2ecf20Sopenharmony_ci } 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci shifted_cp = sign_extend32(val, 5); 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci shared->actual_temp_cp = 2 * NOMINAL_CALIB_CP + shifted_cp; 1028c2ecf20Sopenharmony_ci shared->actual_temp_ft = 2 * NOMINAL_CALIB_FT + shifted_ft; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci return 0; 1058c2ecf20Sopenharmony_ci} 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ciint tegra_calc_tsensor_calib(const struct tegra_tsensor *sensor, 1088c2ecf20Sopenharmony_ci const struct tsensor_shared_calib *shared, 1098c2ecf20Sopenharmony_ci u32 *calibration) 1108c2ecf20Sopenharmony_ci{ 1118c2ecf20Sopenharmony_ci const struct tegra_tsensor_group *sensor_group; 1128c2ecf20Sopenharmony_ci u32 val, calib; 1138c2ecf20Sopenharmony_ci s32 actual_tsensor_ft, actual_tsensor_cp; 1148c2ecf20Sopenharmony_ci s32 delta_sens, delta_temp; 1158c2ecf20Sopenharmony_ci s32 mult, div; 1168c2ecf20Sopenharmony_ci s16 therma, thermb; 1178c2ecf20Sopenharmony_ci s64 temp; 1188c2ecf20Sopenharmony_ci int err; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci sensor_group = sensor->group; 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci err = tegra_fuse_readl(sensor->calib_fuse_offset, &val); 1238c2ecf20Sopenharmony_ci if (err) 1248c2ecf20Sopenharmony_ci return err; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci actual_tsensor_cp = (shared->base_cp * 64) + sign_extend32(val, 12); 1278c2ecf20Sopenharmony_ci val = (val & FUSE_TSENSOR_CALIB_FT_TS_BASE_MASK) >> 1288c2ecf20Sopenharmony_ci FUSE_TSENSOR_CALIB_FT_TS_BASE_SHIFT; 1298c2ecf20Sopenharmony_ci actual_tsensor_ft = (shared->base_ft * 32) + sign_extend32(val, 12); 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci delta_sens = actual_tsensor_ft - actual_tsensor_cp; 1328c2ecf20Sopenharmony_ci delta_temp = shared->actual_temp_ft - shared->actual_temp_cp; 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci mult = sensor_group->pdiv * sensor->config->tsample_ate; 1358c2ecf20Sopenharmony_ci div = sensor->config->tsample * sensor_group->pdiv_ate; 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci temp = (s64)delta_temp * (1LL << 13) * mult; 1388c2ecf20Sopenharmony_ci therma = div64_s64_precise(temp, (s64)delta_sens * div); 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci temp = ((s64)actual_tsensor_ft * shared->actual_temp_cp) - 1418c2ecf20Sopenharmony_ci ((s64)actual_tsensor_cp * shared->actual_temp_ft); 1428c2ecf20Sopenharmony_ci thermb = div64_s64_precise(temp, delta_sens); 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci temp = (s64)therma * sensor->fuse_corr_alpha; 1458c2ecf20Sopenharmony_ci therma = div64_s64_precise(temp, CALIB_COEFFICIENT); 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci temp = (s64)thermb * sensor->fuse_corr_alpha + sensor->fuse_corr_beta; 1488c2ecf20Sopenharmony_ci thermb = div64_s64_precise(temp, CALIB_COEFFICIENT); 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci calib = ((u16)therma << SENSOR_CONFIG2_THERMA_SHIFT) | 1518c2ecf20Sopenharmony_ci ((u16)thermb << SENSOR_CONFIG2_THERMB_SHIFT); 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci *calibration = calib; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci return 0; 1568c2ecf20Sopenharmony_ci} 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ciMODULE_AUTHOR("Wei Ni <wni@nvidia.com>"); 1598c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Tegra SOCTHERM fuse management"); 1608c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 161