1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Driver for MPS MP5990 Hot-Swap Controller 4 */ 5 6 #include <linux/i2c.h> 7 #include <linux/module.h> 8 #include <linux/of_device.h> 9 #include "pmbus.h" 10 11 enum chips { mp5990, mp5998 }; 12 13 #define MP5990_EFUSE_CFG (0xC4) 14 #define MP5990_VOUT_FORMAT BIT(9) 15 16 struct mp5990_data { 17 struct pmbus_driver_info info; 18 u8 vout_mode; 19 u8 vout_linear_exponent; 20 }; 21 22 #define to_mp5990_data(x) container_of(x, struct mp5990_data, info) 23 24 static int mp5990_read_byte_data(struct i2c_client *client, int page, int reg) 25 { 26 const struct pmbus_driver_info *info = pmbus_get_driver_info(client); 27 struct mp5990_data *data = to_mp5990_data(info); 28 29 switch (reg) { 30 case PMBUS_VOUT_MODE: 31 if (data->vout_mode == linear) { 32 /* 33 * The VOUT format used by the chip is linear11, 34 * not linear16. Report that VOUT is in linear mode 35 * and return exponent value extracted while probing 36 * the chip. 37 */ 38 return data->vout_linear_exponent; 39 } 40 41 /* 42 * The datasheet does not support the VOUT command, 43 * but the device responds with a default value of 0x17. 44 * In the standard, 0x17 represents linear mode. 45 * Therefore, we should report that VOUT is in direct 46 * format when the chip is configured for it. 47 */ 48 return PB_VOUT_MODE_DIRECT; 49 50 default: 51 return -ENODATA; 52 } 53 } 54 55 static int mp5990_read_word_data(struct i2c_client *client, int page, 56 int phase, int reg) 57 { 58 const struct pmbus_driver_info *info = pmbus_get_driver_info(client); 59 struct mp5990_data *data = to_mp5990_data(info); 60 int ret; 61 s32 mantissa; 62 63 switch (reg) { 64 case PMBUS_READ_VOUT: 65 ret = pmbus_read_word_data(client, page, phase, reg); 66 if (ret < 0) 67 return ret; 68 /* 69 * Because the VOUT format used by the chip is linear11 and not 70 * linear16, we disregard bits[15:11]. The exponent is reported 71 * as part of the VOUT_MODE command. 72 */ 73 if (data->vout_mode == linear) { 74 mantissa = ((s16)((ret & 0x7ff) << 5)) >> 5; 75 ret = mantissa; 76 } 77 break; 78 default: 79 return -ENODATA; 80 } 81 82 return ret; 83 } 84 85 static struct pmbus_driver_info mp5990_info = { 86 .pages = 1, 87 .format[PSC_VOLTAGE_IN] = direct, 88 .format[PSC_VOLTAGE_OUT] = direct, 89 .format[PSC_CURRENT_OUT] = direct, 90 .format[PSC_POWER] = direct, 91 .format[PSC_TEMPERATURE] = direct, 92 .m[PSC_VOLTAGE_IN] = 32, 93 .b[PSC_VOLTAGE_IN] = 0, 94 .R[PSC_VOLTAGE_IN] = 0, 95 .m[PSC_VOLTAGE_OUT] = 32, 96 .b[PSC_VOLTAGE_OUT] = 0, 97 .R[PSC_VOLTAGE_OUT] = 0, 98 .m[PSC_CURRENT_OUT] = 16, 99 .b[PSC_CURRENT_OUT] = 0, 100 .R[PSC_CURRENT_OUT] = 0, 101 .m[PSC_POWER] = 1, 102 .b[PSC_POWER] = 0, 103 .R[PSC_POWER] = 0, 104 .m[PSC_TEMPERATURE] = 1, 105 .b[PSC_TEMPERATURE] = 0, 106 .R[PSC_TEMPERATURE] = 0, 107 .func[0] = 108 PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_PIN | 109 PMBUS_HAVE_TEMP | PMBUS_HAVE_IOUT | 110 PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP, 111 .read_byte_data = mp5990_read_byte_data, 112 .read_word_data = mp5990_read_word_data, 113 }; 114 115 static struct pmbus_driver_info mp5998_info = { 116 .pages = 1, 117 .format[PSC_VOLTAGE_IN] = direct, 118 .format[PSC_VOLTAGE_OUT] = direct, 119 .format[PSC_CURRENT_IN] = direct, 120 .format[PSC_CURRENT_OUT] = direct, 121 .format[PSC_POWER] = direct, 122 .format[PSC_TEMPERATURE] = direct, 123 .m[PSC_VOLTAGE_IN] = 64, 124 .b[PSC_VOLTAGE_IN] = 0, 125 .R[PSC_VOLTAGE_IN] = 0, 126 .m[PSC_VOLTAGE_OUT] = 64, 127 .b[PSC_VOLTAGE_OUT] = 0, 128 .R[PSC_VOLTAGE_OUT] = 0, 129 .m[PSC_CURRENT_IN] = 16, 130 .b[PSC_CURRENT_IN] = 0, 131 .R[PSC_CURRENT_IN] = 0, 132 .m[PSC_CURRENT_OUT] = 16, 133 .b[PSC_CURRENT_OUT] = 0, 134 .R[PSC_CURRENT_OUT] = 0, 135 .m[PSC_POWER] = 2, 136 .b[PSC_POWER] = 0, 137 .R[PSC_POWER] = 0, 138 .m[PSC_TEMPERATURE] = 1, 139 .b[PSC_TEMPERATURE] = 0, 140 .R[PSC_TEMPERATURE] = 0, 141 .func[0] = 142 PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | 143 PMBUS_HAVE_IIN | PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | 144 PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_IOUT | 145 PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP, 146 .read_byte_data = mp5990_read_byte_data, 147 .read_word_data = mp5990_read_word_data, 148 }; 149 150 static const struct i2c_device_id mp5990_id[] = { 151 {"mp5990", mp5990}, 152 {"mp5998", mp5998}, 153 { } 154 }; 155 MODULE_DEVICE_TABLE(i2c, mp5990_id); 156 157 static int mp5990_probe(struct i2c_client *client) 158 { 159 struct pmbus_driver_info *info; 160 struct mp5990_data *data; 161 enum chips chip; 162 int ret; 163 164 data = devm_kzalloc(&client->dev, sizeof(struct mp5990_data), 165 GFP_KERNEL); 166 if (!data) 167 return -ENOMEM; 168 169 if (client->dev.of_node) 170 chip = (uintptr_t)of_device_get_match_data(&client->dev); 171 else 172 chip = i2c_match_id(mp5990_id, client)->driver_data; 173 174 if (chip == mp5990) 175 memcpy(&data->info, &mp5990_info, sizeof(*info)); 176 else 177 memcpy(&data->info, &mp5998_info, sizeof(*info)); 178 info = &data->info; 179 180 /* Read Vout Config */ 181 ret = i2c_smbus_read_word_data(client, MP5990_EFUSE_CFG); 182 if (ret < 0) { 183 dev_err(&client->dev, "Can't get vout mode."); 184 return ret; 185 } 186 187 /* 188 * EFUSE_CFG (0xC4) bit9=1 is linear mode, bit=0 is direct mode. 189 */ 190 if (ret & MP5990_VOUT_FORMAT) { 191 data->vout_mode = linear; 192 data->info.format[PSC_VOLTAGE_IN] = linear; 193 data->info.format[PSC_VOLTAGE_OUT] = linear; 194 data->info.format[PSC_CURRENT_OUT] = linear; 195 data->info.format[PSC_POWER] = linear; 196 if (chip == mp5998) 197 data->info.format[PSC_CURRENT_IN] = linear; 198 199 ret = i2c_smbus_read_word_data(client, PMBUS_READ_VOUT); 200 if (ret < 0) { 201 dev_err(&client->dev, "Can't get vout exponent."); 202 return ret; 203 } 204 data->vout_linear_exponent = (u8)((ret >> 11) & 0x1f); 205 } else { 206 data->vout_mode = direct; 207 } 208 return pmbus_do_probe(client, info); 209 } 210 211 static const struct of_device_id mp5990_of_match[] = { 212 { .compatible = "mps,mp5990", .data = (void *)mp5990 }, 213 { .compatible = "mps,mp5998", .data = (void *)mp5998 }, 214 {} 215 }; 216 217 static struct i2c_driver mp5990_driver = { 218 .driver = { 219 .name = "mp5990", 220 .of_match_table = mp5990_of_match, 221 }, 222 .probe = mp5990_probe, 223 .id_table = mp5990_id, 224 }; 225 module_i2c_driver(mp5990_driver); 226 227 MODULE_AUTHOR("Peter Yin <peter.yin@quantatw.com>"); 228 MODULE_DESCRIPTION("PMBus driver for MP5990 HSC"); 229 MODULE_LICENSE("GPL"); 230 MODULE_IMPORT_NS("PMBUS"); 231