1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * Hardware monitoring driver for FSP 3Y-Power PSUs
5 * Copyright (c) 2021 Václav Kubernát, CESNET
7 * This driver is mostly reverse engineered with the help of a tool called pmbus_peek written by
8 * David Brownell (and later adopted by Jan Kundrát). The device has some sort of a timing issue
9 * when switching pages, details are explained in the code. The driver support is limited. It
10 * exposes only the values, that have been tested to work correctly. Unsupported values either
11 * aren't supported by the devices or their encondings are unknown.
14 #include <linux/delay.h>
15 #include <linux/i2c.h>
16 #include <linux/kernel.h>
17 #include <linux/module.h>
20 #define YM2151_PAGE_12V_LOG 0x00
21 #define YM2151_PAGE_12V_REAL 0x00
22 #define YM2151_PAGE_5VSB_LOG 0x01
23 #define YM2151_PAGE_5VSB_REAL 0x20
24 #define YH5151E_PAGE_12V_LOG 0x00
25 #define YH5151E_PAGE_12V_REAL 0x00
26 #define YH5151E_PAGE_5V_LOG 0x01
27 #define YH5151E_PAGE_5V_REAL 0x10
28 #define YH5151E_PAGE_3V3_LOG 0x02
29 #define YH5151E_PAGE_3V3_REAL 0x11
37 struct pmbus_driver_info info
;
44 #define to_fsp3y_data(x) container_of(x, struct fsp3y_data, info)
46 static int page_log_to_page_real(int page_log
, enum chips chip
)
51 case YM2151_PAGE_12V_LOG
:
52 return YM2151_PAGE_12V_REAL
;
53 case YM2151_PAGE_5VSB_LOG
:
54 return YM2151_PAGE_5VSB_REAL
;
59 case YH5151E_PAGE_12V_LOG
:
60 return YH5151E_PAGE_12V_REAL
;
61 case YH5151E_PAGE_5V_LOG
:
62 return YH5151E_PAGE_5V_REAL
;
63 case YH5151E_PAGE_3V3_LOG
:
64 return YH5151E_PAGE_3V3_REAL
;
72 static int set_page(struct i2c_client
*client
, int page_log
)
74 const struct pmbus_driver_info
*info
= pmbus_get_driver_info(client
);
75 struct fsp3y_data
*data
= to_fsp3y_data(info
);
82 page_real
= page_log_to_page_real(page_log
, data
->chip
);
86 if (data
->page
!= page_real
) {
87 rv
= i2c_smbus_write_byte_data(client
, PMBUS_PAGE
, page_real
);
91 data
->page
= page_real
;
94 * Testing showed that the device has a timing issue. After
95 * setting a page, it takes a while, before the device actually
96 * gives the correct values from the correct page. 20 ms was
97 * tested to be enough to not give wrong values (15 ms wasn't
100 usleep_range(20000, 30000);
106 static int fsp3y_read_byte_data(struct i2c_client
*client
, int page
, int reg
)
108 const struct pmbus_driver_info
*info
= pmbus_get_driver_info(client
);
109 struct fsp3y_data
*data
= to_fsp3y_data(info
);
113 * Inject an exponent for non-compliant YH5151-E.
115 if (data
->vout_linear_11
&& reg
== PMBUS_VOUT_MODE
)
118 rv
= set_page(client
, page
);
122 return i2c_smbus_read_byte_data(client
, reg
);
125 static int fsp3y_read_word_data(struct i2c_client
*client
, int page
, int phase
, int reg
)
127 const struct pmbus_driver_info
*info
= pmbus_get_driver_info(client
);
128 struct fsp3y_data
*data
= to_fsp3y_data(info
);
132 * This masks commands which weren't tested to work correctly. Some of
133 * the masked commands return 0xFFFF. These would probably get tagged as
134 * invalid by pmbus_core. Other ones do return values which might be
135 * useful (that is, they are not 0xFFFF), but their encoding is unknown,
136 * and so they are unsupported.
139 case PMBUS_READ_FAN_SPEED_1
:
141 case PMBUS_READ_IOUT
:
143 case PMBUS_READ_POUT
:
144 case PMBUS_READ_TEMPERATURE_1
:
145 case PMBUS_READ_TEMPERATURE_2
:
146 case PMBUS_READ_TEMPERATURE_3
:
148 case PMBUS_READ_VOUT
:
149 case PMBUS_STATUS_WORD
:
155 rv
= set_page(client
, page
);
159 rv
= i2c_smbus_read_word_data(client
, reg
);
164 * Handle YH-5151E non-compliant linear11 vout voltage.
166 if (data
->vout_linear_11
&& reg
== PMBUS_READ_VOUT
)
167 rv
= sign_extend32(rv
, 10) & 0xffff;
172 static struct pmbus_driver_info fsp3y_info
[] = {
175 .func
[YM2151_PAGE_12V_LOG
] =
176 PMBUS_HAVE_VOUT
| PMBUS_HAVE_IOUT
|
177 PMBUS_HAVE_PIN
| PMBUS_HAVE_POUT
|
178 PMBUS_HAVE_TEMP
| PMBUS_HAVE_TEMP2
|
179 PMBUS_HAVE_VIN
| PMBUS_HAVE_IIN
|
181 .func
[YM2151_PAGE_5VSB_LOG
] =
182 PMBUS_HAVE_VOUT
| PMBUS_HAVE_IOUT
,
183 .read_word_data
= fsp3y_read_word_data
,
184 .read_byte_data
= fsp3y_read_byte_data
,
188 .func
[YH5151E_PAGE_12V_LOG
] =
189 PMBUS_HAVE_VOUT
| PMBUS_HAVE_IOUT
|
191 PMBUS_HAVE_TEMP
| PMBUS_HAVE_TEMP2
| PMBUS_HAVE_TEMP3
,
192 .func
[YH5151E_PAGE_5V_LOG
] =
193 PMBUS_HAVE_VOUT
| PMBUS_HAVE_IOUT
|
195 .func
[YH5151E_PAGE_3V3_LOG
] =
196 PMBUS_HAVE_VOUT
| PMBUS_HAVE_IOUT
|
198 .read_word_data
= fsp3y_read_word_data
,
199 .read_byte_data
= fsp3y_read_byte_data
,
203 static int fsp3y_detect(struct i2c_client
*client
)
206 u8 buf
[I2C_SMBUS_BLOCK_MAX
+ 1];
208 rv
= i2c_smbus_read_block_data(client
, PMBUS_MFR_MODEL
, buf
);
215 if (!strcmp(buf
, "YM-2151E"))
217 else if (!strcmp(buf
, "YH-5151E"))
221 dev_err(&client
->dev
, "Unsupported model %.*s\n", rv
, buf
);
225 static const struct i2c_device_id fsp3y_id
[] = {
226 {"ym2151e", ym2151e
},
227 {"yh5151e", yh5151e
},
231 static int fsp3y_probe(struct i2c_client
*client
)
233 struct fsp3y_data
*data
;
234 const struct i2c_device_id
*id
;
237 data
= devm_kzalloc(&client
->dev
, sizeof(struct fsp3y_data
), GFP_KERNEL
);
241 data
->chip
= fsp3y_detect(client
);
245 id
= i2c_match_id(fsp3y_id
, client
);
246 if (data
->chip
!= id
->driver_data
)
247 dev_warn(&client
->dev
, "Device mismatch: Configured %s (%d), detected %d\n",
248 id
->name
, (int)id
->driver_data
, data
->chip
);
250 rv
= i2c_smbus_read_byte_data(client
, PMBUS_PAGE
);
255 data
->info
= fsp3y_info
[data
->chip
];
258 * YH-5151E sometimes reports vout in linear11 and sometimes in
259 * linear16. This depends on the exact individual piece of hardware. One
260 * YH-5151E can use linear16 and another might use linear11 instead.
262 * The format can be recognized by reading VOUT_MODE - if it doesn't
263 * report a valid exponent, then vout uses linear11. Otherwise, the
264 * device is compliant and uses linear16.
266 data
->vout_linear_11
= false;
267 if (data
->chip
== yh5151e
) {
268 rv
= i2c_smbus_read_byte_data(client
, PMBUS_VOUT_MODE
);
273 data
->vout_linear_11
= true;
276 return pmbus_do_probe(client
, &data
->info
);
279 MODULE_DEVICE_TABLE(i2c
, fsp3y_id
);
281 static struct i2c_driver fsp3y_driver
= {
285 .probe
= fsp3y_probe
,
289 module_i2c_driver(fsp3y_driver
);
291 MODULE_AUTHOR("Václav Kubernát");
292 MODULE_DESCRIPTION("PMBus driver for FSP/3Y-Power power supplies");
293 MODULE_LICENSE("GPL");
294 MODULE_IMPORT_NS("PMBUS");