162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (c) 2014-2016, NVIDIA CORPORATION. All rights reserved. 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#include <linux/module.h> 762306a36Sopenharmony_ci#include <linux/platform_device.h> 862306a36Sopenharmony_ci#include <soc/tegra/fuse.h> 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include "soctherm.h" 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#define NOMINAL_CALIB_FT 105 1362306a36Sopenharmony_ci#define NOMINAL_CALIB_CP 25 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#define FUSE_TSENSOR_CALIB_CP_TS_BASE_MASK 0x1fff 1662306a36Sopenharmony_ci#define FUSE_TSENSOR_CALIB_FT_TS_BASE_MASK (0x1fff << 13) 1762306a36Sopenharmony_ci#define FUSE_TSENSOR_CALIB_FT_TS_BASE_SHIFT 13 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#define FUSE_TSENSOR_COMMON 0x180 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci/* 2262306a36Sopenharmony_ci * Tegra210: Layout of bits in FUSE_TSENSOR_COMMON: 2362306a36Sopenharmony_ci * 3 2 1 0 2462306a36Sopenharmony_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 2562306a36Sopenharmony_ci * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 2662306a36Sopenharmony_ci * | BASE_FT | BASE_CP | SHFT_FT | SHIFT_CP | 2762306a36Sopenharmony_ci * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 2862306a36Sopenharmony_ci * 2962306a36Sopenharmony_ci * Tegra12x, etc: 3062306a36Sopenharmony_ci * In chips prior to Tegra210, this fuse was incorrectly sized as 26 bits, 3162306a36Sopenharmony_ci * and didn't hold SHIFT_CP in [31:26]. Therefore these missing six bits 3262306a36Sopenharmony_ci * were obtained via the FUSE_SPARE_REALIGNMENT_REG register [5:0]. 3362306a36Sopenharmony_ci * 3462306a36Sopenharmony_ci * FUSE_TSENSOR_COMMON: 3562306a36Sopenharmony_ci * 3 2 1 0 3662306a36Sopenharmony_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 3762306a36Sopenharmony_ci * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 3862306a36Sopenharmony_ci * |-----------| SHFT_FT | BASE_FT | BASE_CP | 3962306a36Sopenharmony_ci * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 4062306a36Sopenharmony_ci * 4162306a36Sopenharmony_ci * FUSE_SPARE_REALIGNMENT_REG: 4262306a36Sopenharmony_ci * 3 2 1 0 4362306a36Sopenharmony_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 4462306a36Sopenharmony_ci * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 4562306a36Sopenharmony_ci * |---------------------------------------------------| SHIFT_CP | 4662306a36Sopenharmony_ci * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 4762306a36Sopenharmony_ci */ 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci#define CALIB_COEFFICIENT 1000000LL 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci/** 5262306a36Sopenharmony_ci * div64_s64_precise() - wrapper for div64_s64() 5362306a36Sopenharmony_ci * @a: the dividend 5462306a36Sopenharmony_ci * @b: the divisor 5562306a36Sopenharmony_ci * 5662306a36Sopenharmony_ci * Implements division with fairly accurate rounding instead of truncation by 5762306a36Sopenharmony_ci * shifting the dividend to the left by 16 so that the quotient has a 5862306a36Sopenharmony_ci * much higher precision. 5962306a36Sopenharmony_ci * 6062306a36Sopenharmony_ci * Return: the quotient of a / b. 6162306a36Sopenharmony_ci */ 6262306a36Sopenharmony_cistatic s64 div64_s64_precise(s64 a, s32 b) 6362306a36Sopenharmony_ci{ 6462306a36Sopenharmony_ci s64 r, al; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci /* Scale up for increased precision division */ 6762306a36Sopenharmony_ci al = a << 16; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci r = div64_s64(al * 2 + 1, 2 * b); 7062306a36Sopenharmony_ci return r >> 16; 7162306a36Sopenharmony_ci} 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ciint tegra_calc_shared_calib(const struct tegra_soctherm_fuse *tfuse, 7462306a36Sopenharmony_ci struct tsensor_shared_calib *shared) 7562306a36Sopenharmony_ci{ 7662306a36Sopenharmony_ci u32 val; 7762306a36Sopenharmony_ci s32 shifted_cp, shifted_ft; 7862306a36Sopenharmony_ci int err; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci err = tegra_fuse_readl(FUSE_TSENSOR_COMMON, &val); 8162306a36Sopenharmony_ci if (err) 8262306a36Sopenharmony_ci return err; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci shared->base_cp = (val & tfuse->fuse_base_cp_mask) >> 8562306a36Sopenharmony_ci tfuse->fuse_base_cp_shift; 8662306a36Sopenharmony_ci shared->base_ft = (val & tfuse->fuse_base_ft_mask) >> 8762306a36Sopenharmony_ci tfuse->fuse_base_ft_shift; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci shifted_ft = (val & tfuse->fuse_shift_ft_mask) >> 9062306a36Sopenharmony_ci tfuse->fuse_shift_ft_shift; 9162306a36Sopenharmony_ci shifted_ft = sign_extend32(shifted_ft, 4); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci if (tfuse->fuse_spare_realignment) { 9462306a36Sopenharmony_ci err = tegra_fuse_readl(tfuse->fuse_spare_realignment, &val); 9562306a36Sopenharmony_ci if (err) 9662306a36Sopenharmony_ci return err; 9762306a36Sopenharmony_ci } 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci shifted_cp = sign_extend32(val, 5); 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci shared->actual_temp_cp = 2 * NOMINAL_CALIB_CP + shifted_cp; 10262306a36Sopenharmony_ci shared->actual_temp_ft = 2 * NOMINAL_CALIB_FT + shifted_ft; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci return 0; 10562306a36Sopenharmony_ci} 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ciint tegra_calc_tsensor_calib(const struct tegra_tsensor *sensor, 10862306a36Sopenharmony_ci const struct tsensor_shared_calib *shared, 10962306a36Sopenharmony_ci u32 *calibration) 11062306a36Sopenharmony_ci{ 11162306a36Sopenharmony_ci const struct tegra_tsensor_group *sensor_group; 11262306a36Sopenharmony_ci u32 val, calib; 11362306a36Sopenharmony_ci s32 actual_tsensor_ft, actual_tsensor_cp; 11462306a36Sopenharmony_ci s32 delta_sens, delta_temp; 11562306a36Sopenharmony_ci s32 mult, div; 11662306a36Sopenharmony_ci s16 therma, thermb; 11762306a36Sopenharmony_ci s64 temp; 11862306a36Sopenharmony_ci int err; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci sensor_group = sensor->group; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci err = tegra_fuse_readl(sensor->calib_fuse_offset, &val); 12362306a36Sopenharmony_ci if (err) 12462306a36Sopenharmony_ci return err; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci actual_tsensor_cp = (shared->base_cp * 64) + sign_extend32(val, 12); 12762306a36Sopenharmony_ci val = (val & FUSE_TSENSOR_CALIB_FT_TS_BASE_MASK) >> 12862306a36Sopenharmony_ci FUSE_TSENSOR_CALIB_FT_TS_BASE_SHIFT; 12962306a36Sopenharmony_ci actual_tsensor_ft = (shared->base_ft * 32) + sign_extend32(val, 12); 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci delta_sens = actual_tsensor_ft - actual_tsensor_cp; 13262306a36Sopenharmony_ci delta_temp = shared->actual_temp_ft - shared->actual_temp_cp; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci mult = sensor_group->pdiv * sensor->config->tsample_ate; 13562306a36Sopenharmony_ci div = sensor->config->tsample * sensor_group->pdiv_ate; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci temp = (s64)delta_temp * (1LL << 13) * mult; 13862306a36Sopenharmony_ci therma = div64_s64_precise(temp, (s64)delta_sens * div); 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci temp = ((s64)actual_tsensor_ft * shared->actual_temp_cp) - 14162306a36Sopenharmony_ci ((s64)actual_tsensor_cp * shared->actual_temp_ft); 14262306a36Sopenharmony_ci thermb = div64_s64_precise(temp, delta_sens); 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci temp = (s64)therma * sensor->fuse_corr_alpha; 14562306a36Sopenharmony_ci therma = div64_s64_precise(temp, CALIB_COEFFICIENT); 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci temp = (s64)thermb * sensor->fuse_corr_alpha + sensor->fuse_corr_beta; 14862306a36Sopenharmony_ci thermb = div64_s64_precise(temp, CALIB_COEFFICIENT); 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci calib = ((u16)therma << SENSOR_CONFIG2_THERMA_SHIFT) | 15162306a36Sopenharmony_ci ((u16)thermb << SENSOR_CONFIG2_THERMB_SHIFT); 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci *calibration = calib; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci return 0; 15662306a36Sopenharmony_ci} 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ciMODULE_AUTHOR("Wei Ni <wni@nvidia.com>"); 15962306a36Sopenharmony_ciMODULE_DESCRIPTION("Tegra SOCTHERM fuse management"); 16062306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 161