-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The ADM1266 is a cascadable super sequencer with margin control and fault recording. This commit adds basic support for its PMBus commands and models the identification registers that can be modified in a firmware update. Reviewed-by: Hao Wu <wuhaotsh@google.com> Acked-by: Corey Minyard <cminyard@mvista.com> Signed-off-by: Titus Rwantare <titusr@google.com> [PMD: Cover file in MAINTAINERS] Message-ID: <20231023-staging-pmbus-v3-v4-5-07a8cb7cd20a@google.com> Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org>
- Loading branch information
Showing
5 changed files
with
262 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,254 @@ | ||
/* | ||
* 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) | ||
{ | ||
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 = (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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters