xref: /openbmc/qemu/hw/i2c/pmbus_device.c (revision 30b6852c)
1 /*
2  * PMBus wrapper over SMBus
3  *
4  * Copyright 2021 Google LLC
5  *
6  * SPDX-License-Identifier: GPL-2.0-or-later
7  */
8 
9 #include "qemu/osdep.h"
10 #include <math.h>
11 #include <string.h>
12 #include "hw/i2c/pmbus_device.h"
13 #include "migration/vmstate.h"
14 #include "qemu/module.h"
15 #include "qemu/log.h"
16 
17 uint16_t pmbus_data2direct_mode(PMBusCoefficients c, uint32_t value)
18 {
19     /* R is usually negative to fit large readings into 16 bits */
20     uint16_t y = (c.m * value + c.b) * pow(10, c.R);
21     return y;
22 }
23 
24 uint32_t pmbus_direct_mode2data(PMBusCoefficients c, uint16_t value)
25 {
26     /* X = (Y * 10^-R - b) / m */
27     uint32_t x = (value / pow(10, c.R) - c.b) / c.m;
28     return x;
29 }
30 
31 void pmbus_send(PMBusDevice *pmdev, const uint8_t *data, uint16_t len)
32 {
33     if (pmdev->out_buf_len + len > SMBUS_DATA_MAX_LEN) {
34         qemu_log_mask(LOG_GUEST_ERROR,
35                       "PMBus device tried to send too much data");
36         len = 0;
37     }
38 
39     for (int i = len - 1; i >= 0; i--) {
40         pmdev->out_buf[i + pmdev->out_buf_len] = data[len - i - 1];
41     }
42     pmdev->out_buf_len += len;
43 }
44 
45 /* Internal only, convert unsigned ints to the little endian bus */
46 static void pmbus_send_uint(PMBusDevice *pmdev, uint64_t data, uint8_t size)
47 {
48     uint8_t bytes[8];
49     g_assert(size <= 8);
50 
51     for (int i = 0; i < size; i++) {
52         bytes[i] = data & 0xFF;
53         data = data >> 8;
54     }
55     pmbus_send(pmdev, bytes, size);
56 }
57 
58 void pmbus_send8(PMBusDevice *pmdev, uint8_t data)
59 {
60     pmbus_send_uint(pmdev, data, 1);
61 }
62 
63 void pmbus_send16(PMBusDevice *pmdev, uint16_t data)
64 {
65     pmbus_send_uint(pmdev, data, 2);
66 }
67 
68 void pmbus_send32(PMBusDevice *pmdev, uint32_t data)
69 {
70     pmbus_send_uint(pmdev, data, 4);
71 }
72 
73 void pmbus_send64(PMBusDevice *pmdev, uint64_t data)
74 {
75     pmbus_send_uint(pmdev, data, 8);
76 }
77 
78 void pmbus_send_string(PMBusDevice *pmdev, const char *data)
79 {
80     size_t len = strlen(data);
81     g_assert(len > 0);
82     g_assert(len + pmdev->out_buf_len < SMBUS_DATA_MAX_LEN);
83     pmdev->out_buf[len + pmdev->out_buf_len] = len;
84 
85     for (int i = len - 1; i >= 0; i--) {
86         pmdev->out_buf[i + pmdev->out_buf_len] = data[len - 1 - i];
87     }
88     pmdev->out_buf_len += len + 1;
89 }
90 
91 
92 static uint64_t pmbus_receive_uint(const uint8_t *buf, uint8_t len)
93 {
94     uint64_t ret = 0;
95 
96     /* Exclude command code from return value */
97     buf++;
98     len--;
99 
100     for (int i = len - 1; i >= 0; i--) {
101         ret = ret << 8 | buf[i];
102     }
103     return ret;
104 }
105 
106 uint8_t pmbus_receive8(PMBusDevice *pmdev)
107 {
108     if (pmdev->in_buf_len - 1 != 1) {
109         qemu_log_mask(LOG_GUEST_ERROR,
110                       "%s: length mismatch. Expected 1 byte, got %d bytes\n",
111                       __func__, pmdev->in_buf_len - 1);
112     }
113     return pmbus_receive_uint(pmdev->in_buf, pmdev->in_buf_len);
114 }
115 
116 uint16_t pmbus_receive16(PMBusDevice *pmdev)
117 {
118     if (pmdev->in_buf_len - 1 != 2) {
119         qemu_log_mask(LOG_GUEST_ERROR,
120                       "%s: length mismatch. Expected 2 bytes, got %d bytes\n",
121                       __func__, pmdev->in_buf_len - 1);
122     }
123     return pmbus_receive_uint(pmdev->in_buf, pmdev->in_buf_len);
124 }
125 
126 uint32_t pmbus_receive32(PMBusDevice *pmdev)
127 {
128     if (pmdev->in_buf_len - 1 != 4) {
129         qemu_log_mask(LOG_GUEST_ERROR,
130                       "%s: length mismatch. Expected 4 bytes, got %d bytes\n",
131                       __func__, pmdev->in_buf_len - 1);
132     }
133     return pmbus_receive_uint(pmdev->in_buf, pmdev->in_buf_len);
134 }
135 
136 uint64_t pmbus_receive64(PMBusDevice *pmdev)
137 {
138     if (pmdev->in_buf_len - 1 != 8) {
139         qemu_log_mask(LOG_GUEST_ERROR,
140                       "%s: length mismatch. Expected 8 bytes, got %d bytes\n",
141                       __func__, pmdev->in_buf_len - 1);
142     }
143     return pmbus_receive_uint(pmdev->in_buf, pmdev->in_buf_len);
144 }
145 
146 static uint8_t pmbus_out_buf_pop(PMBusDevice *pmdev)
147 {
148     if (pmdev->out_buf_len == 0) {
149         qemu_log_mask(LOG_GUEST_ERROR,
150                       "%s: tried to read from empty buffer",
151                       __func__);
152         return 0xFF;
153     }
154     uint8_t data = pmdev->out_buf[pmdev->out_buf_len - 1];
155     pmdev->out_buf_len--;
156     return data;
157 }
158 
159 static void pmbus_quick_cmd(SMBusDevice *smd, uint8_t read)
160 {
161     PMBusDevice *pmdev = PMBUS_DEVICE(smd);
162     PMBusDeviceClass *pmdc = PMBUS_DEVICE_GET_CLASS(pmdev);
163 
164     if (pmdc->quick_cmd) {
165         pmdc->quick_cmd(pmdev, read);
166     }
167 }
168 
169 static void pmbus_pages_alloc(PMBusDevice *pmdev)
170 {
171     /* some PMBus devices don't use the PAGE command, so they get 1 page */
172     PMBusDeviceClass *k = PMBUS_DEVICE_GET_CLASS(pmdev);
173     if (k->device_num_pages == 0) {
174         k->device_num_pages = 1;
175     }
176     pmdev->num_pages = k->device_num_pages;
177     pmdev->pages = g_new0(PMBusPage, k->device_num_pages);
178 }
179 
180 void pmbus_check_limits(PMBusDevice *pmdev)
181 {
182     for (int i = 0; i < pmdev->num_pages; i++) {
183         if ((pmdev->pages[i].operation & PB_OP_ON) == 0) {
184             continue;   /* don't check powered off devices */
185         }
186 
187         if (pmdev->pages[i].read_vout > pmdev->pages[i].vout_ov_fault_limit) {
188             pmdev->pages[i].status_word |= PB_STATUS_VOUT;
189             pmdev->pages[i].status_vout |= PB_STATUS_VOUT_OV_FAULT;
190         }
191 
192         if (pmdev->pages[i].read_vout > pmdev->pages[i].vout_ov_warn_limit) {
193             pmdev->pages[i].status_word |= PB_STATUS_VOUT;
194             pmdev->pages[i].status_vout |= PB_STATUS_VOUT_OV_WARN;
195         }
196 
197         if (pmdev->pages[i].read_vout < pmdev->pages[i].vout_uv_warn_limit) {
198             pmdev->pages[i].status_word |= PB_STATUS_VOUT;
199             pmdev->pages[i].status_vout |= PB_STATUS_VOUT_UV_WARN;
200         }
201 
202         if (pmdev->pages[i].read_vout < pmdev->pages[i].vout_uv_fault_limit) {
203             pmdev->pages[i].status_word |= PB_STATUS_VOUT;
204             pmdev->pages[i].status_vout |= PB_STATUS_VOUT_UV_FAULT;
205         }
206 
207         if (pmdev->pages[i].read_vin > pmdev->pages[i].vin_ov_warn_limit) {
208             pmdev->pages[i].status_word |= PB_STATUS_INPUT;
209             pmdev->pages[i].status_input |= PB_STATUS_INPUT_VIN_OV_WARN;
210         }
211 
212         if (pmdev->pages[i].read_vin < pmdev->pages[i].vin_uv_warn_limit) {
213             pmdev->pages[i].status_word |= PB_STATUS_INPUT;
214             pmdev->pages[i].status_input |= PB_STATUS_INPUT_VIN_UV_WARN;
215         }
216 
217         if (pmdev->pages[i].read_iout > pmdev->pages[i].iout_oc_warn_limit) {
218             pmdev->pages[i].status_word |= PB_STATUS_IOUT_POUT;
219             pmdev->pages[i].status_iout |= PB_STATUS_IOUT_OC_WARN;
220         }
221 
222         if (pmdev->pages[i].read_iout > pmdev->pages[i].iout_oc_fault_limit) {
223             pmdev->pages[i].status_word |= PB_STATUS_IOUT_POUT;
224             pmdev->pages[i].status_iout |= PB_STATUS_IOUT_OC_FAULT;
225         }
226 
227         if (pmdev->pages[i].read_pin > pmdev->pages[i].pin_op_warn_limit) {
228             pmdev->pages[i].status_word |= PB_STATUS_INPUT;
229             pmdev->pages[i].status_input |= PB_STATUS_INPUT_PIN_OP_WARN;
230         }
231 
232         if (pmdev->pages[i].read_temperature_1
233                 > pmdev->pages[i].ot_fault_limit) {
234             pmdev->pages[i].status_word |= PB_STATUS_TEMPERATURE;
235             pmdev->pages[i].status_temperature |= PB_STATUS_OT_FAULT;
236         }
237 
238         if (pmdev->pages[i].read_temperature_1
239                 > pmdev->pages[i].ot_warn_limit) {
240             pmdev->pages[i].status_word |= PB_STATUS_TEMPERATURE;
241             pmdev->pages[i].status_temperature |= PB_STATUS_OT_WARN;
242         }
243     }
244 }
245 
246 static uint8_t pmbus_receive_byte(SMBusDevice *smd)
247 {
248     PMBusDevice *pmdev = PMBUS_DEVICE(smd);
249     PMBusDeviceClass *pmdc = PMBUS_DEVICE_GET_CLASS(pmdev);
250     uint8_t ret = 0xFF;
251     uint8_t index = pmdev->page;
252 
253     if (pmdev->out_buf_len != 0) {
254         ret = pmbus_out_buf_pop(pmdev);
255         return ret;
256     }
257 
258     switch (pmdev->code) {
259     case PMBUS_PAGE:
260         pmbus_send8(pmdev, pmdev->page);
261         break;
262 
263     case PMBUS_OPERATION:                 /* R/W byte */
264         pmbus_send8(pmdev, pmdev->pages[index].operation);
265         break;
266 
267     case PMBUS_ON_OFF_CONFIG:             /* R/W byte */
268         pmbus_send8(pmdev, pmdev->pages[index].on_off_config);
269         break;
270 
271     case PMBUS_PHASE:                     /* R/W byte */
272         pmbus_send8(pmdev, pmdev->pages[index].phase);
273         break;
274 
275     case PMBUS_WRITE_PROTECT:             /* R/W byte */
276         pmbus_send8(pmdev, pmdev->pages[index].write_protect);
277         break;
278 
279     case PMBUS_CAPABILITY:
280         pmbus_send8(pmdev, pmdev->capability);
281         break;
282 
283     case PMBUS_VOUT_MODE:                 /* R/W byte */
284         if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MODE) {
285             pmbus_send8(pmdev, pmdev->pages[index].vout_mode);
286         } else {
287             goto passthough;
288         }
289         break;
290 
291     case PMBUS_VOUT_COMMAND:              /* R/W word */
292         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
293             pmbus_send16(pmdev, pmdev->pages[index].vout_command);
294         } else {
295             goto passthough;
296         }
297         break;
298 
299     case PMBUS_VOUT_TRIM:                 /* R/W word */
300         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
301             pmbus_send16(pmdev, pmdev->pages[index].vout_trim);
302         } else {
303             goto passthough;
304         }
305         break;
306 
307     case PMBUS_VOUT_CAL_OFFSET:           /* R/W word */
308         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
309             pmbus_send16(pmdev, pmdev->pages[index].vout_cal_offset);
310         } else {
311             goto passthough;
312         }
313         break;
314 
315     case PMBUS_VOUT_MAX:                  /* R/W word */
316         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
317             pmbus_send16(pmdev, pmdev->pages[index].vout_max);
318         } else {
319             goto passthough;
320         }
321         break;
322 
323     case PMBUS_VOUT_MARGIN_HIGH:          /* R/W word */
324         if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MARGIN) {
325             pmbus_send16(pmdev, pmdev->pages[index].vout_margin_high);
326         } else {
327             goto passthough;
328         }
329         break;
330 
331     case PMBUS_VOUT_MARGIN_LOW:           /* R/W word */
332         if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MARGIN) {
333             pmbus_send16(pmdev, pmdev->pages[index].vout_margin_low);
334         } else {
335             goto passthough;
336         }
337         break;
338 
339     case PMBUS_VOUT_TRANSITION_RATE:      /* R/W word */
340         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
341             pmbus_send16(pmdev, pmdev->pages[index].vout_transition_rate);
342         } else {
343             goto passthough;
344         }
345         break;
346 
347     case PMBUS_VOUT_DROOP:                /* R/W word */
348         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
349             pmbus_send16(pmdev, pmdev->pages[index].vout_droop);
350         } else {
351             goto passthough;
352         }
353         break;
354 
355     case PMBUS_VOUT_SCALE_LOOP:           /* R/W word */
356         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
357             pmbus_send16(pmdev, pmdev->pages[index].vout_scale_loop);
358         } else {
359             goto passthough;
360         }
361         break;
362 
363     case PMBUS_VOUT_SCALE_MONITOR:        /* R/W word */
364         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
365             pmbus_send16(pmdev, pmdev->pages[index].vout_scale_monitor);
366         } else {
367             goto passthough;
368         }
369         break;
370 
371     /* TODO: implement coefficients support */
372 
373     case PMBUS_POUT_MAX:                  /* R/W word */
374         if (pmdev->pages[index].page_flags & PB_HAS_POUT) {
375             pmbus_send16(pmdev, pmdev->pages[index].pout_max);
376         } else {
377             goto passthough;
378         }
379         break;
380 
381     case PMBUS_VIN_ON:                    /* R/W word */
382         if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
383             pmbus_send16(pmdev, pmdev->pages[index].vin_on);
384         } else {
385             goto passthough;
386         }
387         break;
388 
389     case PMBUS_VIN_OFF:                   /* R/W word */
390         if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
391             pmbus_send16(pmdev, pmdev->pages[index].vin_off);
392         } else {
393             goto passthough;
394         }
395         break;
396 
397     case PMBUS_IOUT_CAL_GAIN:             /* R/W word */
398         if (pmdev->pages[index].page_flags & PB_HAS_IOUT_GAIN) {
399             pmbus_send16(pmdev, pmdev->pages[index].iout_cal_gain);
400         } else {
401             goto passthough;
402         }
403         break;
404 
405     case PMBUS_VOUT_OV_FAULT_LIMIT:       /* R/W word */
406         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
407             pmbus_send16(pmdev, pmdev->pages[index].vout_ov_fault_limit);
408         } else {
409             goto passthough;
410         }
411         break;
412 
413     case PMBUS_VOUT_OV_FAULT_RESPONSE:    /* R/W byte */
414         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
415             pmbus_send8(pmdev, pmdev->pages[index].vout_ov_fault_response);
416         } else {
417             goto passthough;
418         }
419         break;
420 
421     case PMBUS_VOUT_OV_WARN_LIMIT:        /* R/W word */
422         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
423             pmbus_send16(pmdev, pmdev->pages[index].vout_ov_warn_limit);
424         } else {
425             goto passthough;
426         }
427         break;
428 
429     case PMBUS_VOUT_UV_WARN_LIMIT:        /* R/W word */
430         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
431             pmbus_send16(pmdev, pmdev->pages[index].vout_uv_warn_limit);
432         } else {
433             goto passthough;
434         }
435         break;
436 
437     case PMBUS_VOUT_UV_FAULT_LIMIT:       /* R/W word */
438         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
439             pmbus_send16(pmdev, pmdev->pages[index].vout_uv_fault_limit);
440         } else {
441             goto passthough;
442         }
443         break;
444 
445     case PMBUS_VOUT_UV_FAULT_RESPONSE:    /* R/W byte */
446         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
447             pmbus_send8(pmdev, pmdev->pages[index].vout_uv_fault_response);
448         } else {
449             goto passthough;
450         }
451         break;
452 
453     case PMBUS_IOUT_OC_FAULT_LIMIT:       /* R/W word */
454         if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
455             pmbus_send16(pmdev, pmdev->pages[index].iout_oc_fault_limit);
456         } else {
457             goto passthough;
458         }
459         break;
460 
461     case PMBUS_IOUT_OC_FAULT_RESPONSE:    /* R/W byte */
462         if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
463             pmbus_send8(pmdev, pmdev->pages[index].iout_oc_fault_response);
464         } else {
465             goto passthough;
466         }
467         break;
468 
469     case PMBUS_IOUT_OC_LV_FAULT_LIMIT:    /* R/W word */
470         if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
471             pmbus_send16(pmdev, pmdev->pages[index].iout_oc_lv_fault_limit);
472         } else {
473             goto passthough;
474         }
475         break;
476 
477     case PMBUS_IOUT_OC_LV_FAULT_RESPONSE: /* R/W byte */
478         if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
479             pmbus_send8(pmdev, pmdev->pages[index].iout_oc_lv_fault_response);
480         } else {
481             goto passthough;
482         }
483         break;
484 
485     case PMBUS_IOUT_OC_WARN_LIMIT:        /* R/W word */
486         if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
487             pmbus_send16(pmdev, pmdev->pages[index].iout_oc_warn_limit);
488         } else {
489             goto passthough;
490         }
491         break;
492 
493     case PMBUS_IOUT_UC_FAULT_LIMIT:       /* R/W word */
494         if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
495             pmbus_send16(pmdev, pmdev->pages[index].iout_uc_fault_limit);
496         } else {
497             goto passthough;
498         }
499         break;
500 
501     case PMBUS_IOUT_UC_FAULT_RESPONSE:    /* R/W byte */
502         if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
503             pmbus_send8(pmdev, pmdev->pages[index].iout_uc_fault_response);
504         } else {
505             goto passthough;
506         }
507         break;
508 
509     case PMBUS_OT_FAULT_LIMIT:            /* R/W word */
510         if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
511             pmbus_send16(pmdev, pmdev->pages[index].ot_fault_limit);
512         } else {
513             goto passthough;
514         }
515         break;
516 
517     case PMBUS_OT_FAULT_RESPONSE:         /* R/W byte */
518         if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
519             pmbus_send8(pmdev, pmdev->pages[index].ot_fault_response);
520         } else {
521             goto passthough;
522         }
523         break;
524 
525     case PMBUS_OT_WARN_LIMIT:             /* R/W word */
526         if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
527             pmbus_send16(pmdev, pmdev->pages[index].ot_warn_limit);
528         } else {
529             goto passthough;
530         }
531         break;
532 
533     case PMBUS_UT_WARN_LIMIT:             /* R/W word */
534         if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
535             pmbus_send16(pmdev, pmdev->pages[index].ut_warn_limit);
536         } else {
537             goto passthough;
538         }
539         break;
540 
541     case PMBUS_UT_FAULT_LIMIT:            /* R/W word */
542         if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
543             pmbus_send16(pmdev, pmdev->pages[index].ut_fault_limit);
544         } else {
545             goto passthough;
546         }
547         break;
548 
549     case PMBUS_UT_FAULT_RESPONSE:         /* R/W byte */
550         if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
551             pmbus_send8(pmdev, pmdev->pages[index].ut_fault_response);
552         } else {
553             goto passthough;
554         }
555         break;
556 
557     case PMBUS_VIN_OV_FAULT_LIMIT:        /* R/W word */
558         if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
559             pmbus_send16(pmdev, pmdev->pages[index].vin_ov_fault_limit);
560         } else {
561             goto passthough;
562         }
563         break;
564 
565     case PMBUS_VIN_OV_FAULT_RESPONSE:     /* R/W byte */
566         if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
567             pmbus_send8(pmdev, pmdev->pages[index].vin_ov_fault_response);
568         } else {
569             goto passthough;
570         }
571         break;
572 
573     case PMBUS_VIN_OV_WARN_LIMIT:         /* R/W word */
574         if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
575             pmbus_send16(pmdev, pmdev->pages[index].vin_ov_warn_limit);
576         } else {
577             goto passthough;
578         }
579         break;
580 
581     case PMBUS_VIN_UV_WARN_LIMIT:         /* R/W word */
582         if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
583             pmbus_send16(pmdev, pmdev->pages[index].vin_uv_warn_limit);
584         } else {
585             goto passthough;
586         }
587         break;
588 
589     case PMBUS_VIN_UV_FAULT_LIMIT:        /* R/W word */
590         if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
591             pmbus_send16(pmdev, pmdev->pages[index].vin_uv_fault_limit);
592         } else {
593             goto passthough;
594         }
595         break;
596 
597     case PMBUS_VIN_UV_FAULT_RESPONSE:     /* R/W byte */
598         if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
599             pmbus_send8(pmdev, pmdev->pages[index].vin_uv_fault_response);
600         } else {
601             goto passthough;
602         }
603         break;
604 
605     case PMBUS_IIN_OC_FAULT_LIMIT:        /* R/W word */
606         if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
607             pmbus_send16(pmdev, pmdev->pages[index].iin_oc_fault_limit);
608         } else {
609             goto passthough;
610         }
611         break;
612 
613     case PMBUS_IIN_OC_FAULT_RESPONSE:     /* R/W byte */
614         if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
615             pmbus_send8(pmdev, pmdev->pages[index].iin_oc_fault_response);
616         } else {
617             goto passthough;
618         }
619         break;
620 
621     case PMBUS_IIN_OC_WARN_LIMIT:         /* R/W word */
622         if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
623             pmbus_send16(pmdev, pmdev->pages[index].iin_oc_warn_limit);
624         } else {
625             goto passthough;
626         }
627         break;
628 
629     case PMBUS_POUT_OP_FAULT_LIMIT:       /* R/W word */
630         if (pmdev->pages[index].page_flags & PB_HAS_POUT) {
631             pmbus_send16(pmdev, pmdev->pages[index].pout_op_fault_limit);
632         } else {
633             goto passthough;
634         }
635         break;
636 
637     case PMBUS_POUT_OP_FAULT_RESPONSE:    /* R/W byte */
638         if (pmdev->pages[index].page_flags & PB_HAS_POUT) {
639             pmbus_send8(pmdev, pmdev->pages[index].pout_op_fault_response);
640         } else {
641             goto passthough;
642         }
643         break;
644 
645     case PMBUS_POUT_OP_WARN_LIMIT:        /* R/W word */
646         if (pmdev->pages[index].page_flags & PB_HAS_POUT) {
647             pmbus_send16(pmdev, pmdev->pages[index].pout_op_warn_limit);
648         } else {
649             goto passthough;
650         }
651         break;
652 
653     case PMBUS_PIN_OP_WARN_LIMIT:         /* R/W word */
654         if (pmdev->pages[index].page_flags & PB_HAS_PIN) {
655             pmbus_send16(pmdev, pmdev->pages[index].pin_op_warn_limit);
656         } else {
657             goto passthough;
658         }
659         break;
660 
661     case PMBUS_STATUS_BYTE:               /* R/W byte */
662         pmbus_send8(pmdev, pmdev->pages[index].status_word & 0xFF);
663         break;
664 
665     case PMBUS_STATUS_WORD:               /* R/W word */
666         pmbus_send16(pmdev, pmdev->pages[index].status_word);
667         break;
668 
669     case PMBUS_STATUS_VOUT:               /* R/W byte */
670         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
671             pmbus_send8(pmdev, pmdev->pages[index].status_vout);
672         } else {
673             goto passthough;
674         }
675         break;
676 
677     case PMBUS_STATUS_IOUT:               /* R/W byte */
678         if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
679             pmbus_send8(pmdev, pmdev->pages[index].status_iout);
680         } else {
681             goto passthough;
682         }
683         break;
684 
685     case PMBUS_STATUS_INPUT:              /* R/W byte */
686         if (pmdev->pages[index].page_flags & PB_HAS_VIN ||
687             pmdev->pages[index].page_flags & PB_HAS_IIN ||
688             pmdev->pages[index].page_flags & PB_HAS_PIN) {
689             pmbus_send8(pmdev, pmdev->pages[index].status_input);
690         } else {
691             goto passthough;
692         }
693         break;
694 
695     case PMBUS_STATUS_TEMPERATURE:        /* R/W byte */
696         if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
697             pmbus_send8(pmdev, pmdev->pages[index].status_temperature);
698         } else {
699             goto passthough;
700         }
701         break;
702 
703     case PMBUS_STATUS_CML:                /* R/W byte */
704         pmbus_send8(pmdev, pmdev->pages[index].status_cml);
705         break;
706 
707     case PMBUS_STATUS_OTHER:              /* R/W byte */
708         pmbus_send8(pmdev, pmdev->pages[index].status_other);
709         break;
710 
711     case PMBUS_READ_EIN:                  /* Read-Only block 5 bytes */
712         if (pmdev->pages[index].page_flags & PB_HAS_EIN) {
713             pmbus_send(pmdev, pmdev->pages[index].read_ein, 5);
714         } else {
715             goto passthough;
716         }
717         break;
718 
719     case PMBUS_READ_EOUT:                 /* Read-Only block 5 bytes */
720         if (pmdev->pages[index].page_flags & PB_HAS_EOUT) {
721             pmbus_send(pmdev, pmdev->pages[index].read_eout, 5);
722         } else {
723             goto passthough;
724         }
725         break;
726 
727     case PMBUS_READ_VIN:                  /* Read-Only word */
728         if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
729             pmbus_send16(pmdev, pmdev->pages[index].read_vin);
730         } else {
731             goto passthough;
732         }
733         break;
734 
735     case PMBUS_READ_IIN:                  /* Read-Only word */
736         if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
737             pmbus_send16(pmdev, pmdev->pages[index].read_iin);
738         } else {
739             goto passthough;
740         }
741         break;
742 
743     case PMBUS_READ_VOUT:                 /* Read-Only word */
744         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
745             pmbus_send16(pmdev, pmdev->pages[index].read_vout);
746         } else {
747             goto passthough;
748         }
749         break;
750 
751     case PMBUS_READ_IOUT:                 /* Read-Only word */
752         if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
753             pmbus_send16(pmdev, pmdev->pages[index].read_iout);
754         } else {
755             goto passthough;
756         }
757         break;
758 
759     case PMBUS_READ_TEMPERATURE_1:        /* Read-Only word */
760         if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
761             pmbus_send16(pmdev, pmdev->pages[index].read_temperature_1);
762         } else {
763             goto passthough;
764         }
765         break;
766 
767     case PMBUS_READ_TEMPERATURE_2:        /* Read-Only word */
768         if (pmdev->pages[index].page_flags & PB_HAS_TEMP2) {
769             pmbus_send16(pmdev, pmdev->pages[index].read_temperature_2);
770         } else {
771             goto passthough;
772         }
773         break;
774 
775     case PMBUS_READ_TEMPERATURE_3:        /* Read-Only word */
776         if (pmdev->pages[index].page_flags & PB_HAS_TEMP3) {
777             pmbus_send16(pmdev, pmdev->pages[index].read_temperature_3);
778         } else {
779             goto passthough;
780         }
781         break;
782 
783     case PMBUS_READ_POUT:                 /* Read-Only word */
784         if (pmdev->pages[index].page_flags & PB_HAS_POUT) {
785             pmbus_send16(pmdev, pmdev->pages[index].read_pout);
786         } else {
787             goto passthough;
788         }
789         break;
790 
791     case PMBUS_READ_PIN:                  /* Read-Only word */
792         if (pmdev->pages[index].page_flags & PB_HAS_PIN) {
793             pmbus_send16(pmdev, pmdev->pages[index].read_pin);
794         } else {
795             goto passthough;
796         }
797         break;
798 
799     case PMBUS_REVISION:                  /* Read-Only byte */
800         pmbus_send8(pmdev, pmdev->pages[index].revision);
801         break;
802 
803     case PMBUS_MFR_ID:                    /* R/W block */
804         if (pmdev->pages[index].page_flags & PB_HAS_MFR_INFO) {
805             pmbus_send_string(pmdev, pmdev->pages[index].mfr_id);
806         } else {
807             goto passthough;
808         }
809         break;
810 
811     case PMBUS_MFR_MODEL:                 /* R/W block */
812         if (pmdev->pages[index].page_flags & PB_HAS_MFR_INFO) {
813             pmbus_send_string(pmdev, pmdev->pages[index].mfr_model);
814         } else {
815             goto passthough;
816         }
817         break;
818 
819     case PMBUS_MFR_REVISION:              /* R/W block */
820         if (pmdev->pages[index].page_flags & PB_HAS_MFR_INFO) {
821             pmbus_send_string(pmdev, pmdev->pages[index].mfr_revision);
822         } else {
823             goto passthough;
824         }
825         break;
826 
827     case PMBUS_MFR_LOCATION:              /* R/W block */
828         if (pmdev->pages[index].page_flags & PB_HAS_MFR_INFO) {
829             pmbus_send_string(pmdev, pmdev->pages[index].mfr_location);
830         } else {
831             goto passthough;
832         }
833         break;
834 
835     case PMBUS_MFR_VIN_MIN:               /* Read-Only word */
836         if (pmdev->pages[index].page_flags & PB_HAS_VIN_RATING) {
837             pmbus_send16(pmdev, pmdev->pages[index].mfr_vin_min);
838         } else {
839             goto passthough;
840         }
841         break;
842 
843     case PMBUS_MFR_VIN_MAX:               /* Read-Only word */
844         if (pmdev->pages[index].page_flags & PB_HAS_VIN_RATING) {
845             pmbus_send16(pmdev, pmdev->pages[index].mfr_vin_max);
846         } else {
847             goto passthough;
848         }
849         break;
850 
851     case PMBUS_MFR_IIN_MAX:               /* Read-Only word */
852         if (pmdev->pages[index].page_flags & PB_HAS_IIN_RATING) {
853             pmbus_send16(pmdev, pmdev->pages[index].mfr_iin_max);
854         } else {
855             goto passthough;
856         }
857         break;
858 
859     case PMBUS_MFR_PIN_MAX:               /* Read-Only word */
860         if (pmdev->pages[index].page_flags & PB_HAS_PIN_RATING) {
861             pmbus_send16(pmdev, pmdev->pages[index].mfr_pin_max);
862         } else {
863             goto passthough;
864         }
865         break;
866 
867     case PMBUS_MFR_VOUT_MIN:              /* Read-Only word */
868         if (pmdev->pages[index].page_flags & PB_HAS_VOUT_RATING) {
869             pmbus_send16(pmdev, pmdev->pages[index].mfr_vout_min);
870         } else {
871             goto passthough;
872         }
873         break;
874 
875     case PMBUS_MFR_VOUT_MAX:              /* Read-Only word */
876         if (pmdev->pages[index].page_flags & PB_HAS_VOUT_RATING) {
877             pmbus_send16(pmdev, pmdev->pages[index].mfr_vout_max);
878         } else {
879             goto passthough;
880         }
881         break;
882 
883     case PMBUS_MFR_IOUT_MAX:              /* Read-Only word */
884         if (pmdev->pages[index].page_flags & PB_HAS_IOUT_RATING) {
885             pmbus_send16(pmdev, pmdev->pages[index].mfr_iout_max);
886         } else {
887             goto passthough;
888         }
889         break;
890 
891     case PMBUS_MFR_POUT_MAX:              /* Read-Only word */
892         if (pmdev->pages[index].page_flags & PB_HAS_POUT_RATING) {
893             pmbus_send16(pmdev, pmdev->pages[index].mfr_pout_max);
894         } else {
895             goto passthough;
896         }
897         break;
898 
899     case PMBUS_MFR_MAX_TEMP_1:            /* R/W word */
900         if (pmdev->pages[index].page_flags & PB_HAS_TEMP_RATING) {
901             pmbus_send16(pmdev, pmdev->pages[index].mfr_max_temp_1);
902         } else {
903             goto passthough;
904         }
905         break;
906 
907     case PMBUS_MFR_MAX_TEMP_2:            /* R/W word */
908         if (pmdev->pages[index].page_flags & PB_HAS_TEMP_RATING) {
909             pmbus_send16(pmdev, pmdev->pages[index].mfr_max_temp_2);
910         } else {
911             goto passthough;
912         }
913         break;
914 
915     case PMBUS_MFR_MAX_TEMP_3:            /* R/W word */
916         if (pmdev->pages[index].page_flags & PB_HAS_TEMP_RATING) {
917             pmbus_send16(pmdev, pmdev->pages[index].mfr_max_temp_3);
918         } else {
919             goto passthough;
920         }
921         break;
922 
923     case PMBUS_CLEAR_FAULTS:              /* Send Byte */
924     case PMBUS_PAGE_PLUS_WRITE:           /* Block Write-only */
925     case PMBUS_STORE_DEFAULT_ALL:         /* Send Byte */
926     case PMBUS_RESTORE_DEFAULT_ALL:       /* Send Byte */
927     case PMBUS_STORE_DEFAULT_CODE:        /* Write-only Byte */
928     case PMBUS_RESTORE_DEFAULT_CODE:      /* Write-only Byte */
929     case PMBUS_STORE_USER_ALL:            /* Send Byte */
930     case PMBUS_RESTORE_USER_ALL:          /* Send Byte */
931     case PMBUS_STORE_USER_CODE:           /* Write-only Byte */
932     case PMBUS_RESTORE_USER_CODE:         /* Write-only Byte */
933     case PMBUS_QUERY:                     /* Write-Only */
934         qemu_log_mask(LOG_GUEST_ERROR,
935                       "%s: reading from write only register 0x%02x\n",
936                       __func__, pmdev->code);
937         break;
938 
939 passthough:
940     default:
941         /* Pass through read request if not handled */
942         if (pmdc->receive_byte) {
943             ret = pmdc->receive_byte(pmdev);
944         }
945         break;
946     }
947 
948     if (pmdev->out_buf_len != 0) {
949         ret = pmbus_out_buf_pop(pmdev);
950         return ret;
951     }
952 
953     return ret;
954 }
955 
956 /*
957  * PMBus clear faults command applies to all status registers, existing faults
958  * should separately get re-asserted.
959  */
960 static void pmbus_clear_faults(PMBusDevice *pmdev)
961 {
962     for (uint8_t i = 0; i < pmdev->num_pages; i++) {
963         pmdev->pages[i].status_word = 0;
964         pmdev->pages[i].status_vout = 0;
965         pmdev->pages[i].status_iout = 0;
966         pmdev->pages[i].status_input = 0;
967         pmdev->pages[i].status_temperature = 0;
968         pmdev->pages[i].status_cml = 0;
969         pmdev->pages[i].status_other = 0;
970         pmdev->pages[i].status_mfr_specific = 0;
971         pmdev->pages[i].status_fans_1_2 = 0;
972         pmdev->pages[i].status_fans_3_4 = 0;
973     }
974 
975 }
976 
977 /*
978  * PMBus operation is used to turn On and Off PSUs
979  * Therefore, default value for the Operation should be PB_OP_ON or 0x80
980  */
981 static void pmbus_operation(PMBusDevice *pmdev)
982 {
983     uint8_t index = pmdev->page;
984     if ((pmdev->pages[index].operation & PB_OP_ON) == 0) {
985         pmdev->pages[index].read_vout = 0;
986         pmdev->pages[index].read_iout = 0;
987         pmdev->pages[index].read_pout = 0;
988         return;
989     }
990 
991     if (pmdev->pages[index].operation & (PB_OP_ON | PB_OP_MARGIN_HIGH)) {
992         pmdev->pages[index].read_vout = pmdev->pages[index].vout_margin_high;
993     }
994 
995     if (pmdev->pages[index].operation & (PB_OP_ON | PB_OP_MARGIN_LOW)) {
996         pmdev->pages[index].read_vout = pmdev->pages[index].vout_margin_low;
997     }
998     pmbus_check_limits(pmdev);
999 }
1000 
1001 static int pmbus_write_data(SMBusDevice *smd, uint8_t *buf, uint8_t len)
1002 {
1003     PMBusDevice *pmdev = PMBUS_DEVICE(smd);
1004     PMBusDeviceClass *pmdc = PMBUS_DEVICE_GET_CLASS(pmdev);
1005     int ret = 0;
1006     uint8_t index;
1007 
1008     if (len == 0) {
1009         qemu_log_mask(LOG_GUEST_ERROR, "%s: writing empty data\n", __func__);
1010         return -1;
1011     }
1012 
1013     if (!pmdev->pages) { /* allocate memory for pages on first use */
1014         pmbus_pages_alloc(pmdev);
1015     }
1016 
1017     pmdev->in_buf_len = len;
1018     pmdev->in_buf = buf;
1019 
1020     pmdev->code = buf[0]; /* PMBus command code */
1021     if (len == 1) { /* Single length writes are command codes only */
1022         return 0;
1023     }
1024 
1025     if (pmdev->code == PMBUS_PAGE) {
1026         pmdev->page = pmbus_receive8(pmdev);
1027         return 0;
1028     }
1029     /* loop through all the pages when 0xFF is received */
1030     if (pmdev->page == PB_ALL_PAGES) {
1031         for (int i = 0; i < pmdev->num_pages; i++) {
1032             pmdev->page = i;
1033             pmbus_write_data(smd, buf, len);
1034         }
1035         pmdev->page = PB_ALL_PAGES;
1036         return 0;
1037     }
1038 
1039     index = pmdev->page;
1040 
1041     switch (pmdev->code) {
1042     case PMBUS_OPERATION:                 /* R/W byte */
1043         pmdev->pages[index].operation = pmbus_receive8(pmdev);
1044         pmbus_operation(pmdev);
1045         break;
1046 
1047     case PMBUS_ON_OFF_CONFIG:             /* R/W byte */
1048         pmdev->pages[index].on_off_config = pmbus_receive8(pmdev);
1049         break;
1050 
1051     case PMBUS_CLEAR_FAULTS:              /* Send Byte */
1052         pmbus_clear_faults(pmdev);
1053         break;
1054 
1055     case PMBUS_PHASE:                     /* R/W byte */
1056         pmdev->pages[index].phase = pmbus_receive8(pmdev);
1057         break;
1058 
1059     case PMBUS_PAGE_PLUS_WRITE:           /* Block Write-only */
1060     case PMBUS_WRITE_PROTECT:             /* R/W byte */
1061         pmdev->pages[index].write_protect = pmbus_receive8(pmdev);
1062         break;
1063 
1064     case PMBUS_VOUT_MODE:                 /* R/W byte */
1065         if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MODE) {
1066             pmdev->pages[index].vout_mode = pmbus_receive8(pmdev);
1067         } else {
1068             goto passthrough;
1069         }
1070         break;
1071 
1072     case PMBUS_VOUT_COMMAND:              /* R/W word */
1073         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
1074             pmdev->pages[index].vout_command = pmbus_receive16(pmdev);
1075         } else {
1076             goto passthrough;
1077         }
1078         break;
1079 
1080     case PMBUS_VOUT_TRIM:                 /* R/W word */
1081         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
1082             pmdev->pages[index].vout_trim = pmbus_receive16(pmdev);
1083         } else {
1084             goto passthrough;
1085         }
1086         break;
1087 
1088     case PMBUS_VOUT_CAL_OFFSET:           /* R/W word */
1089         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
1090             pmdev->pages[index].vout_cal_offset = pmbus_receive16(pmdev);
1091         } else {
1092             goto passthrough;
1093         }
1094         break;
1095 
1096     case PMBUS_VOUT_MAX:                  /* R/W word */
1097         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
1098             pmdev->pages[index].vout_max = pmbus_receive16(pmdev);
1099         } else {
1100             goto passthrough;
1101         }
1102         break;
1103 
1104     case PMBUS_VOUT_MARGIN_HIGH:          /* R/W word */
1105         if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MARGIN) {
1106             pmdev->pages[index].vout_margin_high = pmbus_receive16(pmdev);
1107         } else {
1108             goto passthrough;
1109         }
1110         break;
1111 
1112     case PMBUS_VOUT_MARGIN_LOW:           /* R/W word */
1113         if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MARGIN) {
1114             pmdev->pages[index].vout_margin_low = pmbus_receive16(pmdev);
1115         } else {
1116             goto passthrough;
1117         }
1118         break;
1119 
1120     case PMBUS_VOUT_TRANSITION_RATE:      /* R/W word */
1121         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
1122             pmdev->pages[index].vout_transition_rate = pmbus_receive16(pmdev);
1123         } else {
1124             goto passthrough;
1125         }
1126         break;
1127 
1128     case PMBUS_VOUT_DROOP:                /* R/W word */
1129         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
1130             pmdev->pages[index].vout_droop = pmbus_receive16(pmdev);
1131         } else {
1132             goto passthrough;
1133         }
1134         break;
1135 
1136     case PMBUS_VOUT_SCALE_LOOP:           /* R/W word */
1137         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
1138             pmdev->pages[index].vout_scale_loop = pmbus_receive16(pmdev);
1139         } else {
1140             goto passthrough;
1141         }
1142         break;
1143 
1144     case PMBUS_VOUT_SCALE_MONITOR:        /* R/W word */
1145         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
1146             pmdev->pages[index].vout_scale_monitor = pmbus_receive16(pmdev);
1147         } else {
1148             goto passthrough;
1149         }
1150         break;
1151 
1152     case PMBUS_POUT_MAX:                  /* R/W word */
1153         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
1154             pmdev->pages[index].pout_max = pmbus_receive16(pmdev);
1155         } else {
1156             goto passthrough;
1157         }
1158         break;
1159 
1160     case PMBUS_VIN_ON:                    /* R/W word */
1161         if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
1162             pmdev->pages[index].vin_on = pmbus_receive16(pmdev);
1163         } else {
1164             goto passthrough;
1165         }
1166         break;
1167 
1168     case PMBUS_VIN_OFF:                   /* R/W word */
1169         if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
1170             pmdev->pages[index].vin_off = pmbus_receive16(pmdev);
1171         } else {
1172             goto passthrough;
1173         }
1174         break;
1175 
1176     case PMBUS_IOUT_CAL_GAIN:             /* R/W word */
1177         if (pmdev->pages[index].page_flags & PB_HAS_IOUT_GAIN) {
1178             pmdev->pages[index].iout_cal_gain = pmbus_receive16(pmdev);
1179         } else {
1180             goto passthrough;
1181         }
1182         break;
1183 
1184     case PMBUS_VOUT_OV_FAULT_LIMIT:       /* R/W word */
1185         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
1186             pmdev->pages[index].vout_ov_fault_limit = pmbus_receive16(pmdev);
1187         } else {
1188             goto passthrough;
1189         }
1190         break;
1191 
1192     case PMBUS_VOUT_OV_FAULT_RESPONSE:    /* R/W byte */
1193         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
1194             pmdev->pages[index].vout_ov_fault_response = pmbus_receive8(pmdev);
1195         } else {
1196             goto passthrough;
1197         }
1198         break;
1199 
1200     case PMBUS_VOUT_OV_WARN_LIMIT:        /* R/W word */
1201         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
1202             pmdev->pages[index].vout_ov_warn_limit = pmbus_receive16(pmdev);
1203         } else {
1204             goto passthrough;
1205         }
1206         break;
1207 
1208     case PMBUS_VOUT_UV_WARN_LIMIT:        /* R/W word */
1209         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
1210             pmdev->pages[index].vout_uv_warn_limit = pmbus_receive16(pmdev);
1211         } else {
1212             goto passthrough;
1213         }
1214         break;
1215 
1216     case PMBUS_VOUT_UV_FAULT_LIMIT:       /* R/W word */
1217         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
1218             pmdev->pages[index].vout_uv_fault_limit = pmbus_receive16(pmdev);
1219         } else {
1220             goto passthrough;
1221         }
1222         break;
1223 
1224     case PMBUS_VOUT_UV_FAULT_RESPONSE:    /* R/W byte */
1225         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
1226             pmdev->pages[index].vout_uv_fault_response = pmbus_receive8(pmdev);
1227         } else {
1228             goto passthrough;
1229         }
1230         break;
1231 
1232     case PMBUS_IOUT_OC_FAULT_LIMIT:       /* R/W word */
1233         if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
1234             pmdev->pages[index].iout_oc_fault_limit = pmbus_receive16(pmdev);
1235         } else {
1236             goto passthrough;
1237         }
1238         break;
1239 
1240     case PMBUS_IOUT_OC_FAULT_RESPONSE:    /* R/W byte */
1241         if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
1242             pmdev->pages[index].iout_oc_fault_response = pmbus_receive8(pmdev);
1243         } else {
1244             goto passthrough;
1245         }
1246         break;
1247 
1248     case PMBUS_IOUT_OC_LV_FAULT_LIMIT:    /* R/W word */
1249         if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
1250             pmdev->pages[index].iout_oc_lv_fault_limit = pmbus_receive16(pmdev);
1251         } else {
1252             goto passthrough;
1253         }
1254         break;
1255 
1256     case PMBUS_IOUT_OC_LV_FAULT_RESPONSE: /* R/W byte */
1257         if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
1258             pmdev->pages[index].iout_oc_lv_fault_response
1259                 = pmbus_receive8(pmdev);
1260         } else {
1261             goto passthrough;
1262         }
1263         break;
1264 
1265     case PMBUS_IOUT_OC_WARN_LIMIT:        /* R/W word */
1266         if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
1267             pmdev->pages[index].iout_oc_warn_limit = pmbus_receive16(pmdev);
1268         } else {
1269             goto passthrough;
1270         }
1271         break;
1272 
1273     case PMBUS_IOUT_UC_FAULT_LIMIT:       /* R/W word */
1274         if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
1275             pmdev->pages[index].iout_uc_fault_limit = pmbus_receive16(pmdev);
1276         } else {
1277             goto passthrough;
1278         }
1279         break;
1280 
1281     case PMBUS_IOUT_UC_FAULT_RESPONSE:    /* R/W byte */
1282         if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
1283             pmdev->pages[index].iout_uc_fault_response = pmbus_receive8(pmdev);
1284         } else {
1285             goto passthrough;
1286         }
1287         break;
1288 
1289     case PMBUS_OT_FAULT_LIMIT:            /* R/W word */
1290         if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
1291             pmdev->pages[index].ot_fault_limit = pmbus_receive16(pmdev);
1292         } else {
1293             goto passthrough;
1294         }
1295         break;
1296 
1297     case PMBUS_OT_FAULT_RESPONSE:         /* R/W byte */
1298         if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
1299             pmdev->pages[index].ot_fault_response = pmbus_receive8(pmdev);
1300         } else {
1301             goto passthrough;
1302         }
1303         break;
1304 
1305     case PMBUS_OT_WARN_LIMIT:             /* R/W word */
1306         if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
1307             pmdev->pages[index].ot_warn_limit = pmbus_receive16(pmdev);
1308         } else {
1309             goto passthrough;
1310         }
1311         break;
1312 
1313     case PMBUS_UT_WARN_LIMIT:             /* R/W word */
1314         if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
1315             pmdev->pages[index].ut_warn_limit = pmbus_receive16(pmdev);
1316         } else {
1317             goto passthrough;
1318         }
1319         break;
1320 
1321     case PMBUS_UT_FAULT_LIMIT:            /* R/W word */
1322         if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
1323             pmdev->pages[index].ut_fault_limit = pmbus_receive16(pmdev);
1324         } else {
1325             goto passthrough;
1326         }
1327         break;
1328 
1329     case PMBUS_UT_FAULT_RESPONSE:         /* R/W byte */
1330         if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
1331             pmdev->pages[index].ut_fault_response = pmbus_receive8(pmdev);
1332         } else {
1333             goto passthrough;
1334         }
1335         break;
1336 
1337     case PMBUS_VIN_OV_FAULT_LIMIT:        /* R/W word */
1338         if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
1339             pmdev->pages[index].vin_ov_fault_limit = pmbus_receive16(pmdev);
1340         } else {
1341             goto passthrough;
1342         }
1343         break;
1344 
1345     case PMBUS_VIN_OV_FAULT_RESPONSE:     /* R/W byte */
1346         if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
1347             pmdev->pages[index].vin_ov_fault_response = pmbus_receive8(pmdev);
1348         } else {
1349             goto passthrough;
1350         }
1351         break;
1352 
1353     case PMBUS_VIN_OV_WARN_LIMIT:         /* R/W word */
1354         if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
1355             pmdev->pages[index].vin_ov_warn_limit = pmbus_receive16(pmdev);
1356         } else {
1357             goto passthrough;
1358         }
1359         break;
1360 
1361     case PMBUS_VIN_UV_WARN_LIMIT:         /* R/W word */
1362         if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
1363             pmdev->pages[index].vin_uv_warn_limit = pmbus_receive16(pmdev);
1364         } else {
1365             goto passthrough;
1366         }
1367         break;
1368 
1369     case PMBUS_VIN_UV_FAULT_LIMIT:        /* R/W word */
1370         if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
1371             pmdev->pages[index].vin_uv_fault_limit = pmbus_receive16(pmdev);
1372         } else {
1373             goto passthrough;
1374         }
1375         break;
1376 
1377     case PMBUS_VIN_UV_FAULT_RESPONSE:     /* R/W byte */
1378         if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
1379             pmdev->pages[index].vin_uv_fault_response = pmbus_receive8(pmdev);
1380         } else {
1381             goto passthrough;
1382         }
1383         break;
1384 
1385     case PMBUS_IIN_OC_FAULT_LIMIT:        /* R/W word */
1386         if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
1387             pmdev->pages[index].iin_oc_fault_limit = pmbus_receive16(pmdev);
1388         } else {
1389             goto passthrough;
1390         }
1391         break;
1392 
1393     case PMBUS_IIN_OC_FAULT_RESPONSE:     /* R/W byte */
1394         if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
1395             pmdev->pages[index].iin_oc_fault_response = pmbus_receive8(pmdev);
1396         } else {
1397             goto passthrough;
1398         }
1399         break;
1400 
1401     case PMBUS_IIN_OC_WARN_LIMIT:         /* R/W word */
1402         if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
1403             pmdev->pages[index].iin_oc_warn_limit = pmbus_receive16(pmdev);
1404         } else {
1405             goto passthrough;
1406         }
1407         break;
1408 
1409     case PMBUS_POUT_OP_FAULT_LIMIT:       /* R/W word */
1410         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
1411             pmdev->pages[index].pout_op_fault_limit = pmbus_receive16(pmdev);
1412         } else {
1413             goto passthrough;
1414         }
1415         break;
1416 
1417     case PMBUS_POUT_OP_FAULT_RESPONSE:    /* R/W byte */
1418         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
1419             pmdev->pages[index].pout_op_fault_response = pmbus_receive8(pmdev);
1420         } else {
1421             goto passthrough;
1422         }
1423         break;
1424 
1425     case PMBUS_POUT_OP_WARN_LIMIT:        /* R/W word */
1426         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
1427             pmdev->pages[index].pout_op_warn_limit = pmbus_receive16(pmdev);
1428         } else {
1429             goto passthrough;
1430         }
1431         break;
1432 
1433     case PMBUS_PIN_OP_WARN_LIMIT:         /* R/W word */
1434         if (pmdev->pages[index].page_flags & PB_HAS_PIN) {
1435             pmdev->pages[index].pin_op_warn_limit = pmbus_receive16(pmdev);
1436         } else {
1437             goto passthrough;
1438         }
1439         break;
1440 
1441     case PMBUS_STATUS_BYTE:               /* R/W byte */
1442         pmdev->pages[index].status_word = pmbus_receive8(pmdev);
1443         break;
1444 
1445     case PMBUS_STATUS_WORD:               /* R/W word */
1446         pmdev->pages[index].status_word = pmbus_receive16(pmdev);
1447         break;
1448 
1449     case PMBUS_STATUS_VOUT:               /* R/W byte */
1450         if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
1451             pmdev->pages[index].status_vout = pmbus_receive8(pmdev);
1452         } else {
1453             goto passthrough;
1454         }
1455         break;
1456 
1457     case PMBUS_STATUS_IOUT:               /* R/W byte */
1458         if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
1459             pmdev->pages[index].status_iout = pmbus_receive8(pmdev);
1460         } else {
1461             goto passthrough;
1462         }
1463         break;
1464 
1465     case PMBUS_STATUS_INPUT:              /* R/W byte */
1466         pmdev->pages[index].status_input = pmbus_receive8(pmdev);
1467         break;
1468 
1469     case PMBUS_STATUS_TEMPERATURE:        /* R/W byte */
1470         if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
1471             pmdev->pages[index].status_temperature = pmbus_receive8(pmdev);
1472         } else {
1473             goto passthrough;
1474         }
1475         break;
1476 
1477     case PMBUS_STATUS_CML:                /* R/W byte */
1478         pmdev->pages[index].status_cml = pmbus_receive8(pmdev);
1479         break;
1480 
1481     case PMBUS_STATUS_OTHER:              /* R/W byte */
1482         pmdev->pages[index].status_other = pmbus_receive8(pmdev);
1483         break;
1484 
1485     case PMBUS_PAGE_PLUS_READ:            /* Block Read-only */
1486     case PMBUS_CAPABILITY:                /* Read-Only byte */
1487     case PMBUS_COEFFICIENTS:              /* Read-only block 5 bytes */
1488     case PMBUS_READ_EIN:                  /* Read-Only block 5 bytes */
1489     case PMBUS_READ_EOUT:                 /* Read-Only block 5 bytes */
1490     case PMBUS_READ_VIN:                  /* Read-Only word */
1491     case PMBUS_READ_IIN:                  /* Read-Only word */
1492     case PMBUS_READ_VCAP:                 /* Read-Only word */
1493     case PMBUS_READ_VOUT:                 /* Read-Only word */
1494     case PMBUS_READ_IOUT:                 /* Read-Only word */
1495     case PMBUS_READ_TEMPERATURE_1:        /* Read-Only word */
1496     case PMBUS_READ_TEMPERATURE_2:        /* Read-Only word */
1497     case PMBUS_READ_TEMPERATURE_3:        /* Read-Only word */
1498     case PMBUS_READ_FAN_SPEED_1:          /* Read-Only word */
1499     case PMBUS_READ_FAN_SPEED_2:          /* Read-Only word */
1500     case PMBUS_READ_FAN_SPEED_3:          /* Read-Only word */
1501     case PMBUS_READ_FAN_SPEED_4:          /* Read-Only word */
1502     case PMBUS_READ_DUTY_CYCLE:           /* Read-Only word */
1503     case PMBUS_READ_FREQUENCY:            /* Read-Only word */
1504     case PMBUS_READ_POUT:                 /* Read-Only word */
1505     case PMBUS_READ_PIN:                  /* Read-Only word */
1506     case PMBUS_REVISION:                  /* Read-Only byte */
1507     case PMBUS_APP_PROFILE_SUPPORT:       /* Read-Only block-read */
1508     case PMBUS_MFR_VIN_MIN:               /* Read-Only word */
1509     case PMBUS_MFR_VIN_MAX:               /* Read-Only word */
1510     case PMBUS_MFR_IIN_MAX:               /* Read-Only word */
1511     case PMBUS_MFR_PIN_MAX:               /* Read-Only word */
1512     case PMBUS_MFR_VOUT_MIN:              /* Read-Only word */
1513     case PMBUS_MFR_VOUT_MAX:              /* Read-Only word */
1514     case PMBUS_MFR_IOUT_MAX:              /* Read-Only word */
1515     case PMBUS_MFR_POUT_MAX:              /* Read-Only word */
1516     case PMBUS_MFR_TAMBIENT_MAX:          /* Read-Only word */
1517     case PMBUS_MFR_TAMBIENT_MIN:          /* Read-Only word */
1518     case PMBUS_MFR_EFFICIENCY_LL:         /* Read-Only block 14 bytes */
1519     case PMBUS_MFR_EFFICIENCY_HL:         /* Read-Only block 14 bytes */
1520     case PMBUS_MFR_PIN_ACCURACY:          /* Read-Only byte */
1521     case PMBUS_IC_DEVICE_ID:              /* Read-Only block-read */
1522     case PMBUS_IC_DEVICE_REV:             /* Read-Only block-read */
1523         qemu_log_mask(LOG_GUEST_ERROR,
1524                       "%s: writing to read-only register 0x%02x\n",
1525                       __func__, pmdev->code);
1526         break;
1527 
1528 passthrough:
1529     /* Unimplimented registers get passed to the device */
1530     default:
1531         if (pmdc->write_data) {
1532             ret = pmdc->write_data(pmdev, buf, len);
1533         }
1534         break;
1535     }
1536     pmbus_check_limits(pmdev);
1537     pmdev->in_buf_len = 0;
1538     return ret;
1539 }
1540 
1541 int pmbus_page_config(PMBusDevice *pmdev, uint8_t index, uint64_t flags)
1542 {
1543     if (!pmdev->pages) { /* allocate memory for pages on first use */
1544         pmbus_pages_alloc(pmdev);
1545     }
1546 
1547     /* The 0xFF page is special for commands applying to all pages */
1548     if (index == PB_ALL_PAGES) {
1549         for (int i = 0; i < pmdev->num_pages; i++) {
1550             pmdev->pages[i].page_flags = flags;
1551         }
1552         return 0;
1553     }
1554 
1555     if (index > pmdev->num_pages - 1) {
1556         qemu_log_mask(LOG_GUEST_ERROR,
1557                       "%s: index %u is out of range\n",
1558                       __func__, index);
1559         return -1;
1560     }
1561 
1562     pmdev->pages[index].page_flags = flags;
1563 
1564     return 0;
1565 }
1566 
1567 /* TODO: include pmbus page info in vmstate */
1568 const VMStateDescription vmstate_pmbus_device = {
1569     .name = TYPE_PMBUS_DEVICE,
1570     .version_id = 0,
1571     .minimum_version_id = 0,
1572     .fields = (VMStateField[]) {
1573         VMSTATE_SMBUS_DEVICE(smb, PMBusDevice),
1574         VMSTATE_UINT8(num_pages, PMBusDevice),
1575         VMSTATE_UINT8(code, PMBusDevice),
1576         VMSTATE_UINT8(page, PMBusDevice),
1577         VMSTATE_UINT8(capability, PMBusDevice),
1578         VMSTATE_END_OF_LIST()
1579     }
1580 };
1581 
1582 static void pmbus_device_finalize(Object *obj)
1583 {
1584     PMBusDevice *pmdev = PMBUS_DEVICE(obj);
1585     g_free(pmdev->pages);
1586 }
1587 
1588 static void pmbus_device_class_init(ObjectClass *klass, void *data)
1589 {
1590     SMBusDeviceClass *k = SMBUS_DEVICE_CLASS(klass);
1591 
1592     k->quick_cmd = pmbus_quick_cmd;
1593     k->write_data = pmbus_write_data;
1594     k->receive_byte = pmbus_receive_byte;
1595 }
1596 
1597 static const TypeInfo pmbus_device_type_info = {
1598     .name = TYPE_PMBUS_DEVICE,
1599     .parent = TYPE_SMBUS_DEVICE,
1600     .instance_size = sizeof(PMBusDevice),
1601     .instance_finalize = pmbus_device_finalize,
1602     .abstract = true,
1603     .class_size = sizeof(PMBusDeviceClass),
1604     .class_init = pmbus_device_class_init,
1605 };
1606 
1607 static void pmbus_device_register_types(void)
1608 {
1609     type_register_static(&pmbus_device_type_info);
1610 }
1611 
1612 type_init(pmbus_device_register_types)
1613