| 1 | /* |
| 2 | * PMBus device for Renesas Digital Multiphase Voltage Regulators |
| 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 "hw/sensor/isl_pmbus_vr.h" |
| 11 | #include "hw/core/qdev-properties.h" |
| 12 | #include "qapi/visitor.h" |
| 13 | #include "qemu/log.h" |
| 14 | #include "qemu/module.h" |
| 15 | |
| 16 | static uint8_t isl_pmbus_vr_read_byte(PMBusDevice *pmdev) |
| 17 | { |
| 18 | ISLState *s = ISL69260(pmdev); |
| 19 | |
| 20 | switch (pmdev->code) { |
| 21 | case PMBUS_IC_DEVICE_ID: |
| 22 | if (!s->ic_device_id_len) { |
| 23 | break; |
| 24 | } |
| 25 | pmbus_send(pmdev, s->ic_device_id, s->ic_device_id_len); |
| 26 | pmbus_idle(pmdev); |
| 27 | return 0; |
| 28 | } |
| 29 | |
| 30 | qemu_log_mask(LOG_GUEST_ERROR, |
| 31 | "%s: reading from unsupported register: 0x%02x\n", |
| 32 | __func__, pmdev->code); |
| 33 | return PMBUS_ERR_BYTE; |
| 34 | } |
| 35 | |
| 36 | static int isl_pmbus_vr_write_data(PMBusDevice *pmdev, const uint8_t *buf, |
| 37 | uint8_t len) |
| 38 | { |
| 39 | qemu_log_mask(LOG_GUEST_ERROR, |
| 40 | "%s: write to unsupported register: 0x%02x\n", |
| 41 | __func__, pmdev->code); |
| 42 | return PMBUS_ERR_BYTE; |
| 43 | } |
| 44 | |
| 45 | /* TODO: Implement coefficients support in pmbus_device.c for qmp */ |
| 46 | static void isl_pmbus_vr_get(Object *obj, Visitor *v, const char *name, |
| 47 | void *opaque, Error **errp) |
| 48 | { |
| 49 | visit_type_uint16(v, name, (uint16_t *)opaque, errp); |
| 50 | } |
| 51 | |
| 52 | static void isl_pmbus_vr_set(Object *obj, Visitor *v, const char *name, |
| 53 | void *opaque, Error **errp) |
| 54 | { |
| 55 | PMBusDevice *pmdev = PMBUS_DEVICE(obj); |
| 56 | uint16_t *internal = opaque; |
| 57 | uint16_t value; |
| 58 | if (!visit_type_uint16(v, name, &value, errp)) { |
| 59 | return; |
| 60 | } |
| 61 | |
| 62 | *internal = value; |
| 63 | pmbus_check_limits(pmdev); |
| 64 | } |
| 65 | |
| 66 | static void isl_pmbus_vr_exit_reset(Object *obj, ResetType type) |
| 67 | { |
| 68 | PMBusDevice *pmdev = PMBUS_DEVICE(obj); |
| 69 | |
| 70 | pmdev->page = 0; |
| 71 | pmdev->capability = ISL_CAPABILITY_DEFAULT; |
| 72 | for (int i = 0; i < pmdev->num_pages; i++) { |
| 73 | pmdev->pages[i].operation = ISL_OPERATION_DEFAULT; |
| 74 | pmdev->pages[i].on_off_config = ISL_ON_OFF_CONFIG_DEFAULT; |
| 75 | pmdev->pages[i].vout_mode = ISL_VOUT_MODE_DEFAULT; |
| 76 | pmdev->pages[i].vout_command = ISL_VOUT_COMMAND_DEFAULT; |
| 77 | pmdev->pages[i].vout_max = ISL_VOUT_MAX_DEFAULT; |
| 78 | pmdev->pages[i].vout_margin_high = ISL_VOUT_MARGIN_HIGH_DEFAULT; |
| 79 | pmdev->pages[i].vout_margin_low = ISL_VOUT_MARGIN_LOW_DEFAULT; |
| 80 | pmdev->pages[i].vout_transition_rate = ISL_VOUT_TRANSITION_RATE_DEFAULT; |
| 81 | pmdev->pages[i].vout_ov_fault_limit = ISL_VOUT_OV_FAULT_LIMIT_DEFAULT; |
| 82 | pmdev->pages[i].ot_fault_limit = ISL_OT_FAULT_LIMIT_DEFAULT; |
| 83 | pmdev->pages[i].ot_warn_limit = ISL_OT_WARN_LIMIT_DEFAULT; |
| 84 | pmdev->pages[i].vin_ov_warn_limit = ISL_VIN_OV_WARN_LIMIT_DEFAULT; |
| 85 | pmdev->pages[i].vin_uv_warn_limit = ISL_VIN_UV_WARN_LIMIT_DEFAULT; |
| 86 | pmdev->pages[i].iin_oc_fault_limit = ISL_IIN_OC_FAULT_LIMIT_DEFAULT; |
| 87 | pmdev->pages[i].ton_delay = ISL_TON_DELAY_DEFAULT; |
| 88 | pmdev->pages[i].ton_rise = ISL_TON_RISE_DEFAULT; |
| 89 | pmdev->pages[i].toff_fall = ISL_TOFF_FALL_DEFAULT; |
| 90 | pmdev->pages[i].revision = ISL_REVISION_DEFAULT; |
| 91 | |
| 92 | pmdev->pages[i].read_vout = ISL_READ_VOUT_DEFAULT; |
| 93 | pmdev->pages[i].read_iout = ISL_READ_IOUT_DEFAULT; |
| 94 | pmdev->pages[i].read_pout = ISL_READ_POUT_DEFAULT; |
| 95 | pmdev->pages[i].read_vin = ISL_READ_VIN_DEFAULT; |
| 96 | pmdev->pages[i].read_iin = ISL_READ_IIN_DEFAULT; |
| 97 | pmdev->pages[i].read_pin = ISL_READ_PIN_DEFAULT; |
| 98 | pmdev->pages[i].read_temperature_1 = ISL_READ_TEMP_DEFAULT; |
| 99 | pmdev->pages[i].read_temperature_2 = ISL_READ_TEMP_DEFAULT; |
| 100 | pmdev->pages[i].read_temperature_3 = ISL_READ_TEMP_DEFAULT; |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | /* The raa228000 uses different direct mode coefficients from most isl devices */ |
| 105 | static void raa228000_exit_reset(Object *obj, ResetType type) |
| 106 | { |
| 107 | PMBusDevice *pmdev = PMBUS_DEVICE(obj); |
| 108 | |
| 109 | isl_pmbus_vr_exit_reset(obj, type); |
| 110 | |
| 111 | pmdev->pages[0].read_iout = 0; |
| 112 | pmdev->pages[0].read_pout = 0; |
| 113 | pmdev->pages[0].read_vout = 0; |
| 114 | pmdev->pages[0].read_vin = 0; |
| 115 | pmdev->pages[0].read_iin = 0; |
| 116 | pmdev->pages[0].read_pin = 0; |
| 117 | pmdev->pages[0].read_temperature_1 = 0; |
| 118 | pmdev->pages[0].read_temperature_2 = 0; |
| 119 | pmdev->pages[0].read_temperature_3 = 0; |
| 120 | } |
| 121 | |
| 122 | static void isl69259_exit_reset(Object *obj, ResetType type) |
| 123 | { |
| 124 | ISLState *s = ISL69260(obj); |
| 125 | static const uint8_t ic_device_id[] = {0x04, 0x00, 0x81, 0xD2, 0x49, 0x3c}; |
| 126 | g_assert(sizeof(ic_device_id) <= sizeof(s->ic_device_id)); |
| 127 | |
| 128 | isl_pmbus_vr_exit_reset(obj, type); |
| 129 | |
| 130 | s->ic_device_id_len = sizeof(ic_device_id); |
| 131 | memcpy(s->ic_device_id, ic_device_id, sizeof(ic_device_id)); |
| 132 | } |
| 133 | |
| 134 | static void isl_pmbus_vr_add_props(Object *obj, uint64_t *flags, uint8_t pages) |
| 135 | { |
| 136 | PMBusDevice *pmdev = PMBUS_DEVICE(obj); |
| 137 | for (int i = 0; i < pages; i++) { |
| 138 | if (flags[i] & PB_HAS_VIN) { |
| 139 | object_property_add(obj, "vin[*]", "uint16", |
| 140 | isl_pmbus_vr_get, |
| 141 | isl_pmbus_vr_set, |
| 142 | NULL, &pmdev->pages[i].read_vin); |
| 143 | } |
| 144 | |
| 145 | if (flags[i] & PB_HAS_VOUT) { |
| 146 | object_property_add(obj, "vout[*]", "uint16", |
| 147 | isl_pmbus_vr_get, |
| 148 | isl_pmbus_vr_set, |
| 149 | NULL, &pmdev->pages[i].read_vout); |
| 150 | } |
| 151 | |
| 152 | if (flags[i] & PB_HAS_IIN) { |
| 153 | object_property_add(obj, "iin[*]", "uint16", |
| 154 | isl_pmbus_vr_get, |
| 155 | isl_pmbus_vr_set, |
| 156 | NULL, &pmdev->pages[i].read_iin); |
| 157 | } |
| 158 | |
| 159 | if (flags[i] & PB_HAS_IOUT) { |
| 160 | object_property_add(obj, "iout[*]", "uint16", |
| 161 | isl_pmbus_vr_get, |
| 162 | isl_pmbus_vr_set, |
| 163 | NULL, &pmdev->pages[i].read_iout); |
| 164 | } |
| 165 | |
| 166 | if (flags[i] & PB_HAS_PIN) { |
| 167 | object_property_add(obj, "pin[*]", "uint16", |
| 168 | isl_pmbus_vr_get, |
| 169 | isl_pmbus_vr_set, |
| 170 | NULL, &pmdev->pages[i].read_pin); |
| 171 | } |
| 172 | |
| 173 | if (flags[i] & PB_HAS_POUT) { |
| 174 | object_property_add(obj, "pout[*]", "uint16", |
| 175 | isl_pmbus_vr_get, |
| 176 | isl_pmbus_vr_set, |
| 177 | NULL, &pmdev->pages[i].read_pout); |
| 178 | } |
| 179 | |
| 180 | if (flags[i] & PB_HAS_TEMPERATURE) { |
| 181 | object_property_add(obj, "temp1[*]", "uint16", |
| 182 | isl_pmbus_vr_get, |
| 183 | isl_pmbus_vr_set, |
| 184 | NULL, &pmdev->pages[i].read_temperature_1); |
| 185 | } |
| 186 | |
| 187 | if (flags[i] & PB_HAS_TEMP2) { |
| 188 | object_property_add(obj, "temp2[*]", "uint16", |
| 189 | isl_pmbus_vr_get, |
| 190 | isl_pmbus_vr_set, |
| 191 | NULL, &pmdev->pages[i].read_temperature_2); |
| 192 | } |
| 193 | |
| 194 | if (flags[i] & PB_HAS_TEMP3) { |
| 195 | object_property_add(obj, "temp3[*]", "uint16", |
| 196 | isl_pmbus_vr_get, |
| 197 | isl_pmbus_vr_set, |
| 198 | NULL, &pmdev->pages[i].read_temperature_3); |
| 199 | } |
| 200 | } |
| 201 | } |
| 202 | |
| 203 | static void raa22xx_init(Object *obj) |
| 204 | { |
| 205 | PMBusDevice *pmdev = PMBUS_DEVICE(obj); |
| 206 | uint64_t flags[2]; |
| 207 | |
| 208 | flags[0] = PB_HAS_VIN | PB_HAS_VOUT | PB_HAS_VOUT_MODE | |
| 209 | PB_HAS_VOUT_RATING | PB_HAS_VOUT_MARGIN | PB_HAS_IIN | |
| 210 | PB_HAS_IOUT | PB_HAS_PIN | PB_HAS_POUT | PB_HAS_TEMPERATURE | |
| 211 | PB_HAS_TEMP2 | PB_HAS_TEMP3 | PB_HAS_STATUS_MFR_SPECIFIC; |
| 212 | flags[1] = PB_HAS_IIN | PB_HAS_PIN | PB_HAS_TEMPERATURE | PB_HAS_TEMP3 | |
| 213 | PB_HAS_VOUT | PB_HAS_VOUT_MODE | PB_HAS_VOUT_MARGIN | |
| 214 | PB_HAS_VOUT_RATING | PB_HAS_IOUT | PB_HAS_POUT | |
| 215 | PB_HAS_STATUS_MFR_SPECIFIC; |
| 216 | |
| 217 | pmbus_page_config(pmdev, 0, flags[0]); |
| 218 | pmbus_page_config(pmdev, 1, flags[1]); |
| 219 | isl_pmbus_vr_add_props(obj, flags, ARRAY_SIZE(flags)); |
| 220 | } |
| 221 | |
| 222 | static void raa228000_init(Object *obj) |
| 223 | { |
| 224 | PMBusDevice *pmdev = PMBUS_DEVICE(obj); |
| 225 | uint64_t flags[1]; |
| 226 | |
| 227 | flags[0] = PB_HAS_VIN | PB_HAS_VOUT | PB_HAS_VOUT_MODE | |
| 228 | PB_HAS_VOUT_RATING | PB_HAS_VOUT_MARGIN | PB_HAS_IIN | |
| 229 | PB_HAS_IOUT | PB_HAS_PIN | PB_HAS_POUT | PB_HAS_TEMPERATURE | |
| 230 | PB_HAS_TEMP2 | PB_HAS_TEMP3 | PB_HAS_STATUS_MFR_SPECIFIC; |
| 231 | |
| 232 | pmbus_page_config(pmdev, 0, flags[0]); |
| 233 | isl_pmbus_vr_add_props(obj, flags, 1); |
| 234 | } |
| 235 | |
| 236 | static void isl_pmbus_vr_class_init(ObjectClass *klass, const void *data, |
| 237 | uint8_t pages) |
| 238 | { |
| 239 | PMBusDeviceClass *k = PMBUS_DEVICE_CLASS(klass); |
| 240 | k->write_data = isl_pmbus_vr_write_data; |
| 241 | k->receive_byte = isl_pmbus_vr_read_byte; |
| 242 | k->device_num_pages = pages; |
| 243 | } |
| 244 | |
| 245 | static void isl69260_class_init(ObjectClass *klass, const void *data) |
| 246 | { |
| 247 | ResettableClass *rc = RESETTABLE_CLASS(klass); |
| 248 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 249 | dc->desc = "Renesas ISL69260 Digital Multiphase Voltage Regulator"; |
| 250 | rc->phases.exit = isl_pmbus_vr_exit_reset; |
| 251 | isl_pmbus_vr_class_init(klass, data, 2); |
| 252 | } |
| 253 | |
| 254 | static void raa228000_class_init(ObjectClass *klass, const void *data) |
| 255 | { |
| 256 | ResettableClass *rc = RESETTABLE_CLASS(klass); |
| 257 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 258 | dc->desc = "Renesas 228000 Digital Multiphase Voltage Regulator"; |
| 259 | rc->phases.exit = raa228000_exit_reset; |
| 260 | isl_pmbus_vr_class_init(klass, data, 1); |
| 261 | } |
| 262 | |
| 263 | static void raa229004_class_init(ObjectClass *klass, const void *data) |
| 264 | { |
| 265 | ResettableClass *rc = RESETTABLE_CLASS(klass); |
| 266 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 267 | dc->desc = "Renesas 229004 Digital Multiphase Voltage Regulator"; |
| 268 | rc->phases.exit = isl_pmbus_vr_exit_reset; |
| 269 | isl_pmbus_vr_class_init(klass, data, 2); |
| 270 | } |
| 271 | |
| 272 | static void isl69259_class_init(ObjectClass *klass, const void *data) |
| 273 | { |
| 274 | ResettableClass *rc = RESETTABLE_CLASS(klass); |
| 275 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 276 | dc->desc = "Renesas ISL69259 Digital Multiphase Voltage Regulator"; |
| 277 | rc->phases.exit = isl69259_exit_reset; |
| 278 | isl_pmbus_vr_class_init(klass, data, 2); |
| 279 | } |
| 280 | |
| 281 | static const TypeInfo isl69259_info = { |
| 282 | .name = TYPE_ISL69259, |
| 283 | .parent = TYPE_ISL69260, |
| 284 | .class_init = isl69259_class_init, |
| 285 | }; |
| 286 | |
| 287 | static const TypeInfo isl69260_info = { |
| 288 | .name = TYPE_ISL69260, |
| 289 | .parent = TYPE_PMBUS_DEVICE, |
| 290 | .instance_size = sizeof(ISLState), |
| 291 | .instance_init = raa22xx_init, |
| 292 | .class_init = isl69260_class_init, |
| 293 | }; |
| 294 | |
| 295 | static const TypeInfo raa229004_info = { |
| 296 | .name = TYPE_RAA229004, |
| 297 | .parent = TYPE_PMBUS_DEVICE, |
| 298 | .instance_size = sizeof(ISLState), |
| 299 | .instance_init = raa22xx_init, |
| 300 | .class_init = raa229004_class_init, |
| 301 | }; |
| 302 | |
| 303 | static const TypeInfo raa228000_info = { |
| 304 | .name = TYPE_RAA228000, |
| 305 | .parent = TYPE_PMBUS_DEVICE, |
| 306 | .instance_size = sizeof(ISLState), |
| 307 | .instance_init = raa228000_init, |
| 308 | .class_init = raa228000_class_init, |
| 309 | }; |
| 310 | |
| 311 | static void isl_pmbus_vr_register_types(void) |
| 312 | { |
| 313 | type_register_static(&isl69259_info); |
| 314 | type_register_static(&isl69260_info); |
| 315 | type_register_static(&raa228000_info); |
| 316 | type_register_static(&raa229004_info); |
| 317 | } |
| 318 | |
| 319 | type_init(isl_pmbus_vr_register_types) |