1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Hardware monitoring driver for Analog Devices MAX20830
4 *
5 * Copyright (C) 2026 Analog Devices, Inc.
6 */
7
8 #include <linux/errno.h>
9 #include <linux/i2c.h>
10 #include <linux/math64.h>
11 #include <linux/module.h>
12 #include <linux/string.h>
13 #include "pmbus.h"
14
15 #define MAX20830_IC_DEVICE_ID_LENGTH 9
16
17 struct max20830_data {
18 struct pmbus_driver_info info;
19 u32 vout_rfb1;
20 u32 vout_rfb2;
21 };
22
23 static const char * const supported_chip_ids[] = {
24 "MAX20830",
25 "MAX20830C",
26 "MAX20840C",
27 };
28
29 /*
30 * MAX20830 only supports READ_VOUT for VOUT monitoring.
31 *
32 * Limit registers (VOUT_OV_WARN_LIMIT, VOUT_OV_FAULT_LIMIT, etc.) are not
33 * supported by this driver and return -ENODATA. This means sysfs attributes
34 * like in1_max, in1_crit, etc. will not be available. Only in1_input (the
35 * scaled output voltage) is supported.
36 *
37 * MAX20830 uses an external resistor divider for voltage sensing:
38 * - VOUT_COMMAND and VOUT_MAX set the reference voltage at the feedback pin
39 * - READ_VOUT reports the feedback voltage, which needs to be scaled for actual
40 * output voltage
41 *
42 * Scaling formula: vout_actual = vout_fb × (1 + RFB1 / RFB2)
43 *
44 * If regulator support is added in the future, some adjustments are needed to
45 * ensure correct feedback voltages are set.
46 */
max20830_read_word_data(struct i2c_client * client,int page,int phase,int reg)47 static int max20830_read_word_data(struct i2c_client *client, int page,
48 int phase, int reg)
49 {
50 const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
51 const struct max20830_data *data = container_of(info, struct max20830_data, info);
52 int ret;
53 u64 temp;
54
55 switch (reg) {
56 case PMBUS_READ_VOUT:
57 ret = pmbus_read_word_data(client, page, phase, reg);
58 if (ret < 0)
59 return ret;
60
61 /* Apply voltage divider scaling if resistors are non-zero */
62 if (data->vout_rfb1 && data->vout_rfb2) {
63 temp = (u64)data->vout_rfb1 + (u64)data->vout_rfb2;
64 temp = DIV_ROUND_CLOSEST_ULL((u64)ret * temp, data->vout_rfb2);
65 ret = clamp_val(temp, 0, 0xFFFF);
66 }
67 return ret;
68 default:
69 return -ENODATA;
70 }
71 }
72
73 static struct pmbus_driver_info max20830_info = {
74 .pages = 1,
75 .format[PSC_VOLTAGE_IN] = linear,
76 .format[PSC_VOLTAGE_OUT] = linear,
77 .format[PSC_CURRENT_OUT] = linear,
78 .format[PSC_TEMPERATURE] = linear,
79 .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
80 PMBUS_HAVE_TEMP |
81 PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT |
82 PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP,
83 .read_word_data = max20830_read_word_data,
84 .have_pmbus_revision = true,
85 .pmbus_revision = PMBUS_REV_13,
86 };
87
max20830_probe(struct i2c_client * client)88 static int max20830_probe(struct i2c_client *client)
89 {
90 u8 buf[I2C_SMBUS_BLOCK_MAX + 1] = {};
91 struct max20830_data *data;
92 int i, ret;
93
94 data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
95 if (!data)
96 return -ENOMEM;
97
98 data->info = max20830_info;
99
100 /* Read optional voltage divider resistor values */
101 device_property_read_u32(&client->dev, "adi,vout-rfb1-ohms", &data->vout_rfb1);
102 device_property_read_u32(&client->dev, "adi,vout-rfb2-ohms", &data->vout_rfb2);
103
104 ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_IC_DEVICE_ID, buf);
105 if (ret < 0)
106 return dev_err_probe(&client->dev, ret,
107 "Failed to read IC_DEVICE_ID\n");
108
109 /* Verify we read the expected number of bytes */
110 if (ret < MAX20830_IC_DEVICE_ID_LENGTH)
111 return dev_err_probe(&client->dev, -ENODEV,
112 "IC_DEVICE_ID too short: expected %d bytes, got %d\n",
113 MAX20830_IC_DEVICE_ID_LENGTH, ret);
114
115 /* Null-terminate the string */
116 buf[ret] = '\0';
117
118 /* Verify the device ID matches what we expect */
119 for (i = 0; i < ARRAY_SIZE(supported_chip_ids); i++) {
120 if (!strcmp(buf, supported_chip_ids[i]))
121 break;
122 }
123
124 /* No match found - unsupported device */
125 if (i == ARRAY_SIZE(supported_chip_ids))
126 return dev_err_probe(&client->dev, -ENODEV,
127 "Unsupported device: '%*pE'\n", ret, buf);
128
129 return pmbus_do_probe(client, &data->info);
130 }
131
132 static const struct i2c_device_id max20830_id[] = {
133 {"max20830"},
134 { }
135 };
136 MODULE_DEVICE_TABLE(i2c, max20830_id);
137
138 static const struct of_device_id max20830_of_match[] = {
139 { .compatible = "adi,max20830" },
140 { }
141 };
142 MODULE_DEVICE_TABLE(of, max20830_of_match);
143
144 static struct i2c_driver max20830_driver = {
145 .driver = {
146 .name = "max20830",
147 .of_match_table = max20830_of_match,
148 },
149 .probe = max20830_probe,
150 .id_table = max20830_id,
151 };
152
153 module_i2c_driver(max20830_driver);
154
155 MODULE_AUTHOR("Alexis Czezar Torreno <alexisczezar.torreno@analog.com>");
156 MODULE_DESCRIPTION("PMBus driver for Analog Devices MAX20830");
157 MODULE_LICENSE("GPL");
158 MODULE_IMPORT_NS("PMBUS");
159