|  | /* | 
|  | * Analog Devices ADM1266 Cascadable Super Sequencer with Margin Control and | 
|  | * Fault Recording with PMBus | 
|  | * | 
|  | * https://www.analog.com/media/en/technical-documentation/data-sheets/adm1266.pdf | 
|  | * | 
|  | * Copyright 2023 Google LLC | 
|  | * | 
|  | * SPDX-License-Identifier: GPL-2.0-or-later | 
|  | */ | 
|  |  | 
|  | #include "qemu/osdep.h" | 
|  | #include "hw/i2c/pmbus_device.h" | 
|  | #include "hw/irq.h" | 
|  | #include "migration/vmstate.h" | 
|  | #include "qapi/error.h" | 
|  | #include "qapi/visitor.h" | 
|  | #include "qemu/log.h" | 
|  | #include "qemu/module.h" | 
|  |  | 
|  | #define TYPE_ADM1266 "adm1266" | 
|  | OBJECT_DECLARE_SIMPLE_TYPE(ADM1266State, ADM1266) | 
|  |  | 
|  | #define ADM1266_BLACKBOX_CONFIG                 0xD3 | 
|  | #define ADM1266_PDIO_CONFIG                     0xD4 | 
|  | #define ADM1266_READ_STATE                      0xD9 | 
|  | #define ADM1266_READ_BLACKBOX                   0xDE | 
|  | #define ADM1266_SET_RTC                         0xDF | 
|  | #define ADM1266_GPIO_SYNC_CONFIGURATION         0xE1 | 
|  | #define ADM1266_BLACKBOX_INFORMATION            0xE6 | 
|  | #define ADM1266_PDIO_STATUS                     0xE9 | 
|  | #define ADM1266_GPIO_STATUS                     0xEA | 
|  |  | 
|  | /* Defaults */ | 
|  | #define ADM1266_OPERATION_DEFAULT               0x80 | 
|  | #define ADM1266_CAPABILITY_DEFAULT              0xA0 | 
|  | #define ADM1266_CAPABILITY_NO_PEC               0x20 | 
|  | #define ADM1266_PMBUS_REVISION_DEFAULT          0x22 | 
|  | #define ADM1266_MFR_ID_DEFAULT                  "ADI" | 
|  | #define ADM1266_MFR_ID_DEFAULT_LEN              32 | 
|  | #define ADM1266_MFR_MODEL_DEFAULT               "ADM1266-A1" | 
|  | #define ADM1266_MFR_MODEL_DEFAULT_LEN           32 | 
|  | #define ADM1266_MFR_REVISION_DEFAULT            "25" | 
|  | #define ADM1266_MFR_REVISION_DEFAULT_LEN        8 | 
|  |  | 
|  | #define ADM1266_NUM_PAGES               17 | 
|  | /** | 
|  | * PAGE Index | 
|  | * Page 0 VH1. | 
|  | * Page 1 VH2. | 
|  | * Page 2 VH3. | 
|  | * Page 3 VH4. | 
|  | * Page 4 VP1. | 
|  | * Page 5 VP2. | 
|  | * Page 6 VP3. | 
|  | * Page 7 VP4. | 
|  | * Page 8 VP5. | 
|  | * Page 9 VP6. | 
|  | * Page 10 VP7. | 
|  | * Page 11 VP8. | 
|  | * Page 12 VP9. | 
|  | * Page 13 VP10. | 
|  | * Page 14 VP11. | 
|  | * Page 15 VP12. | 
|  | * Page 16 VP13. | 
|  | */ | 
|  | typedef struct ADM1266State { | 
|  | PMBusDevice parent; | 
|  |  | 
|  | char mfr_id[32]; | 
|  | char mfr_model[32]; | 
|  | char mfr_rev[8]; | 
|  | } ADM1266State; | 
|  |  | 
|  | static const uint8_t adm1266_ic_device_id[] = {0x03, 0x41, 0x12, 0x66}; | 
|  | static const uint8_t adm1266_ic_device_rev[] = {0x08, 0x01, 0x08, 0x07, 0x0, | 
|  | 0x0, 0x07, 0x41, 0x30}; | 
|  |  | 
|  | static void adm1266_exit_reset(Object *obj, ResetType type) | 
|  | { | 
|  | ADM1266State *s = ADM1266(obj); | 
|  | PMBusDevice *pmdev = PMBUS_DEVICE(obj); | 
|  |  | 
|  | pmdev->page = 0; | 
|  | pmdev->capability = ADM1266_CAPABILITY_NO_PEC; | 
|  |  | 
|  | for (int i = 0; i < ADM1266_NUM_PAGES; i++) { | 
|  | pmdev->pages[i].operation = ADM1266_OPERATION_DEFAULT; | 
|  | pmdev->pages[i].revision = ADM1266_PMBUS_REVISION_DEFAULT; | 
|  | pmdev->pages[i].vout_mode = 0; | 
|  | pmdev->pages[i].read_vout = pmbus_data2linear_mode(12, 0); | 
|  | pmdev->pages[i].vout_margin_high = pmbus_data2linear_mode(15, 0); | 
|  | pmdev->pages[i].vout_margin_low = pmbus_data2linear_mode(3, 0); | 
|  | pmdev->pages[i].vout_ov_fault_limit = pmbus_data2linear_mode(16, 0); | 
|  | pmdev->pages[i].revision = ADM1266_PMBUS_REVISION_DEFAULT; | 
|  | } | 
|  |  | 
|  | strncpy(s->mfr_id, ADM1266_MFR_ID_DEFAULT, 4); | 
|  | strncpy(s->mfr_model, ADM1266_MFR_MODEL_DEFAULT, 11); | 
|  | strncpy(s->mfr_rev, ADM1266_MFR_REVISION_DEFAULT, 3); | 
|  | } | 
|  |  | 
|  | static uint8_t adm1266_read_byte(PMBusDevice *pmdev) | 
|  | { | 
|  | ADM1266State *s = ADM1266(pmdev); | 
|  |  | 
|  | switch (pmdev->code) { | 
|  | case PMBUS_MFR_ID:                    /* R/W block */ | 
|  | pmbus_send_string(pmdev, s->mfr_id); | 
|  | break; | 
|  |  | 
|  | case PMBUS_MFR_MODEL:                 /* R/W block */ | 
|  | pmbus_send_string(pmdev, s->mfr_model); | 
|  | break; | 
|  |  | 
|  | case PMBUS_MFR_REVISION:              /* R/W block */ | 
|  | pmbus_send_string(pmdev, s->mfr_rev); | 
|  | break; | 
|  |  | 
|  | case PMBUS_IC_DEVICE_ID: | 
|  | pmbus_send(pmdev, adm1266_ic_device_id, sizeof(adm1266_ic_device_id)); | 
|  | break; | 
|  |  | 
|  | case PMBUS_IC_DEVICE_REV: | 
|  | pmbus_send(pmdev, adm1266_ic_device_rev, sizeof(adm1266_ic_device_rev)); | 
|  | break; | 
|  |  | 
|  | default: | 
|  | qemu_log_mask(LOG_UNIMP, | 
|  | "%s: reading from unimplemented register: 0x%02x\n", | 
|  | __func__, pmdev->code); | 
|  | return 0xFF; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int adm1266_write_data(PMBusDevice *pmdev, const uint8_t *buf, | 
|  | uint8_t len) | 
|  | { | 
|  | ADM1266State *s = ADM1266(pmdev); | 
|  |  | 
|  | switch (pmdev->code) { | 
|  | case PMBUS_MFR_ID:                    /* R/W block */ | 
|  | pmbus_receive_block(pmdev, (uint8_t *)s->mfr_id, sizeof(s->mfr_id)); | 
|  | break; | 
|  |  | 
|  | case PMBUS_MFR_MODEL:                 /* R/W block */ | 
|  | pmbus_receive_block(pmdev, (uint8_t *)s->mfr_model, | 
|  | sizeof(s->mfr_model)); | 
|  | break; | 
|  |  | 
|  | case PMBUS_MFR_REVISION:               /* R/W block*/ | 
|  | pmbus_receive_block(pmdev, (uint8_t *)s->mfr_rev, sizeof(s->mfr_rev)); | 
|  | break; | 
|  |  | 
|  | case ADM1266_SET_RTC:   /* do nothing */ | 
|  | break; | 
|  |  | 
|  | default: | 
|  | qemu_log_mask(LOG_UNIMP, | 
|  | "%s: writing to unimplemented register: 0x%02x\n", | 
|  | __func__, pmdev->code); | 
|  | break; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void adm1266_get(Object *obj, Visitor *v, const char *name, void *opaque, | 
|  | Error **errp) | 
|  | { | 
|  | uint16_t value; | 
|  | PMBusDevice *pmdev = PMBUS_DEVICE(obj); | 
|  | PMBusVoutMode *mode = (PMBusVoutMode *)&pmdev->pages[0].vout_mode; | 
|  |  | 
|  | if (strcmp(name, "vout") == 0) { | 
|  | value = pmbus_linear_mode2data(*(uint16_t *)opaque, mode->exp); | 
|  | } else { | 
|  | value = *(uint16_t *)opaque; | 
|  | } | 
|  |  | 
|  | visit_type_uint16(v, name, &value, errp); | 
|  | } | 
|  |  | 
|  | static void adm1266_set(Object *obj, Visitor *v, const char *name, void *opaque, | 
|  | Error **errp) | 
|  | { | 
|  | uint16_t *internal = opaque; | 
|  | uint16_t value; | 
|  | PMBusDevice *pmdev = PMBUS_DEVICE(obj); | 
|  | PMBusVoutMode *mode = (PMBusVoutMode *)&pmdev->pages[0].vout_mode; | 
|  |  | 
|  | if (!visit_type_uint16(v, name, &value, errp)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | *internal = pmbus_data2linear_mode(value, mode->exp); | 
|  | pmbus_check_limits(pmdev); | 
|  | } | 
|  |  | 
|  | static const VMStateDescription vmstate_adm1266 = { | 
|  | .name = "ADM1266", | 
|  | .version_id = 0, | 
|  | .minimum_version_id = 0, | 
|  | .fields = (const VMStateField[]){ | 
|  | VMSTATE_PMBUS_DEVICE(parent, ADM1266State), | 
|  | VMSTATE_END_OF_LIST() | 
|  | } | 
|  | }; | 
|  |  | 
|  | static void adm1266_init(Object *obj) | 
|  | { | 
|  | PMBusDevice *pmdev = PMBUS_DEVICE(obj); | 
|  | uint64_t flags = PB_HAS_VOUT_MODE | PB_HAS_VOUT | PB_HAS_VOUT_MARGIN | | 
|  | PB_HAS_VOUT_RATING | PB_HAS_STATUS_MFR_SPECIFIC; | 
|  |  | 
|  | for (int i = 0; i < ADM1266_NUM_PAGES; i++) { | 
|  | pmbus_page_config(pmdev, i, flags); | 
|  |  | 
|  | object_property_add(obj, "vout[*]", "uint16", | 
|  | adm1266_get, | 
|  | adm1266_set, NULL, &pmdev->pages[i].read_vout); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void adm1266_class_init(ObjectClass *klass, void *data) | 
|  | { | 
|  | ResettableClass *rc = RESETTABLE_CLASS(klass); | 
|  | DeviceClass *dc = DEVICE_CLASS(klass); | 
|  | PMBusDeviceClass *k = PMBUS_DEVICE_CLASS(klass); | 
|  |  | 
|  | dc->desc = "Analog Devices ADM1266 Hot Swap controller"; | 
|  | dc->vmsd = &vmstate_adm1266; | 
|  | k->write_data = adm1266_write_data; | 
|  | k->receive_byte = adm1266_read_byte; | 
|  | k->device_num_pages = 17; | 
|  |  | 
|  | rc->phases.exit = adm1266_exit_reset; | 
|  | } | 
|  |  | 
|  | static const TypeInfo adm1266_info = { | 
|  | .name = TYPE_ADM1266, | 
|  | .parent = TYPE_PMBUS_DEVICE, | 
|  | .instance_size = sizeof(ADM1266State), | 
|  | .instance_init = adm1266_init, | 
|  | .class_init = adm1266_class_init, | 
|  | }; | 
|  |  | 
|  | static void adm1266_register_types(void) | 
|  | { | 
|  | type_register_static(&adm1266_info); | 
|  | } | 
|  |  | 
|  | type_init(adm1266_register_types) |