1*ff71049fSWensheng Wang // SPDX-License-Identifier: GPL-2.0-or-later 2*ff71049fSWensheng Wang /* 3*ff71049fSWensheng Wang * Hardware monitoring driver for MPS Synchronous Step-Down Converter(MPQ82D00) 4*ff71049fSWensheng Wang */ 5*ff71049fSWensheng Wang 6*ff71049fSWensheng Wang #include <linux/bitfield.h> 7*ff71049fSWensheng Wang #include <linux/err.h> 8*ff71049fSWensheng Wang #include <linux/i2c.h> 9*ff71049fSWensheng Wang #include <linux/init.h> 10*ff71049fSWensheng Wang #include <linux/kernel.h> 11*ff71049fSWensheng Wang #include <linux/module.h> 12*ff71049fSWensheng Wang #include "pmbus.h" 13*ff71049fSWensheng Wang 14*ff71049fSWensheng Wang #define MPQ82D00_VOUT_DIV 64 15*ff71049fSWensheng Wang 16*ff71049fSWensheng Wang #define MPQ82D00_PAGE_NUM 1 17*ff71049fSWensheng Wang 18*ff71049fSWensheng Wang #define MPQ82D00_RAIL1_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | \ 19*ff71049fSWensheng Wang PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP | \ 20*ff71049fSWensheng Wang PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | \ 21*ff71049fSWensheng Wang PMBUS_HAVE_STATUS_VOUT | \ 22*ff71049fSWensheng Wang PMBUS_HAVE_STATUS_IOUT | \ 23*ff71049fSWensheng Wang PMBUS_HAVE_STATUS_TEMP | \ 24*ff71049fSWensheng Wang PMBUS_HAVE_STATUS_INPUT) 25*ff71049fSWensheng Wang 26*ff71049fSWensheng Wang struct mpq82d00_data { 27*ff71049fSWensheng Wang struct pmbus_driver_info info; 28*ff71049fSWensheng Wang int vout_scale; 29*ff71049fSWensheng Wang }; 30*ff71049fSWensheng Wang 31*ff71049fSWensheng Wang #define to_mpq82d00_data(x) container_of(x, struct mpq82d00_data, info) 32*ff71049fSWensheng Wang 33*ff71049fSWensheng Wang static u16 mpq82d00_linear_exp_transfer(u16 word, u16 expect_exponent) 34*ff71049fSWensheng Wang { 35*ff71049fSWensheng Wang s16 exponent, mantissa, target_exponent; 36*ff71049fSWensheng Wang 37*ff71049fSWensheng Wang exponent = ((s16)word) >> 11; 38*ff71049fSWensheng Wang mantissa = ((s16)((word & 0x7ff) << 5)) >> 5; 39*ff71049fSWensheng Wang target_exponent = (s16)((expect_exponent & 0x1f) << 11) >> 11; 40*ff71049fSWensheng Wang 41*ff71049fSWensheng Wang /* 42*ff71049fSWensheng Wang * The MPQ82D00 does not support negtive limit value, if a negtive 43*ff71049fSWensheng Wang * limit value is written, the limit value will become to 0. And 44*ff71049fSWensheng Wang * the maximum positive limit value is limitted to 0x3FF. 45*ff71049fSWensheng Wang */ 46*ff71049fSWensheng Wang if (mantissa < 0) { 47*ff71049fSWensheng Wang mantissa = 0; 48*ff71049fSWensheng Wang } else { 49*ff71049fSWensheng Wang if (exponent > target_exponent) { 50*ff71049fSWensheng Wang mantissa = (1023 >> (exponent - target_exponent)) >= mantissa ? 51*ff71049fSWensheng Wang mantissa << (exponent - target_exponent) : 52*ff71049fSWensheng Wang 0x3FF; 53*ff71049fSWensheng Wang } else { 54*ff71049fSWensheng Wang mantissa = clamp_val(mantissa >> (target_exponent - exponent), 55*ff71049fSWensheng Wang 0, 0x3FF); 56*ff71049fSWensheng Wang } 57*ff71049fSWensheng Wang } 58*ff71049fSWensheng Wang 59*ff71049fSWensheng Wang return mantissa | ((expect_exponent << 11) & 0xf800); 60*ff71049fSWensheng Wang } 61*ff71049fSWensheng Wang 62*ff71049fSWensheng Wang static int mpq82d00_read_byte_data(struct i2c_client *client, int page, int reg) 63*ff71049fSWensheng Wang { 64*ff71049fSWensheng Wang int ret; 65*ff71049fSWensheng Wang 66*ff71049fSWensheng Wang switch (reg) { 67*ff71049fSWensheng Wang case PMBUS_VOUT_MODE: 68*ff71049fSWensheng Wang /* 69*ff71049fSWensheng Wang * The MPQ82D00 does not follow standard PMBus protocol completely, 70*ff71049fSWensheng Wang * and the calculation of vout in this driver is based on direct 71*ff71049fSWensheng Wang * format. As a result, the format of vout is enforced to direct. 72*ff71049fSWensheng Wang */ 73*ff71049fSWensheng Wang ret = PB_VOUT_MODE_DIRECT; 74*ff71049fSWensheng Wang break; 75*ff71049fSWensheng Wang default: 76*ff71049fSWensheng Wang /* 77*ff71049fSWensheng Wang * These registers are not explicitly handled by the driver, 78*ff71049fSWensheng Wang * as a result, return -ENODATA directly. 79*ff71049fSWensheng Wang */ 80*ff71049fSWensheng Wang ret = -ENODATA; 81*ff71049fSWensheng Wang break; 82*ff71049fSWensheng Wang } 83*ff71049fSWensheng Wang 84*ff71049fSWensheng Wang return ret; 85*ff71049fSWensheng Wang } 86*ff71049fSWensheng Wang 87*ff71049fSWensheng Wang static int mpq82d00_read_word_data(struct i2c_client *client, int page, 88*ff71049fSWensheng Wang int phase, int reg) 89*ff71049fSWensheng Wang { 90*ff71049fSWensheng Wang const struct pmbus_driver_info *info = pmbus_get_driver_info(client); 91*ff71049fSWensheng Wang struct mpq82d00_data *data = to_mpq82d00_data(info); 92*ff71049fSWensheng Wang int ret; 93*ff71049fSWensheng Wang 94*ff71049fSWensheng Wang switch (reg) { 95*ff71049fSWensheng Wang case PMBUS_STATUS_WORD: 96*ff71049fSWensheng Wang case PMBUS_READ_VIN: 97*ff71049fSWensheng Wang case PMBUS_READ_IOUT: 98*ff71049fSWensheng Wang case PMBUS_READ_POUT: 99*ff71049fSWensheng Wang case PMBUS_READ_PIN: 100*ff71049fSWensheng Wang case PMBUS_READ_TEMPERATURE_1: 101*ff71049fSWensheng Wang case PMBUS_IOUT_OC_FAULT_LIMIT: 102*ff71049fSWensheng Wang case PMBUS_OT_FAULT_LIMIT: 103*ff71049fSWensheng Wang /* 104*ff71049fSWensheng Wang * These registers are not explicitly handled by the driver, 105*ff71049fSWensheng Wang * as a result, return -ENODATA directly. 106*ff71049fSWensheng Wang */ 107*ff71049fSWensheng Wang ret = -ENODATA; 108*ff71049fSWensheng Wang break; 109*ff71049fSWensheng Wang case PMBUS_READ_VOUT: 110*ff71049fSWensheng Wang ret = pmbus_read_word_data(client, page, phase, reg); 111*ff71049fSWensheng Wang if (ret < 0) 112*ff71049fSWensheng Wang return ret; 113*ff71049fSWensheng Wang 114*ff71049fSWensheng Wang ret = DIV_ROUND_CLOSEST((ret & GENMASK(11, 0)) * data->vout_scale, 115*ff71049fSWensheng Wang MPQ82D00_VOUT_DIV); 116*ff71049fSWensheng Wang break; 117*ff71049fSWensheng Wang default: 118*ff71049fSWensheng Wang /* 119*ff71049fSWensheng Wang * The MPQ82D00 do not support other telemetry and limit 120*ff71049fSWensheng Wang * value reading, so, return -EINVAL directly. 121*ff71049fSWensheng Wang */ 122*ff71049fSWensheng Wang ret = -EINVAL; 123*ff71049fSWensheng Wang break; 124*ff71049fSWensheng Wang } 125*ff71049fSWensheng Wang 126*ff71049fSWensheng Wang return ret; 127*ff71049fSWensheng Wang } 128*ff71049fSWensheng Wang 129*ff71049fSWensheng Wang static int mpq82d00_write_word_data(struct i2c_client *client, int page, int reg, 130*ff71049fSWensheng Wang u16 word) 131*ff71049fSWensheng Wang { 132*ff71049fSWensheng Wang int ret; 133*ff71049fSWensheng Wang 134*ff71049fSWensheng Wang switch (reg) { 135*ff71049fSWensheng Wang case PMBUS_OT_FAULT_LIMIT: 136*ff71049fSWensheng Wang /* 137*ff71049fSWensheng Wang * The PMBUS_OT_FAULT_LIMIT of MPQ82D00 is linear11 format, 138*ff71049fSWensheng Wang * and the exponent is a constant value(5'b00000), so the 139*ff71049fSWensheng Wang * exponent of word parameter should be converted to 5'b00000. 140*ff71049fSWensheng Wang */ 141*ff71049fSWensheng Wang ret = pmbus_write_word_data(client, page, reg, 142*ff71049fSWensheng Wang mpq82d00_linear_exp_transfer(word, 0x00)); 143*ff71049fSWensheng Wang break; 144*ff71049fSWensheng Wang case PMBUS_IOUT_OC_FAULT_LIMIT: 145*ff71049fSWensheng Wang /* 146*ff71049fSWensheng Wang * The PMBUS_IOUT_OC_FAULT_LIMIT of MPQ82D00 is linear11 format, 147*ff71049fSWensheng Wang * and the exponent is a constant value(5'b11100), so the 148*ff71049fSWensheng Wang * exponent of word parameter should be converted to 5'b11100. 149*ff71049fSWensheng Wang */ 150*ff71049fSWensheng Wang ret = pmbus_write_word_data(client, page, reg, 151*ff71049fSWensheng Wang mpq82d00_linear_exp_transfer(word, 0x1C)); 152*ff71049fSWensheng Wang break; 153*ff71049fSWensheng Wang default: 154*ff71049fSWensheng Wang /* 155*ff71049fSWensheng Wang * The MPQ82D00 do not support other limit value configuration, 156*ff71049fSWensheng Wang * so, return -EINVAL directly. 157*ff71049fSWensheng Wang */ 158*ff71049fSWensheng Wang ret = -EINVAL; 159*ff71049fSWensheng Wang break; 160*ff71049fSWensheng Wang } 161*ff71049fSWensheng Wang 162*ff71049fSWensheng Wang return ret; 163*ff71049fSWensheng Wang } 164*ff71049fSWensheng Wang 165*ff71049fSWensheng Wang static int mpq82d00_identify(struct i2c_client *client, struct pmbus_driver_info *info) 166*ff71049fSWensheng Wang { 167*ff71049fSWensheng Wang struct mpq82d00_data *data = to_mpq82d00_data(info); 168*ff71049fSWensheng Wang int ret; 169*ff71049fSWensheng Wang 170*ff71049fSWensheng Wang ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); 171*ff71049fSWensheng Wang if (ret < 0) 172*ff71049fSWensheng Wang return ret; 173*ff71049fSWensheng Wang 174*ff71049fSWensheng Wang ret = i2c_smbus_read_byte_data(client, PMBUS_VOUT_MODE); 175*ff71049fSWensheng Wang if (ret < 0) 176*ff71049fSWensheng Wang return ret; 177*ff71049fSWensheng Wang 178*ff71049fSWensheng Wang /* 179*ff71049fSWensheng Wang * The MPQ82D00 supports three vout mode. If PMBUS_VOUT_MODE 180*ff71049fSWensheng Wang * bit5 is 1, the vout scale is 5mv/LSB.If PMBUS_VOUT_MODE bit5 181*ff71049fSWensheng Wang * is 0, it is linear mode, the vout scale is 1.953125mv/LSB. 182*ff71049fSWensheng Wang */ 183*ff71049fSWensheng Wang if (FIELD_GET(GENMASK(5, 5), ret)) 184*ff71049fSWensheng Wang data->vout_scale = 320; 185*ff71049fSWensheng Wang else 186*ff71049fSWensheng Wang data->vout_scale = 125; 187*ff71049fSWensheng Wang 188*ff71049fSWensheng Wang return 0; 189*ff71049fSWensheng Wang } 190*ff71049fSWensheng Wang 191*ff71049fSWensheng Wang static struct pmbus_driver_info mpq82d00_info = { 192*ff71049fSWensheng Wang .pages = MPQ82D00_PAGE_NUM, 193*ff71049fSWensheng Wang .format[PSC_VOLTAGE_IN] = linear, 194*ff71049fSWensheng Wang .format[PSC_CURRENT_OUT] = linear, 195*ff71049fSWensheng Wang .format[PSC_TEMPERATURE] = linear, 196*ff71049fSWensheng Wang .format[PSC_VOLTAGE_OUT] = direct, 197*ff71049fSWensheng Wang .format[PSC_POWER] = linear, 198*ff71049fSWensheng Wang 199*ff71049fSWensheng Wang .m[PSC_VOLTAGE_OUT] = 1, 200*ff71049fSWensheng Wang .R[PSC_VOLTAGE_OUT] = 3, 201*ff71049fSWensheng Wang .b[PSC_VOLTAGE_OUT] = 0, 202*ff71049fSWensheng Wang 203*ff71049fSWensheng Wang .func[0] = MPQ82D00_RAIL1_FUNC, 204*ff71049fSWensheng Wang .read_word_data = mpq82d00_read_word_data, 205*ff71049fSWensheng Wang .read_byte_data = mpq82d00_read_byte_data, 206*ff71049fSWensheng Wang .write_word_data = mpq82d00_write_word_data, 207*ff71049fSWensheng Wang .identify = mpq82d00_identify, 208*ff71049fSWensheng Wang }; 209*ff71049fSWensheng Wang 210*ff71049fSWensheng Wang static int mpq82d00_probe(struct i2c_client *client) 211*ff71049fSWensheng Wang { 212*ff71049fSWensheng Wang struct mpq82d00_data *data; 213*ff71049fSWensheng Wang 214*ff71049fSWensheng Wang data = devm_kzalloc(&client->dev, sizeof(struct mpq82d00_data), GFP_KERNEL); 215*ff71049fSWensheng Wang if (!data) 216*ff71049fSWensheng Wang return -ENOMEM; 217*ff71049fSWensheng Wang 218*ff71049fSWensheng Wang memcpy(&data->info, &mpq82d00_info, sizeof(mpq82d00_info)); 219*ff71049fSWensheng Wang 220*ff71049fSWensheng Wang return pmbus_do_probe(client, &data->info); 221*ff71049fSWensheng Wang } 222*ff71049fSWensheng Wang 223*ff71049fSWensheng Wang static const struct i2c_device_id mpq82d00_id[] = { 224*ff71049fSWensheng Wang {.name = "mpq82d00"}, 225*ff71049fSWensheng Wang {} 226*ff71049fSWensheng Wang }; 227*ff71049fSWensheng Wang MODULE_DEVICE_TABLE(i2c, mpq82d00_id); 228*ff71049fSWensheng Wang 229*ff71049fSWensheng Wang static const struct of_device_id __maybe_unused mpq82d00_of_match[] = { 230*ff71049fSWensheng Wang {.compatible = "mps,mpq82d00"}, 231*ff71049fSWensheng Wang {} 232*ff71049fSWensheng Wang }; 233*ff71049fSWensheng Wang MODULE_DEVICE_TABLE(of, mpq82d00_of_match); 234*ff71049fSWensheng Wang 235*ff71049fSWensheng Wang static struct i2c_driver mpq82d00_driver = { 236*ff71049fSWensheng Wang .driver = { 237*ff71049fSWensheng Wang .name = "mpq82d00", 238*ff71049fSWensheng Wang .of_match_table = mpq82d00_of_match, 239*ff71049fSWensheng Wang }, 240*ff71049fSWensheng Wang .probe = mpq82d00_probe, 241*ff71049fSWensheng Wang .id_table = mpq82d00_id, 242*ff71049fSWensheng Wang }; 243*ff71049fSWensheng Wang 244*ff71049fSWensheng Wang module_i2c_driver(mpq82d00_driver); 245*ff71049fSWensheng Wang 246*ff71049fSWensheng Wang MODULE_AUTHOR("Wensheng Wang <wenswang@yeah.net"); 247*ff71049fSWensheng Wang MODULE_DESCRIPTION("PMBus driver for MPS MPQ82D00 device"); 248*ff71049fSWensheng Wang MODULE_LICENSE("GPL"); 249*ff71049fSWensheng Wang MODULE_IMPORT_NS("PMBUS"); 250