xref: /linux/drivers/hwmon/pmbus/max20830.c (revision 3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d)
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  */
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 
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