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

hwmon: (pmbus) add driver for MPS MP9945

Add support for mp9945 device from Monolithic Power Systems, Inc.
(MPS) vendor. This is a single phase digital step down converter.

Signed-off-by: Cosmo Chou <chou.cosmo@gmail.com>
Link: https://lore.kernel.org/r/20251009205458.396368-2-chou.cosmo@gmail.com
Signed-off-by: Guenter Roeck <linux@roeck-us.net>

authored by

Cosmo Chou and committed by
Guenter Roeck
6923e282 6e11e29d

+378
+1
Documentation/hwmon/index.rst
··· 186 186 mp5920 187 187 mp5990 188 188 mp9941 189 + mp9945 189 190 mpq8785 190 191 nct6683 191 192 nct6775
+117
Documentation/hwmon/mp9945.rst
··· 1 + .. SPDX-License-Identifier: GPL-2.0 2 + 3 + Kernel driver mp9945 4 + ===================== 5 + 6 + Supported chips: 7 + 8 + * MPS mp9945 9 + 10 + Prefix: 'mp9945' 11 + 12 + Author: 13 + 14 + Cosmo Chou <chou.cosmo@gmail.com> 15 + 16 + Description 17 + ----------- 18 + 19 + This driver implements support for Monolithic Power Systems, Inc. (MPS) 20 + MP9945 Digital Single-phase Controller. 21 + 22 + Device compliant with: 23 + 24 + - PMBus rev 1.3 interface. 25 + 26 + The driver exports the following attributes via the 'sysfs' files 27 + for input voltage: 28 + 29 + **in1_input** 30 + 31 + **in1_label** 32 + 33 + **in1_crit** 34 + 35 + **in1_crit_alarm** 36 + 37 + **in1_lcrit** 38 + 39 + **in1_lcrit_alarm** 40 + 41 + **in1_max** 42 + 43 + **in1_max_alarm** 44 + 45 + **in1_min** 46 + 47 + **in1_min_alarm** 48 + 49 + The driver provides the following attributes for output voltage: 50 + 51 + **in2_input** 52 + 53 + **in2_label** 54 + 55 + **in2_crit** 56 + 57 + **in2_crit_alarm** 58 + 59 + **in2_lcrit** 60 + 61 + **in2_lcrit_alarm** 62 + 63 + **in2_min** 64 + 65 + **in2_min_alarm** 66 + 67 + The driver provides the following attributes for input current: 68 + 69 + **curr1_input** 70 + 71 + **curr1_label** 72 + 73 + **curr1_max** 74 + 75 + **curr1_max_alarm** 76 + 77 + The driver provides the following attributes for output current: 78 + 79 + **curr2_input** 80 + 81 + **curr2_label** 82 + 83 + **curr2_crit** 84 + 85 + **curr2_crit_alarm** 86 + 87 + **curr2_max** 88 + 89 + **curr2_max_alarm** 90 + 91 + The driver provides the following attributes for input power: 92 + 93 + **power1_input** 94 + 95 + **power1_label** 96 + 97 + The driver provides the following attributes for output power: 98 + 99 + **power2_input** 100 + 101 + **power2_label** 102 + 103 + **power2_max** 104 + 105 + **power2_max_alarm** 106 + 107 + The driver provides the following attributes for temperature: 108 + 109 + **temp1_input** 110 + 111 + **temp1_crit** 112 + 113 + **temp1_crit_alarm** 114 + 115 + **temp1_max** 116 + 117 + **temp1_max_alarm**
+7
MAINTAINERS
··· 17499 17499 F: Documentation/hwmon/mp9941.rst 17500 17500 F: drivers/hwmon/pmbus/mp9941.c 17501 17501 17502 + MPS MP9945 DRIVER 17503 + M: Cosmo Chou <chou.cosmo@gmail.com> 17504 + L: linux-hwmon@vger.kernel.org 17505 + S: Maintained 17506 + F: Documentation/hwmon/mp9945.rst 17507 + F: drivers/hwmon/pmbus/mp9945.c 17508 + 17502 17509 MR800 AVERMEDIA USB FM RADIO DRIVER 17503 17510 M: Alexey Klimov <alexey.klimov@linaro.org> 17504 17511 L: linux-media@vger.kernel.org
+9
drivers/hwmon/pmbus/Kconfig
··· 490 490 This driver can also be built as a module. If so, the module will 491 491 be called mp9941. 492 492 493 + config SENSORS_MP9945 494 + tristate "MPS MP9945" 495 + help 496 + If you say yes here you get hardware monitoring support for MPS 497 + MP9945. 498 + 499 + This driver can also be built as a module. If so, the module will 500 + be called mp9945. 501 + 493 502 config SENSORS_MPQ7932_REGULATOR 494 503 bool "Regulator support for MPQ7932" 495 504 depends on SENSORS_MPQ7932 && REGULATOR
+1
drivers/hwmon/pmbus/Makefile
··· 49 49 obj-$(CONFIG_SENSORS_MP5920) += mp5920.o 50 50 obj-$(CONFIG_SENSORS_MP5990) += mp5990.o 51 51 obj-$(CONFIG_SENSORS_MP9941) += mp9941.o 52 + obj-$(CONFIG_SENSORS_MP9945) += mp9945.o 52 53 obj-$(CONFIG_SENSORS_MPQ7932) += mpq7932.o 53 54 obj-$(CONFIG_SENSORS_MPQ8785) += mpq8785.o 54 55 obj-$(CONFIG_SENSORS_PLI1209BC) += pli1209bc.o
+243
drivers/hwmon/pmbus/mp9945.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-or-later 2 + /* 3 + * Hardware monitoring driver for MPS Single-phase Digital VR Controllers(MP9945) 4 + */ 5 + 6 + #include <linux/bitfield.h> 7 + #include <linux/i2c.h> 8 + #include <linux/module.h> 9 + #include <linux/of_device.h> 10 + #include "pmbus.h" 11 + 12 + #define MFR_VR_MULTI_CONFIG_R1 0x08 13 + #define MFR_SVID_CFG_R1 0xBD 14 + 15 + /* VOUT_MODE register values */ 16 + #define VOUT_MODE_LINEAR16 0x17 17 + #define VOUT_MODE_VID 0x21 18 + #define VOUT_MODE_DIRECT 0x40 19 + 20 + #define MP9945_PAGE_NUM 1 21 + 22 + #define MP9945_RAIL1_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | \ 23 + PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | \ 24 + PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | \ 25 + PMBUS_HAVE_TEMP | \ 26 + PMBUS_HAVE_STATUS_VOUT | \ 27 + PMBUS_HAVE_STATUS_IOUT | \ 28 + PMBUS_HAVE_STATUS_TEMP | \ 29 + PMBUS_HAVE_STATUS_INPUT) 30 + 31 + enum mp9945_vout_mode { 32 + MP9945_VOUT_MODE_VID, 33 + MP9945_VOUT_MODE_DIRECT, 34 + MP9945_VOUT_MODE_LINEAR16, 35 + }; 36 + 37 + struct mp9945_data { 38 + struct pmbus_driver_info info; 39 + enum mp9945_vout_mode vout_mode; 40 + int vid_resolution; 41 + int vid_offset; 42 + }; 43 + 44 + #define to_mp9945_data(x) container_of(x, struct mp9945_data, info) 45 + 46 + static int mp9945_read_vout(struct i2c_client *client, struct mp9945_data *data) 47 + { 48 + int ret; 49 + 50 + ret = i2c_smbus_read_word_data(client, PMBUS_READ_VOUT); 51 + if (ret < 0) 52 + return ret; 53 + 54 + ret &= GENMASK(11, 0); 55 + 56 + switch (data->vout_mode) { 57 + case MP9945_VOUT_MODE_VID: 58 + if (ret > 0) 59 + ret = (ret + data->vid_offset) * data->vid_resolution; 60 + break; 61 + case MP9945_VOUT_MODE_DIRECT: 62 + break; 63 + case MP9945_VOUT_MODE_LINEAR16: 64 + /* LSB: 1000 * 2^-9 (mV) */ 65 + ret = DIV_ROUND_CLOSEST(ret * 125, 64); 66 + break; 67 + default: 68 + return -ENODEV; 69 + } 70 + 71 + return ret; 72 + } 73 + 74 + static int mp9945_read_byte_data(struct i2c_client *client, int page, int reg) 75 + { 76 + int ret; 77 + 78 + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); 79 + if (ret < 0) 80 + return ret; 81 + 82 + switch (reg) { 83 + case PMBUS_VOUT_MODE: 84 + /* 85 + * Override VOUT_MODE to DIRECT as the driver handles custom 86 + * VOUT format conversions internally. 87 + */ 88 + return PB_VOUT_MODE_DIRECT; 89 + default: 90 + return -ENODATA; 91 + } 92 + } 93 + 94 + static int mp9945_read_word_data(struct i2c_client *client, int page, int phase, 95 + int reg) 96 + { 97 + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); 98 + struct mp9945_data *data = to_mp9945_data(info); 99 + int ret; 100 + 101 + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); 102 + if (ret < 0) 103 + return ret; 104 + 105 + switch (reg) { 106 + case PMBUS_READ_VOUT: 107 + ret = mp9945_read_vout(client, data); 108 + break; 109 + case PMBUS_VOUT_OV_FAULT_LIMIT: 110 + case PMBUS_VOUT_UV_FAULT_LIMIT: 111 + ret = i2c_smbus_read_word_data(client, reg); 112 + if (ret < 0) 113 + return ret; 114 + 115 + /* LSB: 1.95 (mV) */ 116 + ret = DIV_ROUND_CLOSEST((ret & GENMASK(11, 0)) * 39, 20); 117 + break; 118 + case PMBUS_VOUT_UV_WARN_LIMIT: 119 + ret = i2c_smbus_read_word_data(client, reg); 120 + if (ret < 0) 121 + return ret; 122 + 123 + ret &= GENMASK(9, 0); 124 + if (ret > 0) 125 + ret = (ret + data->vid_offset) * data->vid_resolution; 126 + break; 127 + default: 128 + ret = -ENODATA; 129 + break; 130 + } 131 + 132 + return ret; 133 + } 134 + 135 + static int mp9945_identify(struct i2c_client *client, 136 + struct pmbus_driver_info *info) 137 + { 138 + struct mp9945_data *data = to_mp9945_data(info); 139 + int ret; 140 + 141 + ret = i2c_smbus_read_byte_data(client, PMBUS_VOUT_MODE); 142 + if (ret < 0) 143 + return ret; 144 + 145 + switch (ret) { 146 + case VOUT_MODE_LINEAR16: 147 + data->vout_mode = MP9945_VOUT_MODE_LINEAR16; 148 + break; 149 + case VOUT_MODE_VID: 150 + data->vout_mode = MP9945_VOUT_MODE_VID; 151 + break; 152 + case VOUT_MODE_DIRECT: 153 + data->vout_mode = MP9945_VOUT_MODE_DIRECT; 154 + break; 155 + default: 156 + return -ENODEV; 157 + } 158 + 159 + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 3); 160 + if (ret < 0) 161 + return ret; 162 + 163 + ret = i2c_smbus_read_word_data(client, MFR_VR_MULTI_CONFIG_R1); 164 + if (ret < 0) 165 + return ret; 166 + 167 + data->vid_resolution = (FIELD_GET(BIT(2), ret)) ? 5 : 10; 168 + 169 + ret = i2c_smbus_read_word_data(client, MFR_SVID_CFG_R1); 170 + if (ret < 0) 171 + return ret; 172 + 173 + data->vid_offset = (FIELD_GET(BIT(15), ret)) ? 19 : 49; 174 + 175 + return i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); 176 + } 177 + 178 + static struct pmbus_driver_info mp9945_info = { 179 + .pages = MP9945_PAGE_NUM, 180 + .format[PSC_VOLTAGE_IN] = linear, 181 + .format[PSC_VOLTAGE_OUT] = direct, 182 + .format[PSC_CURRENT_IN] = linear, 183 + .format[PSC_CURRENT_OUT] = linear, 184 + .format[PSC_POWER] = linear, 185 + .format[PSC_TEMPERATURE] = linear, 186 + .m[PSC_VOLTAGE_OUT] = 1, 187 + .R[PSC_VOLTAGE_OUT] = 3, 188 + .b[PSC_VOLTAGE_OUT] = 0, 189 + .func[0] = MP9945_RAIL1_FUNC, 190 + .read_word_data = mp9945_read_word_data, 191 + .read_byte_data = mp9945_read_byte_data, 192 + .identify = mp9945_identify, 193 + }; 194 + 195 + static int mp9945_probe(struct i2c_client *client) 196 + { 197 + struct mp9945_data *data; 198 + int ret; 199 + 200 + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); 201 + if (!data) 202 + return -ENOMEM; 203 + 204 + memcpy(&data->info, &mp9945_info, sizeof(mp9945_info)); 205 + 206 + /* 207 + * Set page 0 before probe. The core reads paged registers which are 208 + * only on page 0 for this device. 209 + */ 210 + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); 211 + if (ret < 0) 212 + return ret; 213 + 214 + return pmbus_do_probe(client, &data->info); 215 + } 216 + 217 + static const struct i2c_device_id mp9945_id[] = { 218 + {"mp9945"}, 219 + {} 220 + }; 221 + MODULE_DEVICE_TABLE(i2c, mp9945_id); 222 + 223 + static const struct of_device_id __maybe_unused mp9945_of_match[] = { 224 + {.compatible = "mps,mp9945"}, 225 + {} 226 + }; 227 + MODULE_DEVICE_TABLE(of, mp9945_of_match); 228 + 229 + static struct i2c_driver mp9945_driver = { 230 + .driver = { 231 + .name = "mp9945", 232 + .of_match_table = of_match_ptr(mp9945_of_match), 233 + }, 234 + .probe = mp9945_probe, 235 + .id_table = mp9945_id, 236 + }; 237 + 238 + module_i2c_driver(mp9945_driver); 239 + 240 + MODULE_AUTHOR("Cosmo Chou <chou.cosmo@gmail.com>"); 241 + MODULE_DESCRIPTION("PMBus driver for MPS MP9945"); 242 + MODULE_LICENSE("GPL"); 243 + MODULE_IMPORT_NS("PMBUS");