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
31enum mp9945_vout_mode {
32 MP9945_VOUT_MODE_VID,
33 MP9945_VOUT_MODE_DIRECT,
34 MP9945_VOUT_MODE_LINEAR16,
35};
36
37struct 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
46static 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, command: 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
74static 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, command: PMBUS_PAGE, value: 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
94static 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, command: PMBUS_PAGE, value: 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, command: 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, command: 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
135static 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, command: 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, command: PMBUS_PAGE, value: 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, command: PMBUS_PAGE, value: 0);
176}
177
178static 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
195static int mp9945_probe(struct i2c_client *client)
196{
197 struct mp9945_data *data;
198 int ret;
199
200 data = devm_kzalloc(dev: &client->dev, size: 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, command: PMBUS_PAGE, value: 0);
211 if (ret < 0)
212 return ret;
213
214 return pmbus_do_probe(client, info: &data->info);
215}
216
217static const struct i2c_device_id mp9945_id[] = {
218 {"mp9945"},
219 {}
220};
221MODULE_DEVICE_TABLE(i2c, mp9945_id);
222
223static const struct of_device_id __maybe_unused mp9945_of_match[] = {
224 {.compatible = "mps,mp9945"},
225 {}
226};
227MODULE_DEVICE_TABLE(of, mp9945_of_match);
228
229static 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
238module_i2c_driver(mp9945_driver);
239
240MODULE_AUTHOR("Cosmo Chou <chou.cosmo@gmail.com>");
241MODULE_DESCRIPTION("PMBus driver for MPS MP9945");
242MODULE_LICENSE("GPL");
243MODULE_IMPORT_NS("PMBUS");
244

source code of linux/drivers/hwmon/pmbus/mp9945.c