Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

hwmon: add MP9941 driver

Add support for MPS step-down converter mp9941. This driver exposes
telemetry and limit value readings and writtings.

Signed-off-by: Noah Wang <noahwang.wang@outlook.com>
Link: https://lore.kernel.org/r/SEYPR04MB648294005D55F70736B519F6FAC72@SEYPR04MB6482.apcprd04.prod.outlook.com
[groeck: Include bitfield.h (for FIELD_PREP) and bits.h (for GENMASK)]
Signed-off-by: Guenter Roeck <linux@roeck-us.net>

authored by

Noah Wang and committed by
Guenter Roeck
dc5abc2f 512554f6

+429
+1
Documentation/hwmon/index.rst
··· 169 169 mp2993 170 170 mp5023 171 171 mp5990 172 + mp9941 172 173 mpq8785 173 174 nct6683 174 175 nct6775
+92
Documentation/hwmon/mp9941.rst
··· 1 + .. SPDX-License-Identifier: GPL-2.0 2 + 3 + Kernel driver mp9941 4 + ==================== 5 + 6 + Supported chips: 7 + 8 + * MPS mp9941 9 + 10 + Prefix: 'mp9941' 11 + 12 + * Datasheet 13 + https://scnbwymvp-my.sharepoint.com/:f:/g/personal/admin_scnbwy_com/Eth4kX1_J1hMsaASHiOYL4QBHU5a75r-tRfLKbHnJFdKLQ?e=vxj3DF 14 + 15 + Author: 16 + 17 + Noah Wang <noahwang.wang@outlook.com> 18 + 19 + Description 20 + ----------- 21 + 22 + This driver implements support for Monolithic Power Systems, Inc. (MPS) 23 + MP9941 digital step-down converter. 24 + 25 + Device compliant with: 26 + 27 + - PMBus rev 1.3 interface. 28 + 29 + The driver exports the following attributes via the 'sysfs' files 30 + for input voltage: 31 + 32 + **in1_input** 33 + 34 + **in1_label** 35 + 36 + **in1_crit** 37 + 38 + **in1_crit_alarm** 39 + 40 + The driver provides the following attributes for output voltage: 41 + 42 + **in2_input** 43 + 44 + **in2_label** 45 + 46 + **in2_lcrit** 47 + 48 + **in2_lcrit_alarm** 49 + 50 + **in2_rated_max** 51 + 52 + **in2_rated_min** 53 + 54 + The driver provides the following attributes for input current: 55 + 56 + **curr1_input** 57 + 58 + **curr1_label** 59 + 60 + **curr1_max** 61 + 62 + **curr1_max_alarm** 63 + 64 + The driver provides the following attributes for output current: 65 + 66 + **curr2_input** 67 + 68 + **curr2_label** 69 + 70 + The driver provides the following attributes for input power: 71 + 72 + **power1_input** 73 + 74 + **power1_label** 75 + 76 + The driver provides the following attributes for output power: 77 + 78 + **power2_input** 79 + 80 + **power2_label** 81 + 82 + The driver provides the following attributes for temperature: 83 + 84 + **temp1_input** 85 + 86 + **temp1_crit** 87 + 88 + **temp1_crit_alarm** 89 + 90 + **temp1_max** 91 + 92 + **temp1_max_alarm**
+7
MAINTAINERS
··· 15265 15265 F: Documentation/hwmon/mp2993.rst 15266 15266 F: drivers/hwmon/pmbus/mp2993.c 15267 15267 15268 + MPS MP9941 DRIVER 15269 + M: Noah Wang <noahwang.wang@outlook.com> 15270 + L: linux-hwmon@vger.kernel.org 15271 + S: Maintained 15272 + F: Documentation/hwmon/mp9941.rst 15273 + F: drivers/hwmon/pmbus/mp9941.c 15274 + 15268 15275 MR800 AVERMEDIA USB FM RADIO DRIVER 15269 15276 M: Alexey Klimov <klimov.linux@gmail.com> 15270 15277 L: linux-media@vger.kernel.org
+9
drivers/hwmon/pmbus/Kconfig
··· 380 380 This driver can also be built as a module. If so, the module will 381 381 be called mp5990. 382 382 383 + config SENSORS_MP9941 384 + tristate "MPS MP9941" 385 + help 386 + If you say yes here you get hardware monitoring support for MPS 387 + MP9941. 388 + 389 + This driver can also be built as a module. If so, the module will 390 + be called mp9941. 391 + 383 392 config SENSORS_MPQ7932_REGULATOR 384 393 bool "Regulator support for MPQ7932" 385 394 depends on SENSORS_MPQ7932 && REGULATOR
+1
drivers/hwmon/pmbus/Makefile
··· 40 40 obj-$(CONFIG_SENSORS_MP2993) += mp2993.o 41 41 obj-$(CONFIG_SENSORS_MP5023) += mp5023.o 42 42 obj-$(CONFIG_SENSORS_MP5990) += mp5990.o 43 + obj-$(CONFIG_SENSORS_MP9941) += mp9941.o 43 44 obj-$(CONFIG_SENSORS_MPQ7932) += mpq7932.o 44 45 obj-$(CONFIG_SENSORS_MPQ8785) += mpq8785.o 45 46 obj-$(CONFIG_SENSORS_PLI1209BC) += pli1209bc.o
+319
drivers/hwmon/pmbus/mp9941.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-or-later 2 + /* 3 + * Hardware monitoring driver for MPS Multi-phase Digital VR Controllers(MP9941) 4 + */ 5 + 6 + #include <linux/bitfield.h> 7 + #include <linux/bits.h> 8 + #include <linux/i2c.h> 9 + #include <linux/module.h> 10 + #include <linux/of_device.h> 11 + #include "pmbus.h" 12 + 13 + /* 14 + * Vender specific registers. The MFR_ICC_MAX(0x02) is used to 15 + * config the iin scale. The MFR_RESO_SET(0xC7) is used to 16 + * config the vout format. The MFR_VR_MULTI_CONFIG_R1(0x0D) is 17 + * used to identify the vout vid step. 18 + */ 19 + #define MFR_ICC_MAX 0x02 20 + #define MFR_RESO_SET 0xC7 21 + #define MFR_VR_MULTI_CONFIG_R1 0x0D 22 + 23 + #define MP9941_VIN_LIMIT_UINT 1 24 + #define MP9941_VIN_LIMIT_DIV 8 25 + #define MP9941_READ_VIN_UINT 1 26 + #define MP9941_READ_VIN_DIV 32 27 + 28 + #define MP9941_PAGE_NUM 1 29 + 30 + #define MP9941_RAIL1_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | \ 31 + PMBUS_HAVE_IOUT | PMBUS_HAVE_POUT | \ 32 + PMBUS_HAVE_TEMP | PMBUS_HAVE_PIN | \ 33 + PMBUS_HAVE_IIN | \ 34 + PMBUS_HAVE_STATUS_VOUT | \ 35 + PMBUS_HAVE_STATUS_IOUT | \ 36 + PMBUS_HAVE_STATUS_TEMP | \ 37 + PMBUS_HAVE_STATUS_INPUT) 38 + 39 + struct mp9941_data { 40 + struct pmbus_driver_info info; 41 + int vid_resolution; 42 + }; 43 + 44 + #define to_mp9941_data(x) container_of(x, struct mp9941_data, info) 45 + 46 + static int mp9941_set_vout_format(struct i2c_client *client) 47 + { 48 + int ret; 49 + 50 + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); 51 + if (ret < 0) 52 + return ret; 53 + 54 + ret = i2c_smbus_read_word_data(client, MFR_RESO_SET); 55 + if (ret < 0) 56 + return ret; 57 + 58 + /* 59 + * page = 0, MFR_RESO_SET[7:6] defines the vout format 60 + * 2'b11 set the vout format as direct 61 + */ 62 + ret = (ret & ~GENMASK(7, 6)) | FIELD_PREP(GENMASK(7, 6), 3); 63 + 64 + return i2c_smbus_write_word_data(client, MFR_RESO_SET, ret); 65 + } 66 + 67 + static int 68 + mp9941_identify_vid_resolution(struct i2c_client *client, struct pmbus_driver_info *info) 69 + { 70 + struct mp9941_data *data = to_mp9941_data(info); 71 + int ret; 72 + 73 + /* 74 + * page = 2, MFR_VR_MULTI_CONFIG_R1[4:4] defines rail1 vid step value 75 + * 1'b0 represents the vid step value is 10mV 76 + * 1'b1 represents the vid step value is 5mV 77 + */ 78 + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 2); 79 + if (ret < 0) 80 + return ret; 81 + 82 + ret = i2c_smbus_read_word_data(client, MFR_VR_MULTI_CONFIG_R1); 83 + if (ret < 0) 84 + return ret; 85 + 86 + if (FIELD_GET(GENMASK(4, 4), ret)) 87 + data->vid_resolution = 5; 88 + else 89 + data->vid_resolution = 10; 90 + 91 + return 0; 92 + } 93 + 94 + static int mp9941_identify_iin_scale(struct i2c_client *client) 95 + { 96 + int ret; 97 + 98 + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); 99 + if (ret < 0) 100 + return ret; 101 + 102 + ret = i2c_smbus_read_word_data(client, MFR_RESO_SET); 103 + if (ret < 0) 104 + return ret; 105 + 106 + ret = (ret & ~GENMASK(3, 2)) | FIELD_PREP(GENMASK(3, 2), 0); 107 + 108 + ret = i2c_smbus_write_word_data(client, MFR_RESO_SET, ret); 109 + if (ret < 0) 110 + return ret; 111 + 112 + /* 113 + * page = 2, MFR_ICC_MAX[15:13] defines the iin scale 114 + * 3'b000 set the iout scale as 0.5A/Lsb 115 + */ 116 + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 2); 117 + if (ret < 0) 118 + return ret; 119 + 120 + ret = i2c_smbus_read_word_data(client, MFR_ICC_MAX); 121 + if (ret < 0) 122 + return ret; 123 + 124 + ret = (ret & ~GENMASK(15, 13)) | FIELD_PREP(GENMASK(15, 13), 0); 125 + 126 + return i2c_smbus_write_word_data(client, MFR_ICC_MAX, ret); 127 + } 128 + 129 + static int mp9941_identify(struct i2c_client *client, struct pmbus_driver_info *info) 130 + { 131 + int ret; 132 + 133 + ret = mp9941_identify_iin_scale(client); 134 + if (ret < 0) 135 + return ret; 136 + 137 + ret = mp9941_identify_vid_resolution(client, info); 138 + if (ret < 0) 139 + return ret; 140 + 141 + return mp9941_set_vout_format(client); 142 + } 143 + 144 + static int mp9941_read_word_data(struct i2c_client *client, int page, int phase, 145 + int reg) 146 + { 147 + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); 148 + struct mp9941_data *data = to_mp9941_data(info); 149 + int ret; 150 + 151 + switch (reg) { 152 + case PMBUS_READ_VIN: 153 + /* The MP9941 vin scale is (1/32V)/Lsb */ 154 + ret = pmbus_read_word_data(client, page, phase, reg); 155 + if (ret < 0) 156 + return ret; 157 + 158 + ret = DIV_ROUND_CLOSEST((ret & GENMASK(9, 0)) * MP9941_READ_VIN_UINT, 159 + MP9941_READ_VIN_DIV); 160 + break; 161 + case PMBUS_READ_IIN: 162 + ret = pmbus_read_word_data(client, page, phase, reg); 163 + if (ret < 0) 164 + return ret; 165 + 166 + ret = ret & GENMASK(10, 0); 167 + break; 168 + case PMBUS_VIN_OV_FAULT_LIMIT: 169 + /* The MP9941 vin ov limit scale is (1/8V)/Lsb */ 170 + ret = pmbus_read_word_data(client, page, phase, reg); 171 + if (ret < 0) 172 + return ret; 173 + 174 + ret = DIV_ROUND_CLOSEST((ret & GENMASK(7, 0)) * MP9941_VIN_LIMIT_UINT, 175 + MP9941_VIN_LIMIT_DIV); 176 + break; 177 + case PMBUS_IIN_OC_WARN_LIMIT: 178 + ret = pmbus_read_word_data(client, page, phase, reg); 179 + if (ret < 0) 180 + return ret; 181 + 182 + ret = ret & GENMASK(7, 0); 183 + break; 184 + case PMBUS_VOUT_UV_FAULT_LIMIT: 185 + case PMBUS_MFR_VOUT_MIN: 186 + case PMBUS_MFR_VOUT_MAX: 187 + /* 188 + * The vout scale is set to 1mV/Lsb(using r/m/b scale). 189 + * But the vout uv limit and vout max/min scale is 1VID/Lsb, 190 + * so the vout uv limit and vout max/min value should be 191 + * multiplied by vid resolution. 192 + */ 193 + ret = pmbus_read_word_data(client, page, phase, reg); 194 + if (ret < 0) 195 + return ret; 196 + 197 + ret = ret * data->vid_resolution; 198 + break; 199 + case PMBUS_READ_IOUT: 200 + case PMBUS_READ_POUT: 201 + case PMBUS_READ_TEMPERATURE_1: 202 + case PMBUS_READ_VOUT: 203 + case PMBUS_READ_PIN: 204 + case PMBUS_OT_FAULT_LIMIT: 205 + case PMBUS_OT_WARN_LIMIT: 206 + ret = -ENODATA; 207 + break; 208 + default: 209 + ret = -EINVAL; 210 + break; 211 + } 212 + 213 + return ret; 214 + } 215 + 216 + static int mp9941_write_word_data(struct i2c_client *client, int page, int reg, 217 + u16 word) 218 + { 219 + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); 220 + struct mp9941_data *data = to_mp9941_data(info); 221 + int ret; 222 + 223 + switch (reg) { 224 + case PMBUS_VIN_OV_FAULT_LIMIT: 225 + /* The MP9941 vin ov limit scale is (1/8V)/Lsb */ 226 + ret = pmbus_write_word_data(client, page, reg, 227 + DIV_ROUND_CLOSEST(word * MP9941_VIN_LIMIT_DIV, 228 + MP9941_VIN_LIMIT_UINT)); 229 + break; 230 + case PMBUS_VOUT_UV_FAULT_LIMIT: 231 + case PMBUS_MFR_VOUT_MIN: 232 + case PMBUS_MFR_VOUT_MAX: 233 + ret = pmbus_write_word_data(client, page, reg, 234 + DIV_ROUND_CLOSEST(word, data->vid_resolution)); 235 + break; 236 + case PMBUS_IIN_OC_WARN_LIMIT: 237 + case PMBUS_OT_FAULT_LIMIT: 238 + case PMBUS_OT_WARN_LIMIT: 239 + ret = -ENODATA; 240 + break; 241 + default: 242 + ret = -EINVAL; 243 + break; 244 + } 245 + 246 + return ret; 247 + } 248 + 249 + static const struct pmbus_driver_info mp9941_info = { 250 + .pages = MP9941_PAGE_NUM, 251 + .format[PSC_VOLTAGE_IN] = direct, 252 + .format[PSC_CURRENT_IN] = direct, 253 + .format[PSC_CURRENT_OUT] = linear, 254 + .format[PSC_POWER] = linear, 255 + .format[PSC_TEMPERATURE] = direct, 256 + .format[PSC_VOLTAGE_OUT] = direct, 257 + 258 + .m[PSC_TEMPERATURE] = 1, 259 + .R[PSC_TEMPERATURE] = 0, 260 + .b[PSC_TEMPERATURE] = 0, 261 + 262 + .m[PSC_VOLTAGE_IN] = 1, 263 + .R[PSC_VOLTAGE_IN] = 0, 264 + .b[PSC_VOLTAGE_IN] = 0, 265 + 266 + .m[PSC_CURRENT_IN] = 2, 267 + .R[PSC_CURRENT_IN] = 0, 268 + .b[PSC_CURRENT_IN] = 0, 269 + 270 + .m[PSC_VOLTAGE_OUT] = 1, 271 + .R[PSC_VOLTAGE_OUT] = 3, 272 + .b[PSC_VOLTAGE_OUT] = 0, 273 + 274 + .func[0] = MP9941_RAIL1_FUNC, 275 + .read_word_data = mp9941_read_word_data, 276 + .write_word_data = mp9941_write_word_data, 277 + .identify = mp9941_identify, 278 + }; 279 + 280 + static int mp9941_probe(struct i2c_client *client) 281 + { 282 + struct mp9941_data *data; 283 + 284 + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); 285 + if (!data) 286 + return -ENOMEM; 287 + 288 + memcpy(&data->info, &mp9941_info, sizeof(mp9941_info)); 289 + 290 + return pmbus_do_probe(client, &data->info); 291 + } 292 + 293 + static const struct i2c_device_id mp9941_id[] = { 294 + {"mp9941", 0}, 295 + {} 296 + }; 297 + MODULE_DEVICE_TABLE(i2c, mp9941_id); 298 + 299 + static const struct of_device_id __maybe_unused mp9941_of_match[] = { 300 + {.compatible = "mps,mp9941"}, 301 + {} 302 + }; 303 + MODULE_DEVICE_TABLE(of, mp9941_of_match); 304 + 305 + static struct i2c_driver mp9941_driver = { 306 + .driver = { 307 + .name = "mp9941", 308 + .of_match_table = mp9941_of_match, 309 + }, 310 + .probe = mp9941_probe, 311 + .id_table = mp9941_id, 312 + }; 313 + 314 + module_i2c_driver(mp9941_driver); 315 + 316 + MODULE_AUTHOR("Noah Wang <noahwang.wang@outlook.com>"); 317 + MODULE_DESCRIPTION("PMBus driver for MPS MP9941"); 318 + MODULE_LICENSE("GPL"); 319 + MODULE_IMPORT_NS(PMBUS);