| 1 | /* |
| 2 | * Aspeed ADC |
| 3 | * |
| 4 | * Copyright 2017-2021 IBM Corp. |
| 5 | * |
| 6 | * Andrew Jeffery <andrew@aj.id.au> |
| 7 | * |
| 8 | * SPDX-License-Identifier: GPL-2.0-or-later |
| 9 | */ |
| 10 | |
| 11 | #include "qemu/osdep.h" |
| 12 | #include "qapi/error.h" |
| 13 | #include "qemu/log.h" |
| 14 | #include "hw/core/irq.h" |
| 15 | #include "hw/core/qdev-properties.h" |
| 16 | #include "migration/vmstate.h" |
| 17 | #include "hw/adc/aspeed_adc.h" |
| 18 | #include "trace.h" |
| 19 | |
| 20 | #define ASPEED_ADC_MEMORY_REGION_SIZE 0x1000 |
| 21 | #define ASPEED_ADC_ENGINE_MEMORY_REGION_SIZE 0x100 |
| 22 | #define ASPEED_ADC_ENGINE_CH_EN_MASK 0xffff0000 |
| 23 | #define ASPEED_ADC_ENGINE_CH_EN(x) ((BIT(x)) << 16) |
| 24 | #define ASPEED_ADC_ENGINE_INIT BIT(8) |
| 25 | #define ASPEED_ADC_ENGINE_AUTO_COMP BIT(5) |
| 26 | #define ASPEED_ADC_ENGINE_COMP BIT(4) |
| 27 | #define ASPEED_ADC_ENGINE_MODE_MASK 0x0000000e |
| 28 | #define ASPEED_ADC_ENGINE_MODE_OFF (0b000 << 1) |
| 29 | #define ASPEED_ADC_ENGINE_MODE_STANDBY (0b001 << 1) |
| 30 | #define ASPEED_ADC_ENGINE_MODE_NORMAL (0b111 << 1) |
| 31 | #define ASPEED_ADC_ENGINE_EN BIT(0) |
| 32 | #define ASPEED_ADC_HYST_EN BIT(31) |
| 33 | |
| 34 | #define ASPEED_ADC_L_MASK ((1 << 10) - 1) |
| 35 | #define ASPEED_ADC_L(x) ((x) & ASPEED_ADC_L_MASK) |
| 36 | #define ASPEED_ADC_H(x) (((x) >> 16) & ASPEED_ADC_L_MASK) |
| 37 | #define ASPEED_ADC_LH_MASK (ASPEED_ADC_L_MASK << 16 | ASPEED_ADC_L_MASK) |
| 38 | #define LOWER_CHANNEL_MASK ((1 << 10) - 1) |
| 39 | #define LOWER_CHANNEL_DATA(x) ((x) & LOWER_CHANNEL_MASK) |
| 40 | #define UPPER_CHANNEL_DATA(x) (((x) >> 16) & LOWER_CHANNEL_MASK) |
| 41 | |
| 42 | #define TO_REG(addr) (addr >> 2) |
| 43 | |
| 44 | #define ENGINE_CONTROL TO_REG(0x00) |
| 45 | #define INTERRUPT_CONTROL TO_REG(0x04) |
| 46 | #define VGA_DETECT_CONTROL TO_REG(0x08) |
| 47 | #define CLOCK_CONTROL TO_REG(0x0C) |
| 48 | #define DATA_CHANNEL_1_AND_0 TO_REG(0x10) |
| 49 | #define DATA_CHANNEL_7_AND_6 TO_REG(0x1C) |
| 50 | #define DATA_CHANNEL_9_AND_8 TO_REG(0x20) |
| 51 | #define DATA_CHANNEL_15_AND_14 TO_REG(0x2C) |
| 52 | #define BOUNDS_CHANNEL_0 TO_REG(0x30) |
| 53 | #define BOUNDS_CHANNEL_7 TO_REG(0x4C) |
| 54 | #define BOUNDS_CHANNEL_8 TO_REG(0x50) |
| 55 | #define BOUNDS_CHANNEL_15 TO_REG(0x6C) |
| 56 | #define HYSTERESIS_CHANNEL_0 TO_REG(0x70) |
| 57 | #define HYSTERESIS_CHANNEL_7 TO_REG(0x8C) |
| 58 | #define HYSTERESIS_CHANNEL_8 TO_REG(0x90) |
| 59 | #define HYSTERESIS_CHANNEL_15 TO_REG(0xAC) |
| 60 | #define INTERRUPT_SOURCE TO_REG(0xC0) |
| 61 | #define COMPENSATING_AND_TRIMMING TO_REG(0xC4) |
| 62 | |
| 63 | static inline uint32_t update_channels(uint32_t current) |
| 64 | { |
| 65 | return ((((current >> 16) & ASPEED_ADC_L_MASK) + 7) << 16) | |
| 66 | ((current + 5) & ASPEED_ADC_L_MASK); |
| 67 | } |
| 68 | |
| 69 | static bool breaks_threshold(AspeedADCEngineState *s, int reg) |
| 70 | { |
| 71 | assert(reg >= DATA_CHANNEL_1_AND_0 && |
| 72 | reg < DATA_CHANNEL_1_AND_0 + s->nr_channels / 2); |
| 73 | |
| 74 | int a_bounds_reg = BOUNDS_CHANNEL_0 + (reg - DATA_CHANNEL_1_AND_0) * 2; |
| 75 | int b_bounds_reg = a_bounds_reg + 1; |
| 76 | uint32_t a_and_b = s->regs[reg]; |
| 77 | uint32_t a_bounds = s->regs[a_bounds_reg]; |
| 78 | uint32_t b_bounds = s->regs[b_bounds_reg]; |
| 79 | uint32_t a = ASPEED_ADC_L(a_and_b); |
| 80 | uint32_t b = ASPEED_ADC_H(a_and_b); |
| 81 | uint32_t a_lower = ASPEED_ADC_L(a_bounds); |
| 82 | uint32_t a_upper = ASPEED_ADC_H(a_bounds); |
| 83 | uint32_t b_lower = ASPEED_ADC_L(b_bounds); |
| 84 | uint32_t b_upper = ASPEED_ADC_H(b_bounds); |
| 85 | |
| 86 | return (a < a_lower || a > a_upper) || |
| 87 | (b < b_lower || b > b_upper); |
| 88 | } |
| 89 | |
| 90 | static uint32_t read_channel_sample(AspeedADCEngineState *s, int reg) |
| 91 | { |
| 92 | assert(reg >= DATA_CHANNEL_1_AND_0 && |
| 93 | reg < DATA_CHANNEL_1_AND_0 + s->nr_channels / 2); |
| 94 | |
| 95 | /* Poor man's sampling */ |
| 96 | uint32_t value = s->regs[reg]; |
| 97 | s->regs[reg] = update_channels(s->regs[reg]); |
| 98 | |
| 99 | if (breaks_threshold(s, reg)) { |
| 100 | s->regs[INTERRUPT_CONTROL] |= BIT(reg - DATA_CHANNEL_1_AND_0); |
| 101 | qemu_irq_raise(s->irq); |
| 102 | } |
| 103 | |
| 104 | return value; |
| 105 | } |
| 106 | |
| 107 | static uint64_t aspeed_adc_engine_read(void *opaque, hwaddr addr, |
| 108 | unsigned int size) |
| 109 | { |
| 110 | AspeedADCEngineState *s = ASPEED_ADC_ENGINE(opaque); |
| 111 | int reg = TO_REG(addr); |
| 112 | uint32_t value = 0; |
| 113 | |
| 114 | switch (reg) { |
| 115 | case BOUNDS_CHANNEL_8 ... BOUNDS_CHANNEL_15: |
| 116 | if (s->nr_channels <= 8) { |
| 117 | qemu_log_mask(LOG_GUEST_ERROR, "%s: engine[%u]: " |
| 118 | "bounds register %u invalid, only 0...7 valid\n", |
| 119 | __func__, s->engine_id, reg - BOUNDS_CHANNEL_0); |
| 120 | break; |
| 121 | } |
| 122 | /* fallthrough */ |
| 123 | case HYSTERESIS_CHANNEL_8 ... HYSTERESIS_CHANNEL_15: |
| 124 | if (s->nr_channels <= 8) { |
| 125 | qemu_log_mask(LOG_GUEST_ERROR, "%s: engine[%u]: " |
| 126 | "hysteresis register %u invalid, only 0...7 valid\n", |
| 127 | __func__, s->engine_id, reg - HYSTERESIS_CHANNEL_0); |
| 128 | break; |
| 129 | } |
| 130 | /* fallthrough */ |
| 131 | case BOUNDS_CHANNEL_0 ... BOUNDS_CHANNEL_7: |
| 132 | case HYSTERESIS_CHANNEL_0 ... HYSTERESIS_CHANNEL_7: |
| 133 | case ENGINE_CONTROL: |
| 134 | case INTERRUPT_CONTROL: |
| 135 | case VGA_DETECT_CONTROL: |
| 136 | case CLOCK_CONTROL: |
| 137 | case INTERRUPT_SOURCE: |
| 138 | case COMPENSATING_AND_TRIMMING: |
| 139 | value = s->regs[reg]; |
| 140 | break; |
| 141 | case DATA_CHANNEL_9_AND_8 ... DATA_CHANNEL_15_AND_14: |
| 142 | if (s->nr_channels <= 8) { |
| 143 | qemu_log_mask(LOG_GUEST_ERROR, "%s: engine[%u]: " |
| 144 | "data register %u invalid, only 0...3 valid\n", |
| 145 | __func__, s->engine_id, reg - DATA_CHANNEL_1_AND_0); |
| 146 | break; |
| 147 | } |
| 148 | /* fallthrough */ |
| 149 | case DATA_CHANNEL_1_AND_0 ... DATA_CHANNEL_7_AND_6: |
| 150 | value = read_channel_sample(s, reg); |
| 151 | /* Allow 16-bit reads of the data registers */ |
| 152 | if (addr & 0x2) { |
| 153 | assert(size == 2); |
| 154 | value >>= 16; |
| 155 | } |
| 156 | break; |
| 157 | default: |
| 158 | qemu_log_mask(LOG_UNIMP, "%s: engine[%u]: 0x%" HWADDR_PRIx "\n", |
| 159 | __func__, s->engine_id, addr); |
| 160 | break; |
| 161 | } |
| 162 | |
| 163 | trace_aspeed_adc_engine_read(s->engine_id, addr, value); |
| 164 | return value; |
| 165 | } |
| 166 | |
| 167 | static void aspeed_adc_engine_write(void *opaque, hwaddr addr, uint64_t value, |
| 168 | unsigned int size) |
| 169 | { |
| 170 | AspeedADCEngineState *s = ASPEED_ADC_ENGINE(opaque); |
| 171 | int reg = TO_REG(addr); |
| 172 | uint32_t init = 0; |
| 173 | |
| 174 | trace_aspeed_adc_engine_write(s->engine_id, addr, value); |
| 175 | |
| 176 | switch (reg) { |
| 177 | case ENGINE_CONTROL: |
| 178 | init = !!(value & ASPEED_ADC_ENGINE_EN); |
| 179 | init *= ASPEED_ADC_ENGINE_INIT; |
| 180 | |
| 181 | value &= ~ASPEED_ADC_ENGINE_INIT; |
| 182 | value |= init; |
| 183 | |
| 184 | value &= ~ASPEED_ADC_ENGINE_AUTO_COMP; |
| 185 | break; |
| 186 | case INTERRUPT_CONTROL: |
| 187 | case VGA_DETECT_CONTROL: |
| 188 | case CLOCK_CONTROL: |
| 189 | break; |
| 190 | case DATA_CHANNEL_9_AND_8 ... DATA_CHANNEL_15_AND_14: |
| 191 | if (s->nr_channels <= 8) { |
| 192 | qemu_log_mask(LOG_GUEST_ERROR, "%s: engine[%u]: " |
| 193 | "data register %u invalid, only 0...3 valid\n", |
| 194 | __func__, s->engine_id, reg - DATA_CHANNEL_1_AND_0); |
| 195 | return; |
| 196 | } |
| 197 | /* fallthrough */ |
| 198 | case BOUNDS_CHANNEL_8 ... BOUNDS_CHANNEL_15: |
| 199 | if (s->nr_channels <= 8) { |
| 200 | qemu_log_mask(LOG_GUEST_ERROR, "%s: engine[%u]: " |
| 201 | "bounds register %u invalid, only 0...7 valid\n", |
| 202 | __func__, s->engine_id, reg - BOUNDS_CHANNEL_0); |
| 203 | return; |
| 204 | } |
| 205 | /* fallthrough */ |
| 206 | case DATA_CHANNEL_1_AND_0 ... DATA_CHANNEL_7_AND_6: |
| 207 | case BOUNDS_CHANNEL_0 ... BOUNDS_CHANNEL_7: |
| 208 | value &= ASPEED_ADC_LH_MASK; |
| 209 | break; |
| 210 | case HYSTERESIS_CHANNEL_8 ... HYSTERESIS_CHANNEL_15: |
| 211 | if (s->nr_channels <= 8) { |
| 212 | qemu_log_mask(LOG_GUEST_ERROR, "%s: engine[%u]: " |
| 213 | "hysteresis register %u invalid, only 0...7 valid\n", |
| 214 | __func__, s->engine_id, reg - HYSTERESIS_CHANNEL_0); |
| 215 | return; |
| 216 | } |
| 217 | /* fallthrough */ |
| 218 | case HYSTERESIS_CHANNEL_0 ... HYSTERESIS_CHANNEL_7: |
| 219 | value &= (ASPEED_ADC_HYST_EN | ASPEED_ADC_LH_MASK); |
| 220 | break; |
| 221 | case INTERRUPT_SOURCE: |
| 222 | value &= 0xffff; |
| 223 | break; |
| 224 | case COMPENSATING_AND_TRIMMING: |
| 225 | value &= 0xf; |
| 226 | break; |
| 227 | default: |
| 228 | qemu_log_mask(LOG_UNIMP, "%s: engine[%u]: " |
| 229 | "0x%" HWADDR_PRIx " 0x%" PRIx64 "\n", |
| 230 | __func__, s->engine_id, addr, value); |
| 231 | /* Do not update the regs[] array */ |
| 232 | return; |
| 233 | } |
| 234 | |
| 235 | s->regs[reg] = value; |
| 236 | } |
| 237 | |
| 238 | static const MemoryRegionOps aspeed_adc_engine_ops = { |
| 239 | .read = aspeed_adc_engine_read, |
| 240 | .write = aspeed_adc_engine_write, |
| 241 | .endianness = DEVICE_LITTLE_ENDIAN, |
| 242 | .valid = { |
| 243 | .min_access_size = 2, |
| 244 | .max_access_size = 4, |
| 245 | .unaligned = false, |
| 246 | }, |
| 247 | }; |
| 248 | |
| 249 | static const uint32_t aspeed_adc_resets[ASPEED_ADC_NR_REGS] = { |
| 250 | [ENGINE_CONTROL] = 0x00000000, |
| 251 | [INTERRUPT_CONTROL] = 0x00000000, |
| 252 | [VGA_DETECT_CONTROL] = 0x0000000f, |
| 253 | [CLOCK_CONTROL] = 0x0000000f, |
| 254 | }; |
| 255 | |
| 256 | static void aspeed_adc_engine_reset_hold(Object *obj, ResetType type) |
| 257 | { |
| 258 | AspeedADCEngineState *s = ASPEED_ADC_ENGINE(obj); |
| 259 | |
| 260 | memcpy(s->regs, aspeed_adc_resets, sizeof(aspeed_adc_resets)); |
| 261 | } |
| 262 | |
| 263 | static void aspeed_adc_engine_realize(DeviceState *dev, Error **errp) |
| 264 | { |
| 265 | AspeedADCEngineState *s = ASPEED_ADC_ENGINE(dev); |
| 266 | SysBusDevice *sbd = SYS_BUS_DEVICE(dev); |
| 267 | g_autofree char *name = g_strdup_printf(TYPE_ASPEED_ADC_ENGINE ".%d", |
| 268 | s->engine_id); |
| 269 | |
| 270 | assert(s->engine_id < 2); |
| 271 | |
| 272 | sysbus_init_irq(sbd, &s->irq); |
| 273 | |
| 274 | memory_region_init_io(&s->mmio, OBJECT(s), &aspeed_adc_engine_ops, s, name, |
| 275 | ASPEED_ADC_ENGINE_MEMORY_REGION_SIZE); |
| 276 | |
| 277 | sysbus_init_mmio(sbd, &s->mmio); |
| 278 | } |
| 279 | |
| 280 | static const VMStateDescription vmstate_aspeed_adc_engine = { |
| 281 | .name = TYPE_ASPEED_ADC, |
| 282 | .version_id = 1, |
| 283 | .minimum_version_id = 1, |
| 284 | .fields = (const VMStateField[]) { |
| 285 | VMSTATE_UINT32_ARRAY(regs, AspeedADCEngineState, ASPEED_ADC_NR_REGS), |
| 286 | VMSTATE_END_OF_LIST(), |
| 287 | } |
| 288 | }; |
| 289 | |
| 290 | static const Property aspeed_adc_engine_properties[] = { |
| 291 | DEFINE_PROP_UINT32("engine-id", AspeedADCEngineState, engine_id, 0), |
| 292 | DEFINE_PROP_UINT32("nr-channels", AspeedADCEngineState, nr_channels, 0), |
| 293 | }; |
| 294 | |
| 295 | static void aspeed_adc_engine_class_init(ObjectClass *klass, const void *data) |
| 296 | { |
| 297 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 298 | ResettableClass *rc = RESETTABLE_CLASS(klass); |
| 299 | |
| 300 | dc->realize = aspeed_adc_engine_realize; |
| 301 | rc->phases.hold = aspeed_adc_engine_reset_hold; |
| 302 | device_class_set_props(dc, aspeed_adc_engine_properties); |
| 303 | dc->desc = "Aspeed Analog-to-Digital Engine"; |
| 304 | dc->vmsd = &vmstate_aspeed_adc_engine; |
| 305 | } |
| 306 | |
| 307 | static void aspeed_adc_instance_init(Object *obj) |
| 308 | { |
| 309 | AspeedADCState *s = ASPEED_ADC(obj); |
| 310 | AspeedADCClass *aac = ASPEED_ADC_GET_CLASS(obj); |
| 311 | uint32_t nr_channels = ASPEED_ADC_NR_CHANNELS / aac->nr_engines; |
| 312 | |
| 313 | for (int i = 0; i < aac->nr_engines; i++) { |
| 314 | AspeedADCEngineState *engine = &s->engines[i]; |
| 315 | object_initialize_child(obj, "engine[*]", engine, |
| 316 | TYPE_ASPEED_ADC_ENGINE); |
| 317 | qdev_prop_set_uint32(DEVICE(engine), "engine-id", i); |
| 318 | qdev_prop_set_uint32(DEVICE(engine), "nr-channels", nr_channels); |
| 319 | } |
| 320 | } |
| 321 | |
| 322 | static void aspeed_adc_set_irq(void *opaque, int n, int level) |
| 323 | { |
| 324 | AspeedADCState *s = opaque; |
| 325 | AspeedADCClass *aac = ASPEED_ADC_GET_CLASS(s); |
| 326 | uint32_t pending = 0; |
| 327 | |
| 328 | /* TODO: update Global IRQ status register on AST2600 (Need specs) */ |
| 329 | for (int i = 0; i < aac->nr_engines; i++) { |
| 330 | uint32_t irq_status = s->engines[i].regs[INTERRUPT_CONTROL] & 0xFF; |
| 331 | pending |= irq_status << (i * 8); |
| 332 | } |
| 333 | |
| 334 | qemu_set_irq(s->irq, !!pending); |
| 335 | } |
| 336 | |
| 337 | static void aspeed_adc_realize(DeviceState *dev, Error **errp) |
| 338 | { |
| 339 | AspeedADCState *s = ASPEED_ADC(dev); |
| 340 | SysBusDevice *sbd = SYS_BUS_DEVICE(dev); |
| 341 | AspeedADCClass *aac = ASPEED_ADC_GET_CLASS(dev); |
| 342 | |
| 343 | qdev_init_gpio_in_named_with_opaque(DEVICE(sbd), aspeed_adc_set_irq, |
| 344 | s, NULL, aac->nr_engines); |
| 345 | |
| 346 | sysbus_init_irq(sbd, &s->irq); |
| 347 | |
| 348 | memory_region_init(&s->mmio, OBJECT(s), TYPE_ASPEED_ADC, |
| 349 | ASPEED_ADC_MEMORY_REGION_SIZE); |
| 350 | |
| 351 | sysbus_init_mmio(sbd, &s->mmio); |
| 352 | |
| 353 | for (int i = 0; i < aac->nr_engines; i++) { |
| 354 | Object *eng = OBJECT(&s->engines[i]); |
| 355 | |
| 356 | if (!sysbus_realize(SYS_BUS_DEVICE(eng), errp)) { |
| 357 | return; |
| 358 | } |
| 359 | sysbus_connect_irq(SYS_BUS_DEVICE(eng), 0, |
| 360 | qdev_get_gpio_in(DEVICE(sbd), i)); |
| 361 | memory_region_add_subregion(&s->mmio, |
| 362 | i * ASPEED_ADC_ENGINE_MEMORY_REGION_SIZE, |
| 363 | &s->engines[i].mmio); |
| 364 | } |
| 365 | } |
| 366 | |
| 367 | static void aspeed_adc_class_init(ObjectClass *klass, const void *data) |
| 368 | { |
| 369 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 370 | AspeedADCClass *aac = ASPEED_ADC_CLASS(klass); |
| 371 | |
| 372 | dc->realize = aspeed_adc_realize; |
| 373 | dc->desc = "Aspeed Analog-to-Digital Converter"; |
| 374 | aac->nr_engines = 1; |
| 375 | } |
| 376 | |
| 377 | static void aspeed_2600_adc_class_init(ObjectClass *klass, const void *data) |
| 378 | { |
| 379 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 380 | AspeedADCClass *aac = ASPEED_ADC_CLASS(klass); |
| 381 | |
| 382 | dc->desc = "ASPEED 2600 ADC Controller"; |
| 383 | aac->nr_engines = 2; |
| 384 | } |
| 385 | |
| 386 | static void aspeed_1030_adc_class_init(ObjectClass *klass, const void *data) |
| 387 | { |
| 388 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 389 | AspeedADCClass *aac = ASPEED_ADC_CLASS(klass); |
| 390 | |
| 391 | dc->desc = "ASPEED 1030 ADC Controller"; |
| 392 | aac->nr_engines = 2; |
| 393 | } |
| 394 | |
| 395 | static void aspeed_2700_adc_class_init(ObjectClass *klass, const void *data) |
| 396 | { |
| 397 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 398 | AspeedADCClass *aac = ASPEED_ADC_CLASS(klass); |
| 399 | |
| 400 | dc->desc = "ASPEED 2700 ADC Controller"; |
| 401 | aac->nr_engines = 2; |
| 402 | } |
| 403 | |
| 404 | static const TypeInfo aspeed_adc_types[] = { |
| 405 | { |
| 406 | .name = TYPE_ASPEED_ADC_ENGINE, |
| 407 | .parent = TYPE_SYS_BUS_DEVICE, |
| 408 | .instance_size = sizeof(AspeedADCEngineState), |
| 409 | .class_init = aspeed_adc_engine_class_init, |
| 410 | }, |
| 411 | { |
| 412 | .name = TYPE_ASPEED_ADC, |
| 413 | .parent = TYPE_SYS_BUS_DEVICE, |
| 414 | .instance_init = aspeed_adc_instance_init, |
| 415 | .instance_size = sizeof(AspeedADCState), |
| 416 | .class_init = aspeed_adc_class_init, |
| 417 | .class_size = sizeof(AspeedADCClass), |
| 418 | .abstract = true, |
| 419 | }, |
| 420 | { |
| 421 | .name = TYPE_ASPEED_1030_ADC, |
| 422 | .parent = TYPE_ASPEED_ADC, |
| 423 | .class_init = aspeed_1030_adc_class_init, /* No change since AST2600 */ |
| 424 | }, |
| 425 | { |
| 426 | .name = TYPE_ASPEED_2400_ADC, |
| 427 | .parent = TYPE_ASPEED_ADC, |
| 428 | }, |
| 429 | { |
| 430 | .name = TYPE_ASPEED_2500_ADC, |
| 431 | .parent = TYPE_ASPEED_ADC, |
| 432 | }, |
| 433 | { |
| 434 | .name = TYPE_ASPEED_2600_ADC, |
| 435 | .parent = TYPE_ASPEED_ADC, |
| 436 | .class_init = aspeed_2600_adc_class_init, |
| 437 | }, |
| 438 | { |
| 439 | .name = TYPE_ASPEED_2700_ADC, |
| 440 | .parent = TYPE_ASPEED_ADC, |
| 441 | .class_init = aspeed_2700_adc_class_init, |
| 442 | } |
| 443 | }; |
| 444 | |
| 445 | DEFINE_TYPES(aspeed_adc_types) |