1*5de33a7eSCosmo Chou // SPDX-License-Identifier: GPL-2.0-or-later 2*5de33a7eSCosmo Chou 3*5de33a7eSCosmo Chou #include <linux/debugfs.h> 4*5de33a7eSCosmo Chou #include <linux/delay.h> 5*5de33a7eSCosmo Chou #include <linux/err.h> 6*5de33a7eSCosmo Chou #include <linux/i2c.h> 7*5de33a7eSCosmo Chou #include <linux/init.h> 8*5de33a7eSCosmo Chou #include <linux/hwmon.h> 9*5de33a7eSCosmo Chou #include <linux/module.h> 10*5de33a7eSCosmo Chou #include <linux/mutex.h> 11*5de33a7eSCosmo Chou 12*5de33a7eSCosmo Chou /* Aries current average temp ADC code CSR */ 13*5de33a7eSCosmo Chou #define ARIES_CURRENT_AVG_TEMP_ADC_CSR 0x42c 14*5de33a7eSCosmo Chou 15*5de33a7eSCosmo Chou /* Device Load check register */ 16*5de33a7eSCosmo Chou #define ARIES_CODE_LOAD_REG 0x605 17*5de33a7eSCosmo Chou /* Value indicating FW was loaded properly, [3:1] = 3'b111 */ 18*5de33a7eSCosmo Chou #define ARIES_LOAD_CODE 0xe 19*5de33a7eSCosmo Chou 20*5de33a7eSCosmo Chou /* Main Micro Heartbeat register */ 21*5de33a7eSCosmo Chou #define ARIES_MM_HEARTBEAT_ADDR 0x923 22*5de33a7eSCosmo Chou 23*5de33a7eSCosmo Chou /* Reg offset to specify Address for MM assisted accesses */ 24*5de33a7eSCosmo Chou #define ARIES_MM_ASSIST_REG_ADDR_OFFSET 0xd99 25*5de33a7eSCosmo Chou /* Reg offset to specify Command for MM assisted accesses */ 26*5de33a7eSCosmo Chou #define ARIES_MM_ASSIST_CMD_OFFSET 0xd9d 27*5de33a7eSCosmo Chou /* Reg offset to MM SPARE 0 used specify Address[7:0] */ 28*5de33a7eSCosmo Chou #define ARIES_MM_ASSIST_SPARE_0_OFFSET 0xd9f 29*5de33a7eSCosmo Chou /* Reg offset to MM SPARE 3 used specify Data Byte 0 */ 30*5de33a7eSCosmo Chou #define ARIES_MM_ASSIST_SPARE_3_OFFSET 0xda2 31*5de33a7eSCosmo Chou /* Wide register reads */ 32*5de33a7eSCosmo Chou #define ARIES_MM_RD_WIDE_REG_2B 0x1d 33*5de33a7eSCosmo Chou #define ARIES_MM_RD_WIDE_REG_3B 0x1e 34*5de33a7eSCosmo Chou #define ARIES_MM_RD_WIDE_REG_4B 0x1f 35*5de33a7eSCosmo Chou #define ARIES_MM_RD_WIDE_REG_5B 0x20 36*5de33a7eSCosmo Chou 37*5de33a7eSCosmo Chou /* Time delay between checking MM status of EEPROM write (microseconds) */ 38*5de33a7eSCosmo Chou #define ARIES_MM_STATUS_TIME 5000 39*5de33a7eSCosmo Chou 40*5de33a7eSCosmo Chou /* AL Main SRAM DMEM offset (A0) */ 41*5de33a7eSCosmo Chou #define AL_MAIN_SRAM_DMEM_OFFSET (64 * 1024) 42*5de33a7eSCosmo Chou /* SRAM read command */ 43*5de33a7eSCosmo Chou #define AL_TG_RD_LOC_IND_SRAM 0x16 44*5de33a7eSCosmo Chou 45*5de33a7eSCosmo Chou /* Offset for main micro FW info */ 46*5de33a7eSCosmo Chou #define ARIES_MAIN_MICRO_FW_INFO (96 * 1024 - 128) 47*5de33a7eSCosmo Chou /* FW Info (Major) offset location in struct */ 48*5de33a7eSCosmo Chou #define ARIES_MM_FW_VERSION_MAJOR 0 49*5de33a7eSCosmo Chou /* FW Info (Minor) offset location in struct */ 50*5de33a7eSCosmo Chou #define ARIES_MM_FW_VERSION_MINOR 1 51*5de33a7eSCosmo Chou /* FW Info (Build no.) offset location in struct */ 52*5de33a7eSCosmo Chou #define ARIES_MM_FW_VERSION_BUILD 2 53*5de33a7eSCosmo Chou 54*5de33a7eSCosmo Chou #define ARIES_TEMP_CAL_CODE_DEFAULT 84 55*5de33a7eSCosmo Chou 56*5de33a7eSCosmo Chou /* Struct defining FW version loaded on an Aries device */ 57*5de33a7eSCosmo Chou struct pt5161l_fw_ver { 58*5de33a7eSCosmo Chou u8 major; 59*5de33a7eSCosmo Chou u8 minor; 60*5de33a7eSCosmo Chou u16 build; 61*5de33a7eSCosmo Chou }; 62*5de33a7eSCosmo Chou 63*5de33a7eSCosmo Chou /* Each client has this additional data */ 64*5de33a7eSCosmo Chou struct pt5161l_data { 65*5de33a7eSCosmo Chou struct i2c_client *client; 66*5de33a7eSCosmo Chou struct dentry *debugfs; 67*5de33a7eSCosmo Chou struct pt5161l_fw_ver fw_ver; 68*5de33a7eSCosmo Chou struct mutex lock; /* for atomic I2C transactions */ 69*5de33a7eSCosmo Chou bool init_done; 70*5de33a7eSCosmo Chou bool code_load_okay; /* indicate if code load reg value is expected */ 71*5de33a7eSCosmo Chou bool mm_heartbeat_okay; /* indicate if Main Micro heartbeat is good */ 72*5de33a7eSCosmo Chou bool mm_wide_reg_access; /* MM assisted wide register access */ 73*5de33a7eSCosmo Chou }; 74*5de33a7eSCosmo Chou 75*5de33a7eSCosmo Chou static struct dentry *pt5161l_debugfs_dir; 76*5de33a7eSCosmo Chou 77*5de33a7eSCosmo Chou /* 78*5de33a7eSCosmo Chou * Write multiple data bytes to Aries over I2C 79*5de33a7eSCosmo Chou */ 80*5de33a7eSCosmo Chou static int pt5161l_write_block_data(struct pt5161l_data *data, u32 address, 81*5de33a7eSCosmo Chou u8 len, u8 *val) 82*5de33a7eSCosmo Chou { 83*5de33a7eSCosmo Chou struct i2c_client *client = data->client; 84*5de33a7eSCosmo Chou int ret; 85*5de33a7eSCosmo Chou u8 remain_len = len; 86*5de33a7eSCosmo Chou u8 xfer_len, curr_len; 87*5de33a7eSCosmo Chou u8 buf[16]; 88*5de33a7eSCosmo Chou u8 cmd = 0x0F; /* [7]:pec_en, [4:2]:func, [1]:start, [0]:end */ 89*5de33a7eSCosmo Chou u8 config = 0x40; /* [6]:cfg_type, [4:1]:burst_len, [0]:address bit16 */ 90*5de33a7eSCosmo Chou 91*5de33a7eSCosmo Chou while (remain_len > 0) { 92*5de33a7eSCosmo Chou if (remain_len > 4) { 93*5de33a7eSCosmo Chou curr_len = 4; 94*5de33a7eSCosmo Chou remain_len -= 4; 95*5de33a7eSCosmo Chou } else { 96*5de33a7eSCosmo Chou curr_len = remain_len; 97*5de33a7eSCosmo Chou remain_len = 0; 98*5de33a7eSCosmo Chou } 99*5de33a7eSCosmo Chou 100*5de33a7eSCosmo Chou buf[0] = config | (curr_len - 1) << 1 | ((address >> 16) & 0x1); 101*5de33a7eSCosmo Chou buf[1] = (address >> 8) & 0xff; 102*5de33a7eSCosmo Chou buf[2] = address & 0xff; 103*5de33a7eSCosmo Chou memcpy(&buf[3], val, curr_len); 104*5de33a7eSCosmo Chou 105*5de33a7eSCosmo Chou xfer_len = 3 + curr_len; 106*5de33a7eSCosmo Chou ret = i2c_smbus_write_block_data(client, cmd, xfer_len, buf); 107*5de33a7eSCosmo Chou if (ret) 108*5de33a7eSCosmo Chou return ret; 109*5de33a7eSCosmo Chou 110*5de33a7eSCosmo Chou val += curr_len; 111*5de33a7eSCosmo Chou address += curr_len; 112*5de33a7eSCosmo Chou } 113*5de33a7eSCosmo Chou 114*5de33a7eSCosmo Chou return 0; 115*5de33a7eSCosmo Chou } 116*5de33a7eSCosmo Chou 117*5de33a7eSCosmo Chou /* 118*5de33a7eSCosmo Chou * Read multiple data bytes from Aries over I2C 119*5de33a7eSCosmo Chou */ 120*5de33a7eSCosmo Chou static int pt5161l_read_block_data(struct pt5161l_data *data, u32 address, 121*5de33a7eSCosmo Chou u8 len, u8 *val) 122*5de33a7eSCosmo Chou { 123*5de33a7eSCosmo Chou struct i2c_client *client = data->client; 124*5de33a7eSCosmo Chou int ret, tries; 125*5de33a7eSCosmo Chou u8 remain_len = len; 126*5de33a7eSCosmo Chou u8 curr_len; 127*5de33a7eSCosmo Chou u8 wbuf[16], rbuf[24]; 128*5de33a7eSCosmo Chou u8 cmd = 0x08; /* [7]:pec_en, [4:2]:func, [1]:start, [0]:end */ 129*5de33a7eSCosmo Chou u8 config = 0x00; /* [6]:cfg_type, [4:1]:burst_len, [0]:address bit16 */ 130*5de33a7eSCosmo Chou 131*5de33a7eSCosmo Chou while (remain_len > 0) { 132*5de33a7eSCosmo Chou if (remain_len > 16) { 133*5de33a7eSCosmo Chou curr_len = 16; 134*5de33a7eSCosmo Chou remain_len -= 16; 135*5de33a7eSCosmo Chou } else { 136*5de33a7eSCosmo Chou curr_len = remain_len; 137*5de33a7eSCosmo Chou remain_len = 0; 138*5de33a7eSCosmo Chou } 139*5de33a7eSCosmo Chou 140*5de33a7eSCosmo Chou wbuf[0] = config | (curr_len - 1) << 1 | 141*5de33a7eSCosmo Chou ((address >> 16) & 0x1); 142*5de33a7eSCosmo Chou wbuf[1] = (address >> 8) & 0xff; 143*5de33a7eSCosmo Chou wbuf[2] = address & 0xff; 144*5de33a7eSCosmo Chou 145*5de33a7eSCosmo Chou for (tries = 0; tries < 3; tries++) { 146*5de33a7eSCosmo Chou ret = i2c_smbus_write_block_data(client, (cmd | 0x2), 3, 147*5de33a7eSCosmo Chou wbuf); 148*5de33a7eSCosmo Chou if (ret) 149*5de33a7eSCosmo Chou return ret; 150*5de33a7eSCosmo Chou 151*5de33a7eSCosmo Chou ret = i2c_smbus_read_block_data(client, (cmd | 0x1), 152*5de33a7eSCosmo Chou rbuf); 153*5de33a7eSCosmo Chou if (ret == curr_len) 154*5de33a7eSCosmo Chou break; 155*5de33a7eSCosmo Chou } 156*5de33a7eSCosmo Chou if (tries >= 3) 157*5de33a7eSCosmo Chou return ret; 158*5de33a7eSCosmo Chou 159*5de33a7eSCosmo Chou memcpy(val, rbuf, curr_len); 160*5de33a7eSCosmo Chou val += curr_len; 161*5de33a7eSCosmo Chou address += curr_len; 162*5de33a7eSCosmo Chou } 163*5de33a7eSCosmo Chou 164*5de33a7eSCosmo Chou return 0; 165*5de33a7eSCosmo Chou } 166*5de33a7eSCosmo Chou 167*5de33a7eSCosmo Chou static int pt5161l_read_wide_reg(struct pt5161l_data *data, u32 address, 168*5de33a7eSCosmo Chou u8 width, u8 *val) 169*5de33a7eSCosmo Chou { 170*5de33a7eSCosmo Chou int ret, tries; 171*5de33a7eSCosmo Chou u8 buf[8]; 172*5de33a7eSCosmo Chou u8 status; 173*5de33a7eSCosmo Chou 174*5de33a7eSCosmo Chou /* 175*5de33a7eSCosmo Chou * Safely access wide registers using mailbox method to prevent 176*5de33a7eSCosmo Chou * risking conflict with Aries firmware; otherwise fallback to 177*5de33a7eSCosmo Chou * legacy, less secure method. 178*5de33a7eSCosmo Chou */ 179*5de33a7eSCosmo Chou if (data->mm_wide_reg_access) { 180*5de33a7eSCosmo Chou buf[0] = address & 0xff; 181*5de33a7eSCosmo Chou buf[1] = (address >> 8) & 0xff; 182*5de33a7eSCosmo Chou buf[2] = (address >> 16) & 0x1; 183*5de33a7eSCosmo Chou ret = pt5161l_write_block_data(data, 184*5de33a7eSCosmo Chou ARIES_MM_ASSIST_SPARE_0_OFFSET, 185*5de33a7eSCosmo Chou 3, buf); 186*5de33a7eSCosmo Chou if (ret) 187*5de33a7eSCosmo Chou return ret; 188*5de33a7eSCosmo Chou 189*5de33a7eSCosmo Chou /* Set command based on width */ 190*5de33a7eSCosmo Chou switch (width) { 191*5de33a7eSCosmo Chou case 2: 192*5de33a7eSCosmo Chou buf[0] = ARIES_MM_RD_WIDE_REG_2B; 193*5de33a7eSCosmo Chou break; 194*5de33a7eSCosmo Chou case 3: 195*5de33a7eSCosmo Chou buf[0] = ARIES_MM_RD_WIDE_REG_3B; 196*5de33a7eSCosmo Chou break; 197*5de33a7eSCosmo Chou case 4: 198*5de33a7eSCosmo Chou buf[0] = ARIES_MM_RD_WIDE_REG_4B; 199*5de33a7eSCosmo Chou break; 200*5de33a7eSCosmo Chou case 5: 201*5de33a7eSCosmo Chou buf[0] = ARIES_MM_RD_WIDE_REG_5B; 202*5de33a7eSCosmo Chou break; 203*5de33a7eSCosmo Chou default: 204*5de33a7eSCosmo Chou return -EINVAL; 205*5de33a7eSCosmo Chou } 206*5de33a7eSCosmo Chou ret = pt5161l_write_block_data(data, ARIES_MM_ASSIST_CMD_OFFSET, 207*5de33a7eSCosmo Chou 1, buf); 208*5de33a7eSCosmo Chou if (ret) 209*5de33a7eSCosmo Chou return ret; 210*5de33a7eSCosmo Chou 211*5de33a7eSCosmo Chou status = 0xff; 212*5de33a7eSCosmo Chou for (tries = 0; tries < 100; tries++) { 213*5de33a7eSCosmo Chou ret = pt5161l_read_block_data(data, 214*5de33a7eSCosmo Chou ARIES_MM_ASSIST_CMD_OFFSET, 215*5de33a7eSCosmo Chou 1, &status); 216*5de33a7eSCosmo Chou if (ret) 217*5de33a7eSCosmo Chou return ret; 218*5de33a7eSCosmo Chou 219*5de33a7eSCosmo Chou if (status == 0) 220*5de33a7eSCosmo Chou break; 221*5de33a7eSCosmo Chou 222*5de33a7eSCosmo Chou usleep_range(ARIES_MM_STATUS_TIME, 223*5de33a7eSCosmo Chou ARIES_MM_STATUS_TIME + 1000); 224*5de33a7eSCosmo Chou } 225*5de33a7eSCosmo Chou if (status != 0) 226*5de33a7eSCosmo Chou return -ETIMEDOUT; 227*5de33a7eSCosmo Chou 228*5de33a7eSCosmo Chou ret = pt5161l_read_block_data(data, 229*5de33a7eSCosmo Chou ARIES_MM_ASSIST_SPARE_3_OFFSET, 230*5de33a7eSCosmo Chou width, val); 231*5de33a7eSCosmo Chou if (ret) 232*5de33a7eSCosmo Chou return ret; 233*5de33a7eSCosmo Chou } else { 234*5de33a7eSCosmo Chou return pt5161l_read_block_data(data, address, width, val); 235*5de33a7eSCosmo Chou } 236*5de33a7eSCosmo Chou 237*5de33a7eSCosmo Chou return 0; 238*5de33a7eSCosmo Chou } 239*5de33a7eSCosmo Chou 240*5de33a7eSCosmo Chou /* 241*5de33a7eSCosmo Chou * Read multiple (up to eight) data bytes from micro SRAM over I2C 242*5de33a7eSCosmo Chou */ 243*5de33a7eSCosmo Chou static int 244*5de33a7eSCosmo Chou pt5161l_read_block_data_main_micro_indirect(struct pt5161l_data *data, 245*5de33a7eSCosmo Chou u32 address, u8 len, u8 *val) 246*5de33a7eSCosmo Chou { 247*5de33a7eSCosmo Chou int ret, tries; 248*5de33a7eSCosmo Chou u8 buf[8]; 249*5de33a7eSCosmo Chou u8 i, status; 250*5de33a7eSCosmo Chou u32 uind_offs = ARIES_MM_ASSIST_REG_ADDR_OFFSET; 251*5de33a7eSCosmo Chou u32 eeprom_base, eeprom_addr; 252*5de33a7eSCosmo Chou 253*5de33a7eSCosmo Chou /* No multi-byte indirect support here. Hence read a byte at a time */ 254*5de33a7eSCosmo Chou eeprom_base = address - AL_MAIN_SRAM_DMEM_OFFSET; 255*5de33a7eSCosmo Chou for (i = 0; i < len; i++) { 256*5de33a7eSCosmo Chou eeprom_addr = eeprom_base + i; 257*5de33a7eSCosmo Chou buf[0] = eeprom_addr & 0xff; 258*5de33a7eSCosmo Chou buf[1] = (eeprom_addr >> 8) & 0xff; 259*5de33a7eSCosmo Chou buf[2] = (eeprom_addr >> 16) & 0xff; 260*5de33a7eSCosmo Chou ret = pt5161l_write_block_data(data, uind_offs, 3, buf); 261*5de33a7eSCosmo Chou if (ret) 262*5de33a7eSCosmo Chou return ret; 263*5de33a7eSCosmo Chou 264*5de33a7eSCosmo Chou buf[0] = AL_TG_RD_LOC_IND_SRAM; 265*5de33a7eSCosmo Chou ret = pt5161l_write_block_data(data, uind_offs + 4, 1, buf); 266*5de33a7eSCosmo Chou if (ret) 267*5de33a7eSCosmo Chou return ret; 268*5de33a7eSCosmo Chou 269*5de33a7eSCosmo Chou status = 0xff; 270*5de33a7eSCosmo Chou for (tries = 0; tries < 255; tries++) { 271*5de33a7eSCosmo Chou ret = pt5161l_read_block_data(data, uind_offs + 4, 1, 272*5de33a7eSCosmo Chou &status); 273*5de33a7eSCosmo Chou if (ret) 274*5de33a7eSCosmo Chou return ret; 275*5de33a7eSCosmo Chou 276*5de33a7eSCosmo Chou if (status == 0) 277*5de33a7eSCosmo Chou break; 278*5de33a7eSCosmo Chou } 279*5de33a7eSCosmo Chou if (status != 0) 280*5de33a7eSCosmo Chou return -ETIMEDOUT; 281*5de33a7eSCosmo Chou 282*5de33a7eSCosmo Chou ret = pt5161l_read_block_data(data, uind_offs + 3, 1, buf); 283*5de33a7eSCosmo Chou if (ret) 284*5de33a7eSCosmo Chou return ret; 285*5de33a7eSCosmo Chou 286*5de33a7eSCosmo Chou val[i] = buf[0]; 287*5de33a7eSCosmo Chou } 288*5de33a7eSCosmo Chou 289*5de33a7eSCosmo Chou return 0; 290*5de33a7eSCosmo Chou } 291*5de33a7eSCosmo Chou 292*5de33a7eSCosmo Chou /* 293*5de33a7eSCosmo Chou * Check firmware load status 294*5de33a7eSCosmo Chou */ 295*5de33a7eSCosmo Chou static int pt5161l_fw_load_check(struct pt5161l_data *data) 296*5de33a7eSCosmo Chou { 297*5de33a7eSCosmo Chou int ret; 298*5de33a7eSCosmo Chou u8 buf[8]; 299*5de33a7eSCosmo Chou 300*5de33a7eSCosmo Chou ret = pt5161l_read_block_data(data, ARIES_CODE_LOAD_REG, 1, buf); 301*5de33a7eSCosmo Chou if (ret) 302*5de33a7eSCosmo Chou return ret; 303*5de33a7eSCosmo Chou 304*5de33a7eSCosmo Chou if (buf[0] < ARIES_LOAD_CODE) { 305*5de33a7eSCosmo Chou dev_dbg(&data->client->dev, 306*5de33a7eSCosmo Chou "Code Load reg unexpected. Not all modules are loaded %x\n", 307*5de33a7eSCosmo Chou buf[0]); 308*5de33a7eSCosmo Chou data->code_load_okay = false; 309*5de33a7eSCosmo Chou } else { 310*5de33a7eSCosmo Chou data->code_load_okay = true; 311*5de33a7eSCosmo Chou } 312*5de33a7eSCosmo Chou 313*5de33a7eSCosmo Chou return 0; 314*5de33a7eSCosmo Chou } 315*5de33a7eSCosmo Chou 316*5de33a7eSCosmo Chou /* 317*5de33a7eSCosmo Chou * Check main micro heartbeat 318*5de33a7eSCosmo Chou */ 319*5de33a7eSCosmo Chou static int pt5161l_heartbeat_check(struct pt5161l_data *data) 320*5de33a7eSCosmo Chou { 321*5de33a7eSCosmo Chou int ret, tries; 322*5de33a7eSCosmo Chou u8 buf[8]; 323*5de33a7eSCosmo Chou u8 heartbeat; 324*5de33a7eSCosmo Chou bool hb_changed = false; 325*5de33a7eSCosmo Chou 326*5de33a7eSCosmo Chou ret = pt5161l_read_block_data(data, ARIES_MM_HEARTBEAT_ADDR, 1, buf); 327*5de33a7eSCosmo Chou if (ret) 328*5de33a7eSCosmo Chou return ret; 329*5de33a7eSCosmo Chou 330*5de33a7eSCosmo Chou heartbeat = buf[0]; 331*5de33a7eSCosmo Chou for (tries = 0; tries < 100; tries++) { 332*5de33a7eSCosmo Chou ret = pt5161l_read_block_data(data, ARIES_MM_HEARTBEAT_ADDR, 1, 333*5de33a7eSCosmo Chou buf); 334*5de33a7eSCosmo Chou if (ret) 335*5de33a7eSCosmo Chou return ret; 336*5de33a7eSCosmo Chou 337*5de33a7eSCosmo Chou if (buf[0] != heartbeat) { 338*5de33a7eSCosmo Chou hb_changed = true; 339*5de33a7eSCosmo Chou break; 340*5de33a7eSCosmo Chou } 341*5de33a7eSCosmo Chou } 342*5de33a7eSCosmo Chou data->mm_heartbeat_okay = hb_changed; 343*5de33a7eSCosmo Chou 344*5de33a7eSCosmo Chou return 0; 345*5de33a7eSCosmo Chou } 346*5de33a7eSCosmo Chou 347*5de33a7eSCosmo Chou /* 348*5de33a7eSCosmo Chou * Check the status of firmware 349*5de33a7eSCosmo Chou */ 350*5de33a7eSCosmo Chou static int pt5161l_fwsts_check(struct pt5161l_data *data) 351*5de33a7eSCosmo Chou { 352*5de33a7eSCosmo Chou int ret; 353*5de33a7eSCosmo Chou u8 buf[8]; 354*5de33a7eSCosmo Chou u8 major = 0, minor = 0; 355*5de33a7eSCosmo Chou u16 build = 0; 356*5de33a7eSCosmo Chou 357*5de33a7eSCosmo Chou ret = pt5161l_fw_load_check(data); 358*5de33a7eSCosmo Chou if (ret) 359*5de33a7eSCosmo Chou return ret; 360*5de33a7eSCosmo Chou 361*5de33a7eSCosmo Chou ret = pt5161l_heartbeat_check(data); 362*5de33a7eSCosmo Chou if (ret) 363*5de33a7eSCosmo Chou return ret; 364*5de33a7eSCosmo Chou 365*5de33a7eSCosmo Chou if (data->code_load_okay && data->mm_heartbeat_okay) { 366*5de33a7eSCosmo Chou ret = pt5161l_read_block_data_main_micro_indirect(data, ARIES_MAIN_MICRO_FW_INFO + 367*5de33a7eSCosmo Chou ARIES_MM_FW_VERSION_MAJOR, 368*5de33a7eSCosmo Chou 1, &major); 369*5de33a7eSCosmo Chou if (ret) 370*5de33a7eSCosmo Chou return ret; 371*5de33a7eSCosmo Chou 372*5de33a7eSCosmo Chou ret = pt5161l_read_block_data_main_micro_indirect(data, ARIES_MAIN_MICRO_FW_INFO + 373*5de33a7eSCosmo Chou ARIES_MM_FW_VERSION_MINOR, 374*5de33a7eSCosmo Chou 1, &minor); 375*5de33a7eSCosmo Chou if (ret) 376*5de33a7eSCosmo Chou return ret; 377*5de33a7eSCosmo Chou 378*5de33a7eSCosmo Chou ret = pt5161l_read_block_data_main_micro_indirect(data, ARIES_MAIN_MICRO_FW_INFO + 379*5de33a7eSCosmo Chou ARIES_MM_FW_VERSION_BUILD, 380*5de33a7eSCosmo Chou 2, buf); 381*5de33a7eSCosmo Chou if (ret) 382*5de33a7eSCosmo Chou return ret; 383*5de33a7eSCosmo Chou build = buf[1] << 8 | buf[0]; 384*5de33a7eSCosmo Chou } 385*5de33a7eSCosmo Chou data->fw_ver.major = major; 386*5de33a7eSCosmo Chou data->fw_ver.minor = minor; 387*5de33a7eSCosmo Chou data->fw_ver.build = build; 388*5de33a7eSCosmo Chou 389*5de33a7eSCosmo Chou return 0; 390*5de33a7eSCosmo Chou } 391*5de33a7eSCosmo Chou 392*5de33a7eSCosmo Chou static int pt5161l_fw_is_at_least(struct pt5161l_data *data, u8 major, u8 minor, 393*5de33a7eSCosmo Chou u16 build) 394*5de33a7eSCosmo Chou { 395*5de33a7eSCosmo Chou u32 ver = major << 24 | minor << 16 | build; 396*5de33a7eSCosmo Chou u32 curr_ver = data->fw_ver.major << 24 | data->fw_ver.minor << 16 | 397*5de33a7eSCosmo Chou data->fw_ver.build; 398*5de33a7eSCosmo Chou 399*5de33a7eSCosmo Chou if (curr_ver >= ver) 400*5de33a7eSCosmo Chou return true; 401*5de33a7eSCosmo Chou 402*5de33a7eSCosmo Chou return false; 403*5de33a7eSCosmo Chou } 404*5de33a7eSCosmo Chou 405*5de33a7eSCosmo Chou static int pt5161l_init_dev(struct pt5161l_data *data) 406*5de33a7eSCosmo Chou { 407*5de33a7eSCosmo Chou int ret; 408*5de33a7eSCosmo Chou 409*5de33a7eSCosmo Chou mutex_lock(&data->lock); 410*5de33a7eSCosmo Chou ret = pt5161l_fwsts_check(data); 411*5de33a7eSCosmo Chou mutex_unlock(&data->lock); 412*5de33a7eSCosmo Chou if (ret) 413*5de33a7eSCosmo Chou return ret; 414*5de33a7eSCosmo Chou 415*5de33a7eSCosmo Chou /* Firmware 2.2.0 enables safe access to wide registers */ 416*5de33a7eSCosmo Chou if (pt5161l_fw_is_at_least(data, 2, 2, 0)) 417*5de33a7eSCosmo Chou data->mm_wide_reg_access = true; 418*5de33a7eSCosmo Chou 419*5de33a7eSCosmo Chou data->init_done = true; 420*5de33a7eSCosmo Chou 421*5de33a7eSCosmo Chou return 0; 422*5de33a7eSCosmo Chou } 423*5de33a7eSCosmo Chou 424*5de33a7eSCosmo Chou static int pt5161l_read(struct device *dev, enum hwmon_sensor_types type, 425*5de33a7eSCosmo Chou u32 attr, int channel, long *val) 426*5de33a7eSCosmo Chou { 427*5de33a7eSCosmo Chou struct pt5161l_data *data = dev_get_drvdata(dev); 428*5de33a7eSCosmo Chou int ret; 429*5de33a7eSCosmo Chou u8 buf[8]; 430*5de33a7eSCosmo Chou long adc_code; 431*5de33a7eSCosmo Chou 432*5de33a7eSCosmo Chou switch (attr) { 433*5de33a7eSCosmo Chou case hwmon_temp_input: 434*5de33a7eSCosmo Chou if (!data->init_done) { 435*5de33a7eSCosmo Chou ret = pt5161l_init_dev(data); 436*5de33a7eSCosmo Chou if (ret) 437*5de33a7eSCosmo Chou return ret; 438*5de33a7eSCosmo Chou } 439*5de33a7eSCosmo Chou 440*5de33a7eSCosmo Chou mutex_lock(&data->lock); 441*5de33a7eSCosmo Chou ret = pt5161l_read_wide_reg(data, 442*5de33a7eSCosmo Chou ARIES_CURRENT_AVG_TEMP_ADC_CSR, 4, 443*5de33a7eSCosmo Chou buf); 444*5de33a7eSCosmo Chou mutex_unlock(&data->lock); 445*5de33a7eSCosmo Chou if (ret) { 446*5de33a7eSCosmo Chou dev_dbg(dev, "Read adc_code failed %d\n", ret); 447*5de33a7eSCosmo Chou return ret; 448*5de33a7eSCosmo Chou } 449*5de33a7eSCosmo Chou 450*5de33a7eSCosmo Chou adc_code = buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0]; 451*5de33a7eSCosmo Chou if (adc_code == 0 || adc_code >= 0x3ff) { 452*5de33a7eSCosmo Chou dev_dbg(dev, "Invalid adc_code %lx\n", adc_code); 453*5de33a7eSCosmo Chou return -EIO; 454*5de33a7eSCosmo Chou } 455*5de33a7eSCosmo Chou 456*5de33a7eSCosmo Chou *val = 110000 + 457*5de33a7eSCosmo Chou ((adc_code - (ARIES_TEMP_CAL_CODE_DEFAULT + 250)) * 458*5de33a7eSCosmo Chou -320); 459*5de33a7eSCosmo Chou break; 460*5de33a7eSCosmo Chou default: 461*5de33a7eSCosmo Chou return -EOPNOTSUPP; 462*5de33a7eSCosmo Chou } 463*5de33a7eSCosmo Chou 464*5de33a7eSCosmo Chou return 0; 465*5de33a7eSCosmo Chou } 466*5de33a7eSCosmo Chou 467*5de33a7eSCosmo Chou static umode_t pt5161l_is_visible(const void *data, 468*5de33a7eSCosmo Chou enum hwmon_sensor_types type, u32 attr, 469*5de33a7eSCosmo Chou int channel) 470*5de33a7eSCosmo Chou { 471*5de33a7eSCosmo Chou switch (attr) { 472*5de33a7eSCosmo Chou case hwmon_temp_input: 473*5de33a7eSCosmo Chou return 0444; 474*5de33a7eSCosmo Chou default: 475*5de33a7eSCosmo Chou break; 476*5de33a7eSCosmo Chou } 477*5de33a7eSCosmo Chou 478*5de33a7eSCosmo Chou return 0; 479*5de33a7eSCosmo Chou } 480*5de33a7eSCosmo Chou 481*5de33a7eSCosmo Chou static const struct hwmon_channel_info *pt5161l_info[] = { 482*5de33a7eSCosmo Chou HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), 483*5de33a7eSCosmo Chou NULL 484*5de33a7eSCosmo Chou }; 485*5de33a7eSCosmo Chou 486*5de33a7eSCosmo Chou static const struct hwmon_ops pt5161l_hwmon_ops = { 487*5de33a7eSCosmo Chou .is_visible = pt5161l_is_visible, 488*5de33a7eSCosmo Chou .read = pt5161l_read, 489*5de33a7eSCosmo Chou }; 490*5de33a7eSCosmo Chou 491*5de33a7eSCosmo Chou static const struct hwmon_chip_info pt5161l_chip_info = { 492*5de33a7eSCosmo Chou .ops = &pt5161l_hwmon_ops, 493*5de33a7eSCosmo Chou .info = pt5161l_info, 494*5de33a7eSCosmo Chou }; 495*5de33a7eSCosmo Chou 496*5de33a7eSCosmo Chou static ssize_t pt5161l_debugfs_read_fw_ver(struct file *file, char __user *buf, 497*5de33a7eSCosmo Chou size_t count, loff_t *ppos) 498*5de33a7eSCosmo Chou { 499*5de33a7eSCosmo Chou struct pt5161l_data *data = file->private_data; 500*5de33a7eSCosmo Chou int ret; 501*5de33a7eSCosmo Chou char ver[32]; 502*5de33a7eSCosmo Chou 503*5de33a7eSCosmo Chou mutex_lock(&data->lock); 504*5de33a7eSCosmo Chou ret = pt5161l_fwsts_check(data); 505*5de33a7eSCosmo Chou mutex_unlock(&data->lock); 506*5de33a7eSCosmo Chou if (ret) 507*5de33a7eSCosmo Chou return ret; 508*5de33a7eSCosmo Chou 509*5de33a7eSCosmo Chou ret = snprintf(ver, sizeof(ver), "%u.%u.%u\n", data->fw_ver.major, 510*5de33a7eSCosmo Chou data->fw_ver.minor, data->fw_ver.build); 511*5de33a7eSCosmo Chou 512*5de33a7eSCosmo Chou return simple_read_from_buffer(buf, count, ppos, ver, ret); 513*5de33a7eSCosmo Chou } 514*5de33a7eSCosmo Chou 515*5de33a7eSCosmo Chou static const struct file_operations pt5161l_debugfs_ops_fw_ver = { 516*5de33a7eSCosmo Chou .read = pt5161l_debugfs_read_fw_ver, 517*5de33a7eSCosmo Chou .open = simple_open, 518*5de33a7eSCosmo Chou }; 519*5de33a7eSCosmo Chou 520*5de33a7eSCosmo Chou static ssize_t pt5161l_debugfs_read_fw_load_sts(struct file *file, 521*5de33a7eSCosmo Chou char __user *buf, size_t count, 522*5de33a7eSCosmo Chou loff_t *ppos) 523*5de33a7eSCosmo Chou { 524*5de33a7eSCosmo Chou struct pt5161l_data *data = file->private_data; 525*5de33a7eSCosmo Chou int ret; 526*5de33a7eSCosmo Chou bool status = false; 527*5de33a7eSCosmo Chou char health[16]; 528*5de33a7eSCosmo Chou 529*5de33a7eSCosmo Chou mutex_lock(&data->lock); 530*5de33a7eSCosmo Chou ret = pt5161l_fw_load_check(data); 531*5de33a7eSCosmo Chou mutex_unlock(&data->lock); 532*5de33a7eSCosmo Chou if (ret == 0) 533*5de33a7eSCosmo Chou status = data->code_load_okay; 534*5de33a7eSCosmo Chou 535*5de33a7eSCosmo Chou ret = snprintf(health, sizeof(health), "%s\n", 536*5de33a7eSCosmo Chou status ? "normal" : "abnormal"); 537*5de33a7eSCosmo Chou 538*5de33a7eSCosmo Chou return simple_read_from_buffer(buf, count, ppos, health, ret); 539*5de33a7eSCosmo Chou } 540*5de33a7eSCosmo Chou 541*5de33a7eSCosmo Chou static const struct file_operations pt5161l_debugfs_ops_fw_load_sts = { 542*5de33a7eSCosmo Chou .read = pt5161l_debugfs_read_fw_load_sts, 543*5de33a7eSCosmo Chou .open = simple_open, 544*5de33a7eSCosmo Chou }; 545*5de33a7eSCosmo Chou 546*5de33a7eSCosmo Chou static ssize_t pt5161l_debugfs_read_hb_sts(struct file *file, char __user *buf, 547*5de33a7eSCosmo Chou size_t count, loff_t *ppos) 548*5de33a7eSCosmo Chou { 549*5de33a7eSCosmo Chou struct pt5161l_data *data = file->private_data; 550*5de33a7eSCosmo Chou int ret; 551*5de33a7eSCosmo Chou bool status = false; 552*5de33a7eSCosmo Chou char health[16]; 553*5de33a7eSCosmo Chou 554*5de33a7eSCosmo Chou mutex_lock(&data->lock); 555*5de33a7eSCosmo Chou ret = pt5161l_heartbeat_check(data); 556*5de33a7eSCosmo Chou mutex_unlock(&data->lock); 557*5de33a7eSCosmo Chou if (ret == 0) 558*5de33a7eSCosmo Chou status = data->mm_heartbeat_okay; 559*5de33a7eSCosmo Chou 560*5de33a7eSCosmo Chou ret = snprintf(health, sizeof(health), "%s\n", 561*5de33a7eSCosmo Chou status ? "normal" : "abnormal"); 562*5de33a7eSCosmo Chou 563*5de33a7eSCosmo Chou return simple_read_from_buffer(buf, count, ppos, health, ret); 564*5de33a7eSCosmo Chou } 565*5de33a7eSCosmo Chou 566*5de33a7eSCosmo Chou static const struct file_operations pt5161l_debugfs_ops_hb_sts = { 567*5de33a7eSCosmo Chou .read = pt5161l_debugfs_read_hb_sts, 568*5de33a7eSCosmo Chou .open = simple_open, 569*5de33a7eSCosmo Chou }; 570*5de33a7eSCosmo Chou 571*5de33a7eSCosmo Chou static int pt5161l_init_debugfs(struct pt5161l_data *data) 572*5de33a7eSCosmo Chou { 573*5de33a7eSCosmo Chou data->debugfs = debugfs_create_dir(dev_name(&data->client->dev), 574*5de33a7eSCosmo Chou pt5161l_debugfs_dir); 575*5de33a7eSCosmo Chou 576*5de33a7eSCosmo Chou debugfs_create_file("fw_ver", 0444, data->debugfs, data, 577*5de33a7eSCosmo Chou &pt5161l_debugfs_ops_fw_ver); 578*5de33a7eSCosmo Chou 579*5de33a7eSCosmo Chou debugfs_create_file("fw_load_status", 0444, data->debugfs, data, 580*5de33a7eSCosmo Chou &pt5161l_debugfs_ops_fw_load_sts); 581*5de33a7eSCosmo Chou 582*5de33a7eSCosmo Chou debugfs_create_file("heartbeat_status", 0444, data->debugfs, data, 583*5de33a7eSCosmo Chou &pt5161l_debugfs_ops_hb_sts); 584*5de33a7eSCosmo Chou 585*5de33a7eSCosmo Chou return 0; 586*5de33a7eSCosmo Chou } 587*5de33a7eSCosmo Chou 588*5de33a7eSCosmo Chou static int pt5161l_probe(struct i2c_client *client) 589*5de33a7eSCosmo Chou { 590*5de33a7eSCosmo Chou struct device *dev = &client->dev; 591*5de33a7eSCosmo Chou struct device *hwmon_dev; 592*5de33a7eSCosmo Chou struct pt5161l_data *data; 593*5de33a7eSCosmo Chou 594*5de33a7eSCosmo Chou data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); 595*5de33a7eSCosmo Chou if (!data) 596*5de33a7eSCosmo Chou return -ENOMEM; 597*5de33a7eSCosmo Chou 598*5de33a7eSCosmo Chou data->client = client; 599*5de33a7eSCosmo Chou mutex_init(&data->lock); 600*5de33a7eSCosmo Chou pt5161l_init_dev(data); 601*5de33a7eSCosmo Chou dev_set_drvdata(dev, data); 602*5de33a7eSCosmo Chou 603*5de33a7eSCosmo Chou hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, 604*5de33a7eSCosmo Chou data, 605*5de33a7eSCosmo Chou &pt5161l_chip_info, 606*5de33a7eSCosmo Chou NULL); 607*5de33a7eSCosmo Chou 608*5de33a7eSCosmo Chou pt5161l_init_debugfs(data); 609*5de33a7eSCosmo Chou 610*5de33a7eSCosmo Chou return PTR_ERR_OR_ZERO(hwmon_dev); 611*5de33a7eSCosmo Chou } 612*5de33a7eSCosmo Chou 613*5de33a7eSCosmo Chou static void pt5161l_remove(struct i2c_client *client) 614*5de33a7eSCosmo Chou { 615*5de33a7eSCosmo Chou struct pt5161l_data *data = i2c_get_clientdata(client); 616*5de33a7eSCosmo Chou 617*5de33a7eSCosmo Chou debugfs_remove_recursive(data->debugfs); 618*5de33a7eSCosmo Chou } 619*5de33a7eSCosmo Chou 620*5de33a7eSCosmo Chou static const struct of_device_id __maybe_unused pt5161l_of_match[] = { 621*5de33a7eSCosmo Chou { .compatible = "asteralabs,pt5161l" }, 622*5de33a7eSCosmo Chou {}, 623*5de33a7eSCosmo Chou }; 624*5de33a7eSCosmo Chou MODULE_DEVICE_TABLE(of, pt5161l_of_match); 625*5de33a7eSCosmo Chou 626*5de33a7eSCosmo Chou static const struct acpi_device_id __maybe_unused pt5161l_acpi_match[] = { 627*5de33a7eSCosmo Chou { "PT5161L", 0 }, 628*5de33a7eSCosmo Chou {}, 629*5de33a7eSCosmo Chou }; 630*5de33a7eSCosmo Chou MODULE_DEVICE_TABLE(acpi, pt5161l_acpi_match); 631*5de33a7eSCosmo Chou 632*5de33a7eSCosmo Chou static const struct i2c_device_id pt5161l_id[] = { 633*5de33a7eSCosmo Chou { "pt5161l", 0 }, 634*5de33a7eSCosmo Chou {} 635*5de33a7eSCosmo Chou }; 636*5de33a7eSCosmo Chou MODULE_DEVICE_TABLE(i2c, pt5161l_id); 637*5de33a7eSCosmo Chou 638*5de33a7eSCosmo Chou static struct i2c_driver pt5161l_driver = { 639*5de33a7eSCosmo Chou .class = I2C_CLASS_HWMON, 640*5de33a7eSCosmo Chou .driver = { 641*5de33a7eSCosmo Chou .name = "pt5161l", 642*5de33a7eSCosmo Chou .of_match_table = of_match_ptr(pt5161l_of_match), 643*5de33a7eSCosmo Chou .acpi_match_table = ACPI_PTR(pt5161l_acpi_match), 644*5de33a7eSCosmo Chou }, 645*5de33a7eSCosmo Chou .probe = pt5161l_probe, 646*5de33a7eSCosmo Chou .remove = pt5161l_remove, 647*5de33a7eSCosmo Chou .id_table = pt5161l_id, 648*5de33a7eSCosmo Chou }; 649*5de33a7eSCosmo Chou 650*5de33a7eSCosmo Chou static int __init pt5161l_init(void) 651*5de33a7eSCosmo Chou { 652*5de33a7eSCosmo Chou pt5161l_debugfs_dir = debugfs_create_dir("pt5161l", NULL); 653*5de33a7eSCosmo Chou return i2c_add_driver(&pt5161l_driver); 654*5de33a7eSCosmo Chou } 655*5de33a7eSCosmo Chou 656*5de33a7eSCosmo Chou static void __exit pt5161l_exit(void) 657*5de33a7eSCosmo Chou { 658*5de33a7eSCosmo Chou i2c_del_driver(&pt5161l_driver); 659*5de33a7eSCosmo Chou debugfs_remove_recursive(pt5161l_debugfs_dir); 660*5de33a7eSCosmo Chou } 661*5de33a7eSCosmo Chou 662*5de33a7eSCosmo Chou module_init(pt5161l_init); 663*5de33a7eSCosmo Chou module_exit(pt5161l_exit); 664*5de33a7eSCosmo Chou 665*5de33a7eSCosmo Chou MODULE_AUTHOR("Cosmo Chou <cosmo.chou@quantatw.com>"); 666*5de33a7eSCosmo Chou MODULE_DESCRIPTION("Hwmon driver for Astera Labs Aries PCIe retimer"); 667*5de33a7eSCosmo Chou MODULE_LICENSE("GPL"); 668