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