diff mbox series

[v2,1/2] hw/adc: Add basic Aspeed ADC model

Message ID 20211003191850.513658-2-pdel@fb.com
State New
Headers show
Series [v2,1/2] hw/adc: Add basic Aspeed ADC model | expand

Commit Message

Peter Delevoryas Oct. 3, 2021, 7:18 p.m. UTC
From: Andrew Jeffery <andrew@aj.id.au>

This model implements enough behaviour to do basic functionality tests
such as device initialisation and read out of dummy sample values. The
sample value generation strategy is similar to the STM ADC already in
the tree.

Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
[clg : support for multiple engines (AST2600) ]
Signed-off-by: Cédric Le Goater <clg@kaod.org>
[pdel : refactored engine register struct fields to regs[] array field]
[pdel : added guest-error checking for upper-8 channel regs in AST2600]
Signed-off-by: Peter Delevoryas <pdel@fb.com>
---
 hw/adc/aspeed_adc.c         | 422 ++++++++++++++++++++++++++++++++++++
 hw/adc/meson.build          |   1 +
 hw/adc/trace-events         |   3 +
 include/hw/adc/aspeed_adc.h |  55 +++++
 4 files changed, 481 insertions(+)
 create mode 100644 hw/adc/aspeed_adc.c
 create mode 100644 include/hw/adc/aspeed_adc.h

Comments

Cédric Le Goater Oct. 4, 2021, 7:49 a.m. UTC | #1
On 10/3/21 21:18, pdel@fb.com wrote:
> From: Andrew Jeffery <andrew@aj.id.au>
> 
> This model implements enough behaviour to do basic functionality tests
> such as device initialisation and read out of dummy sample values. The
> sample value generation strategy is similar to the STM ADC already in
> the tree.
> 
> Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
> [clg : support for multiple engines (AST2600) ]
> Signed-off-by: Cédric Le Goater <clg@kaod.org>
> [pdel : refactored engine register struct fields to regs[] array field]
> [pdel : added guest-error checking for upper-8 channel regs in AST2600]
> Signed-off-by: Peter Delevoryas <pdel@fb.com>

Reviewed-by: Cédric Le Goater <clg@kaod.org>

Thanks,

C.


> ---
>   hw/adc/aspeed_adc.c         | 422 ++++++++++++++++++++++++++++++++++++
>   hw/adc/meson.build          |   1 +
>   hw/adc/trace-events         |   3 +
>   include/hw/adc/aspeed_adc.h |  55 +++++
>   4 files changed, 481 insertions(+)
>   create mode 100644 hw/adc/aspeed_adc.c
>   create mode 100644 include/hw/adc/aspeed_adc.h
> 
> diff --git a/hw/adc/aspeed_adc.c b/hw/adc/aspeed_adc.c
> new file mode 100644
> index 0000000000..fcd93d6853
> --- /dev/null
> +++ b/hw/adc/aspeed_adc.c
> @@ -0,0 +1,422 @@
> +/*
> + * Aspeed ADC
> + *
> + * Copyright 2017-2021 IBM Corp.
> + *
> + * Andrew Jeffery <andrew@aj.id.au>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qapi/error.h"
> +#include "qemu/log.h"
> +#include "hw/irq.h"
> +#include "hw/qdev-properties.h"
> +#include "migration/vmstate.h"
> +#include "hw/adc/aspeed_adc.h"
> +#include "trace.h"
> +
> +#define ASPEED_ADC_MEMORY_REGION_SIZE           0x1000
> +#define ASPEED_ADC_ENGINE_MEMORY_REGION_SIZE    0x100
> +#define  ASPEED_ADC_ENGINE_CH_EN_MASK           0xffff0000
> +#define   ASPEED_ADC_ENGINE_CH_EN(x)            ((BIT(x)) << 16)
> +#define  ASPEED_ADC_ENGINE_INIT                 BIT(8)
> +#define  ASPEED_ADC_ENGINE_AUTO_COMP            BIT(5)
> +#define  ASPEED_ADC_ENGINE_COMP                 BIT(4)
> +#define  ASPEED_ADC_ENGINE_MODE_MASK            0x0000000e
> +#define   ASPEED_ADC_ENGINE_MODE_OFF            (0b000 << 1)
> +#define   ASPEED_ADC_ENGINE_MODE_STANDBY        (0b001 << 1)
> +#define   ASPEED_ADC_ENGINE_MODE_NORMAL         (0b111 << 1)
> +#define  ASPEED_ADC_ENGINE_EN                   BIT(0)
> +#define ASPEED_ADC_HYST_EN                      BIT(31)
> +
> +#define ASPEED_ADC_L_MASK       ((1 << 10) - 1)
> +#define ASPEED_ADC_L(x)         ((x) & ASPEED_ADC_L_MASK)
> +#define ASPEED_ADC_H(x)         (((x) >> 16) & ASPEED_ADC_L_MASK)
> +#define ASPEED_ADC_LH_MASK      (ASPEED_ADC_L_MASK << 16 | ASPEED_ADC_L_MASK)
> +#define LOWER_CHANNEL_MASK      ((1 << 10) - 1)
> +#define LOWER_CHANNEL_DATA(x)   ((x) & LOWER_CHANNEL_MASK)
> +#define UPPER_CHANNEL_DATA(x)   (((x) >> 16) & LOWER_CHANNEL_MASK)
> +
> +#define TO_REG(addr) (addr >> 2)
> +
> +#define ENGINE_CONTROL              TO_REG(0x00)
> +#define INTERRUPT_CONTROL           TO_REG(0x04)
> +#define VGA_DETECT_CONTROL          TO_REG(0x08)
> +#define CLOCK_CONTROL               TO_REG(0x0C)
> +#define DATA_CHANNEL_1_AND_0        TO_REG(0x10)
> +#define DATA_CHANNEL_7_AND_6        TO_REG(0x1C)
> +#define DATA_CHANNEL_9_AND_8        TO_REG(0x20)
> +#define DATA_CHANNEL_15_AND_14      TO_REG(0x2C)
> +#define BOUNDS_CHANNEL_0            TO_REG(0x30)
> +#define BOUNDS_CHANNEL_7            TO_REG(0x4C)
> +#define BOUNDS_CHANNEL_8            TO_REG(0x50)
> +#define BOUNDS_CHANNEL_15           TO_REG(0x6C)
> +#define HYSTERESIS_CHANNEL_0        TO_REG(0x70)
> +#define HYSTERESIS_CHANNEL_7        TO_REG(0x8C)
> +#define HYSTERESIS_CHANNEL_8        TO_REG(0x90)
> +#define HYSTERESIS_CHANNEL_15       TO_REG(0xAC)
> +#define INTERRUPT_SOURCE            TO_REG(0xC0)
> +#define COMPENSATING_AND_TRIMMING   TO_REG(0xC4)
> +
> +static inline uint32_t update_channels(uint32_t current)
> +{
> +    return ((((current >> 16) & ASPEED_ADC_L_MASK) + 7) << 16) |
> +        ((current + 5) & ASPEED_ADC_L_MASK);
> +}
> +
> +static bool breaks_threshold(AspeedADCEngineState *s, int reg)
> +{
> +    assert(reg >= DATA_CHANNEL_1_AND_0 &&
> +           reg < DATA_CHANNEL_1_AND_0 + s->nr_channels / 2);
> +
> +    int a_bounds_reg = BOUNDS_CHANNEL_0 + (reg - DATA_CHANNEL_1_AND_0) * 2;
> +    int b_bounds_reg = a_bounds_reg + 1;
> +    uint32_t a_and_b = s->regs[reg];
> +    uint32_t a_bounds = s->regs[a_bounds_reg];
> +    uint32_t b_bounds = s->regs[b_bounds_reg];
> +    uint32_t a = ASPEED_ADC_L(a_and_b);
> +    uint32_t b = ASPEED_ADC_H(a_and_b);
> +    uint32_t a_lower = ASPEED_ADC_L(a_bounds);
> +    uint32_t a_upper = ASPEED_ADC_H(a_bounds);
> +    uint32_t b_lower = ASPEED_ADC_L(b_bounds);
> +    uint32_t b_upper = ASPEED_ADC_H(b_bounds);
> +
> +    return (a < a_lower || a > a_upper) ||
> +           (b < b_lower || b > b_upper);
> +}
> +
> +static uint32_t read_channel_sample(AspeedADCEngineState *s, int reg)
> +{
> +    assert(reg >= DATA_CHANNEL_1_AND_0 &&
> +           reg < DATA_CHANNEL_1_AND_0 + s->nr_channels / 2);
> +
> +    /* Poor man's sampling */
> +    uint32_t value = s->regs[reg];
> +    s->regs[reg] = update_channels(s->regs[reg]);
> +
> +    if (breaks_threshold(s, reg)) {
> +        s->regs[INTERRUPT_CONTROL] |= BIT(reg - DATA_CHANNEL_1_AND_0);
> +        qemu_irq_raise(s->irq);
> +    }
> +
> +    return value;
> +}
> +
> +static uint64_t aspeed_adc_engine_read(void *opaque, hwaddr addr,
> +                                       unsigned int size)
> +{
> +    AspeedADCEngineState *s = ASPEED_ADC_ENGINE(opaque);
> +    int reg = TO_REG(addr);
> +    uint32_t value = 0;
> +
> +    switch (reg) {
> +    case BOUNDS_CHANNEL_8 ... BOUNDS_CHANNEL_15:
> +        if (s->nr_channels <= 8) {
> +            qemu_log_mask(LOG_GUEST_ERROR, "%s: engine[%u]: "
> +                          "bounds register %u invalid, only 0...7 valid\n",
> +                          __func__, s->engine_id, reg - BOUNDS_CHANNEL_0);
> +            break;
> +        }
> +        /* fallthrough */
> +    case HYSTERESIS_CHANNEL_8 ... HYSTERESIS_CHANNEL_15:
> +        if (s->nr_channels <= 8) {
> +            qemu_log_mask(LOG_GUEST_ERROR, "%s: engine[%u]: "
> +                          "hysteresis register %u invalid, only 0...7 valid\n",
> +                          __func__, s->engine_id, reg - HYSTERESIS_CHANNEL_0);
> +            break;
> +        }
> +        /* fallthrough */
> +    case BOUNDS_CHANNEL_0 ... BOUNDS_CHANNEL_7:
> +    case HYSTERESIS_CHANNEL_0 ... HYSTERESIS_CHANNEL_7:
> +    case ENGINE_CONTROL:
> +    case INTERRUPT_CONTROL:
> +    case VGA_DETECT_CONTROL:
> +    case CLOCK_CONTROL:
> +    case INTERRUPT_SOURCE:
> +    case COMPENSATING_AND_TRIMMING:
> +        value = s->regs[reg];
> +        break;
> +    case DATA_CHANNEL_9_AND_8 ... DATA_CHANNEL_15_AND_14:
> +        if (s->nr_channels <= 8) {
> +            qemu_log_mask(LOG_GUEST_ERROR, "%s: engine[%u]: "
> +                          "data register %u invalid, only 0...3 valid\n",
> +                          __func__, s->engine_id, reg - DATA_CHANNEL_1_AND_0);
> +            break;
> +        }
> +        /* fallthrough */
> +    case DATA_CHANNEL_1_AND_0 ... DATA_CHANNEL_7_AND_6:
> +        value = read_channel_sample(s, reg);
> +        break;
> +    default:
> +        qemu_log_mask(LOG_UNIMP, "%s: engine[%u]: 0x%" HWADDR_PRIx "\n",
> +                      __func__, s->engine_id, addr);
> +        break;
> +    }
> +
> +    trace_aspeed_adc_engine_read(s->engine_id, addr, value);
> +    return value;
> +}
> +
> +static void aspeed_adc_engine_write(void *opaque, hwaddr addr, uint64_t value,
> +                                    unsigned int size)
> +{
> +    AspeedADCEngineState *s = ASPEED_ADC_ENGINE(opaque);
> +    int reg = TO_REG(addr);
> +    uint32_t init = 0;
> +
> +    trace_aspeed_adc_engine_write(s->engine_id, addr, value);
> +
> +    switch (reg) {
> +    case ENGINE_CONTROL:
> +        init = !!(value & ASPEED_ADC_ENGINE_EN);
> +        init *= ASPEED_ADC_ENGINE_INIT;
> +
> +        value &= ~ASPEED_ADC_ENGINE_INIT;
> +        value |= init;
> +
> +        value &= ~ASPEED_ADC_ENGINE_AUTO_COMP;
> +        break;
> +    case INTERRUPT_CONTROL:
> +    case VGA_DETECT_CONTROL:
> +    case CLOCK_CONTROL:
> +        break;
> +    case DATA_CHANNEL_9_AND_8 ... DATA_CHANNEL_15_AND_14:
> +        if (s->nr_channels <= 8) {
> +            qemu_log_mask(LOG_GUEST_ERROR, "%s: engine[%u]: "
> +                          "data register %u invalid, only 0...3 valid\n",
> +                          __func__, s->engine_id, reg - DATA_CHANNEL_1_AND_0);
> +            return;
> +        }
> +        /* fallthrough */
> +    case BOUNDS_CHANNEL_8 ... BOUNDS_CHANNEL_15:
> +        if (s->nr_channels <= 8) {
> +            qemu_log_mask(LOG_GUEST_ERROR, "%s: engine[%u]: "
> +                          "bounds register %u invalid, only 0...7 valid\n",
> +                          __func__, s->engine_id, reg - BOUNDS_CHANNEL_0);
> +            return;
> +        }
> +        /* fallthrough */
> +    case DATA_CHANNEL_1_AND_0 ... DATA_CHANNEL_7_AND_6:
> +    case BOUNDS_CHANNEL_0 ... BOUNDS_CHANNEL_7:
> +        value &= ASPEED_ADC_LH_MASK;
> +        break;
> +    case HYSTERESIS_CHANNEL_8 ... HYSTERESIS_CHANNEL_15:
> +        if (s->nr_channels <= 8) {
> +            qemu_log_mask(LOG_GUEST_ERROR, "%s: engine[%u]: "
> +                          "hysteresis register %u invalid, only 0...7 valid\n",
> +                          __func__, s->engine_id, reg - HYSTERESIS_CHANNEL_0);
> +            return;
> +        }
> +        /* fallthrough */
> +    case HYSTERESIS_CHANNEL_0 ... HYSTERESIS_CHANNEL_7:
> +        value &= (ASPEED_ADC_HYST_EN | ASPEED_ADC_LH_MASK);
> +        break;
> +    case INTERRUPT_SOURCE:
> +        value &= 0xffff;
> +        break;
> +    case COMPENSATING_AND_TRIMMING:
> +        value &= 0xf;
> +        break;
> +    default:
> +        qemu_log_mask(LOG_UNIMP, "%s: engine[%u]: "
> +                      "0x%" HWADDR_PRIx " 0x%" PRIx64 "\n",
> +                      __func__, s->engine_id, addr, value);
> +        break;
> +    }
> +
> +    s->regs[reg] = value;
> +}
> +
> +static const MemoryRegionOps aspeed_adc_engine_ops = {
> +    .read = aspeed_adc_engine_read,
> +    .write = aspeed_adc_engine_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +    .valid = {
> +        .min_access_size = 4,
> +        .max_access_size = 4,
> +        .unaligned = false,
> +    },
> +};
> +
> +static const uint32_t aspeed_adc_resets[ASPEED_ADC_NR_REGS] = {
> +    [ENGINE_CONTROL]     = 0x00000000,
> +    [INTERRUPT_CONTROL]  = 0x00000000,
> +    [VGA_DETECT_CONTROL] = 0x0000000f,
> +    [CLOCK_CONTROL]      = 0x0000000f,
> +};
> +
> +static void aspeed_adc_engine_reset(DeviceState *dev)
> +{
> +    AspeedADCEngineState *s = ASPEED_ADC_ENGINE(dev);
> +
> +    memcpy(s->regs, aspeed_adc_resets, sizeof(aspeed_adc_resets));
> +}
> +
> +static void aspeed_adc_engine_realize(DeviceState *dev, Error **errp)
> +{
> +    AspeedADCEngineState *s = ASPEED_ADC_ENGINE(dev);
> +    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
> +    g_autofree char *name = g_strdup_printf(TYPE_ASPEED_ADC_ENGINE ".%d",
> +                                            s->engine_id);
> +
> +    assert(s->engine_id < 2);
> +
> +    sysbus_init_irq(sbd, &s->irq);
> +
> +    memory_region_init_io(&s->mmio, OBJECT(s), &aspeed_adc_engine_ops, s, name,
> +                          ASPEED_ADC_ENGINE_MEMORY_REGION_SIZE);
> +
> +    sysbus_init_mmio(sbd, &s->mmio);
> +}
> +
> +static const VMStateDescription vmstate_aspeed_adc_engine = {
> +    .name = TYPE_ASPEED_ADC,
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .fields = (VMStateField[]) {
> +        VMSTATE_UINT32_ARRAY(regs, AspeedADCEngineState, ASPEED_ADC_NR_REGS),
> +        VMSTATE_END_OF_LIST(),
> +    }
> +};
> +
> +static Property aspeed_adc_engine_properties[] = {
> +    DEFINE_PROP_UINT32("engine-id", AspeedADCEngineState, engine_id, 0),
> +    DEFINE_PROP_UINT32("nr-channels", AspeedADCEngineState, nr_channels, 0),
> +    DEFINE_PROP_END_OF_LIST(),
> +};
> +
> +static void aspeed_adc_engine_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    dc->realize = aspeed_adc_engine_realize;
> +    dc->reset = aspeed_adc_engine_reset;
> +    device_class_set_props(dc, aspeed_adc_engine_properties);
> +    dc->desc = "Aspeed Analog-to-Digital Engine";
> +    dc->vmsd = &vmstate_aspeed_adc_engine;
> +}
> +
> +static const TypeInfo aspeed_adc_engine_info = {
> +    .name = TYPE_ASPEED_ADC_ENGINE,
> +    .parent = TYPE_SYS_BUS_DEVICE,
> +    .instance_size = sizeof(AspeedADCEngineState),
> +    .class_init = aspeed_adc_engine_class_init,
> +};
> +
> +static void aspeed_adc_instance_init(Object *obj)
> +{
> +    AspeedADCState *s = ASPEED_ADC(obj);
> +    AspeedADCClass *aac = ASPEED_ADC_GET_CLASS(obj);
> +    uint32_t nr_channels = ASPEED_ADC_NR_CHANNELS / aac->nr_engines;
> +
> +    for (int i = 0; i < aac->nr_engines; i++) {
> +        AspeedADCEngineState *engine = &s->engines[i];
> +        object_initialize_child(obj, "engine[*]", engine,
> +                                TYPE_ASPEED_ADC_ENGINE);
> +        qdev_prop_set_uint32(DEVICE(engine), "engine-id", i);
> +        qdev_prop_set_uint32(DEVICE(engine), "nr-channels", nr_channels);
> +    }
> +}
> +
> +static void aspeed_adc_set_irq(void *opaque, int n, int level)
> +{
> +    AspeedADCState *s = opaque;
> +    AspeedADCClass *aac = ASPEED_ADC_GET_CLASS(s);
> +    uint32_t pending = 0;
> +
> +    /* TODO: update Global IRQ status register on AST2600 (Need specs) */
> +    for (int i = 0; i < aac->nr_engines; i++) {
> +        uint32_t irq_status = s->engines[i].regs[INTERRUPT_CONTROL] & 0xFF;
> +        pending |= irq_status << (i * 8);
> +    }
> +
> +    qemu_set_irq(s->irq, !!pending);
> +}
> +
> +static void aspeed_adc_realize(DeviceState *dev, Error **errp)
> +{
> +    AspeedADCState *s = ASPEED_ADC(dev);
> +    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
> +    AspeedADCClass *aac = ASPEED_ADC_GET_CLASS(dev);
> +
> +    qdev_init_gpio_in_named_with_opaque(DEVICE(sbd), aspeed_adc_set_irq,
> +                                        s, NULL, aac->nr_engines);
> +
> +    sysbus_init_irq(sbd, &s->irq);
> +
> +    memory_region_init(&s->mmio, OBJECT(s), TYPE_ASPEED_ADC,
> +                       ASPEED_ADC_MEMORY_REGION_SIZE);
> +
> +    sysbus_init_mmio(sbd, &s->mmio);
> +
> +    for (int i = 0; i < aac->nr_engines; i++) {
> +        Object *eng = OBJECT(&s->engines[i]);
> +
> +        if (!sysbus_realize(SYS_BUS_DEVICE(eng), errp)) {
> +            return;
> +        }
> +        sysbus_connect_irq(SYS_BUS_DEVICE(eng), 0,
> +                           qdev_get_gpio_in(DEVICE(sbd), i));
> +        memory_region_add_subregion(&s->mmio,
> +                                    i * ASPEED_ADC_ENGINE_MEMORY_REGION_SIZE,
> +                                    &s->engines[i].mmio);
> +    }
> +}
> +
> +static void aspeed_adc_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    AspeedADCClass *aac = ASPEED_ADC_CLASS(klass);
> +
> +    dc->realize = aspeed_adc_realize;
> +    dc->desc = "Aspeed Analog-to-Digital Converter";
> +    aac->nr_engines = 1;
> +}
> +
> +static void aspeed_2600_adc_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    AspeedADCClass *aac = ASPEED_ADC_CLASS(klass);
> +
> +    dc->desc = "ASPEED 2600 ADC Controller";
> +    aac->nr_engines = 2;
> +}
> +
> +static const TypeInfo aspeed_adc_info = {
> +    .name = TYPE_ASPEED_ADC,
> +    .parent = TYPE_SYS_BUS_DEVICE,
> +    .instance_init = aspeed_adc_instance_init,
> +    .instance_size = sizeof(AspeedADCState),
> +    .class_init = aspeed_adc_class_init,
> +    .class_size = sizeof(AspeedADCClass),
> +    .abstract   = true,
> +};
> +
> +static const TypeInfo aspeed_2400_adc_info = {
> +    .name = TYPE_ASPEED_2400_ADC,
> +    .parent = TYPE_ASPEED_ADC,
> +};
> +
> +static const TypeInfo aspeed_2500_adc_info = {
> +    .name = TYPE_ASPEED_2500_ADC,
> +    .parent = TYPE_ASPEED_ADC,
> +};
> +
> +static const TypeInfo aspeed_2600_adc_info = {
> +    .name = TYPE_ASPEED_2600_ADC,
> +    .parent = TYPE_ASPEED_ADC,
> +    .class_init = aspeed_2600_adc_class_init,
> +};
> +
> +static void aspeed_adc_register_types(void)
> +{
> +    type_register_static(&aspeed_adc_engine_info);
> +    type_register_static(&aspeed_adc_info);
> +    type_register_static(&aspeed_2400_adc_info);
> +    type_register_static(&aspeed_2500_adc_info);
> +    type_register_static(&aspeed_2600_adc_info);
> +}
> +
> +type_init(aspeed_adc_register_types);
> diff --git a/hw/adc/meson.build b/hw/adc/meson.build
> index ac4f093fea..b29ac7ccdf 100644
> --- a/hw/adc/meson.build
> +++ b/hw/adc/meson.build
> @@ -1,4 +1,5 @@
>   softmmu_ss.add(when: 'CONFIG_STM32F2XX_ADC', if_true: files('stm32f2xx_adc.c'))
> +softmmu_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_adc.c'))
>   softmmu_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_adc.c'))
>   softmmu_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq-xadc.c'))
>   softmmu_ss.add(when: 'CONFIG_MAX111X', if_true: files('max111x.c'))
> diff --git a/hw/adc/trace-events b/hw/adc/trace-events
> index 456f21c8f4..5a4c444d77 100644
> --- a/hw/adc/trace-events
> +++ b/hw/adc/trace-events
> @@ -3,3 +3,6 @@
>   # npcm7xx_adc.c
>   npcm7xx_adc_read(const char *id, uint64_t offset, uint32_t value) " %s offset: 0x%04" PRIx64 " value 0x%04" PRIx32
>   npcm7xx_adc_write(const char *id, uint64_t offset, uint32_t value) "%s offset: 0x%04" PRIx64 " value 0x%04" PRIx32
> +
> +aspeed_adc_engine_read(uint32_t engine_id, uint64_t addr, uint64_t value) "engine[%u] 0x%" PRIx64 " 0x%" PRIx64
> +aspeed_adc_engine_write(uint32_t engine_id, uint64_t addr, uint64_t value) "engine[%u] 0x%" PRIx64 " 0x%" PRIx64
> diff --git a/include/hw/adc/aspeed_adc.h b/include/hw/adc/aspeed_adc.h
> new file mode 100644
> index 0000000000..2f166e8be1
> --- /dev/null
> +++ b/include/hw/adc/aspeed_adc.h
> @@ -0,0 +1,55 @@
> +/*
> + * Aspeed ADC
> + *
> + * Copyright 2017-2021 IBM Corp.
> + *
> + * Andrew Jeffery <andrew@aj.id.au>
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef HW_ADC_ASPEED_ADC_H
> +#define HW_ADC_ASPEED_ADC_H
> +
> +#include "hw/sysbus.h"
> +
> +#define TYPE_ASPEED_ADC "aspeed.adc"
> +#define TYPE_ASPEED_2400_ADC TYPE_ASPEED_ADC "-ast2400"
> +#define TYPE_ASPEED_2500_ADC TYPE_ASPEED_ADC "-ast2500"
> +#define TYPE_ASPEED_2600_ADC TYPE_ASPEED_ADC "-ast2600"
> +OBJECT_DECLARE_TYPE(AspeedADCState, AspeedADCClass, ASPEED_ADC)
> +
> +#define TYPE_ASPEED_ADC_ENGINE "aspeed.adc.engine"
> +OBJECT_DECLARE_SIMPLE_TYPE(AspeedADCEngineState, ASPEED_ADC_ENGINE)
> +
> +#define ASPEED_ADC_NR_CHANNELS 16
> +#define ASPEED_ADC_NR_REGS     (0xD0 >> 2)
> +
> +struct AspeedADCEngineState {
> +    /* <private> */
> +    SysBusDevice parent;
> +
> +    MemoryRegion mmio;
> +    qemu_irq irq;
> +    uint32_t engine_id;
> +    uint32_t nr_channels;
> +    uint32_t regs[ASPEED_ADC_NR_REGS];
> +};
> +
> +struct AspeedADCState {
> +    /* <private> */
> +    SysBusDevice parent;
> +
> +    MemoryRegion mmio;
> +    qemu_irq irq;
> +
> +    AspeedADCEngineState engines[2];
> +};
> +
> +struct AspeedADCClass {
> +    SysBusDeviceClass parent_class;
> +
> +    uint32_t nr_engines;
> +};
> +
> +#endif /* HW_ADC_ASPEED_ADC_H */
>
Peter Delevoryas Oct. 5, 2021, 5:31 a.m. UTC | #2
> On Oct 4, 2021, at 12:49 AM, Cédric Le Goater <clg@kaod.org> wrote:
> 
> On 10/3/21 21:18, pdel@fb.com wrote:
>> From: Andrew Jeffery <andrew@aj.id.au>
>> This model implements enough behaviour to do basic functionality tests
>> such as device initialisation and read out of dummy sample values. The
>> sample value generation strategy is similar to the STM ADC already in
>> the tree.
>> Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
>> [clg : support for multiple engines (AST2600) ]
>> Signed-off-by: Cédric Le Goater <clg@kaod.org>
>> [pdel : refactored engine register struct fields to regs[] array field]
>> [pdel : added guest-error checking for upper-8 channel regs in AST2600]
>> Signed-off-by: Peter Delevoryas <pdel@fb.com>
> 
> Reviewed-by: Cédric Le Goater <clg@kaod.org>
> 
> Thanks,
> 
> C.

Hey Cedric,

Actually, I have just submitted a v3 of this patch series to support 16-bit
reads of the channel data registers. I don’t think I tested using the driver
to read from the ADC, and that’s what Patrick found crashed with these
changes. Since it’s relatively easy to enable 16-bit reads, I figured
I would just include that.

Thanks,
Peter

> 
> 
>> ---
>>  hw/adc/aspeed_adc.c         | 422 ++++++++++++++++++++++++++++++++++++
>>  hw/adc/meson.build          |   1 +
>>  hw/adc/trace-events         |   3 +
>>  include/hw/adc/aspeed_adc.h |  55 +++++
>>  4 files changed, 481 insertions(+)
>>  create mode 100644 hw/adc/aspeed_adc.c
>>  create mode 100644 include/hw/adc/aspeed_adc.h
>> diff --git a/hw/adc/aspeed_adc.c b/hw/adc/aspeed_adc.c
>> new file mode 100644
>> index 0000000000..fcd93d6853
>> --- /dev/null
>> +++ b/hw/adc/aspeed_adc.c
>> @@ -0,0 +1,422 @@
>> +/*
>> + * Aspeed ADC
>> + *
>> + * Copyright 2017-2021 IBM Corp.
>> + *
>> + * Andrew Jeffery <andrew@aj.id.au>
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#include "qemu/osdep.h"
>> +#include "qapi/error.h"
>> +#include "qemu/log.h"
>> +#include "hw/irq.h"
>> +#include "hw/qdev-properties.h"
>> +#include "migration/vmstate.h"
>> +#include "hw/adc/aspeed_adc.h"
>> +#include "trace.h"
>> +
>> +#define ASPEED_ADC_MEMORY_REGION_SIZE           0x1000
>> +#define ASPEED_ADC_ENGINE_MEMORY_REGION_SIZE    0x100
>> +#define  ASPEED_ADC_ENGINE_CH_EN_MASK           0xffff0000
>> +#define   ASPEED_ADC_ENGINE_CH_EN(x)            ((BIT(x)) << 16)
>> +#define  ASPEED_ADC_ENGINE_INIT                 BIT(8)
>> +#define  ASPEED_ADC_ENGINE_AUTO_COMP            BIT(5)
>> +#define  ASPEED_ADC_ENGINE_COMP                 BIT(4)
>> +#define  ASPEED_ADC_ENGINE_MODE_MASK            0x0000000e
>> +#define   ASPEED_ADC_ENGINE_MODE_OFF            (0b000 << 1)
>> +#define   ASPEED_ADC_ENGINE_MODE_STANDBY        (0b001 << 1)
>> +#define   ASPEED_ADC_ENGINE_MODE_NORMAL         (0b111 << 1)
>> +#define  ASPEED_ADC_ENGINE_EN                   BIT(0)
>> +#define ASPEED_ADC_HYST_EN                      BIT(31)
>> +
>> +#define ASPEED_ADC_L_MASK       ((1 << 10) - 1)
>> +#define ASPEED_ADC_L(x)         ((x) & ASPEED_ADC_L_MASK)
>> +#define ASPEED_ADC_H(x)         (((x) >> 16) & ASPEED_ADC_L_MASK)
>> +#define ASPEED_ADC_LH_MASK      (ASPEED_ADC_L_MASK << 16 | ASPEED_ADC_L_MASK)
>> +#define LOWER_CHANNEL_MASK      ((1 << 10) - 1)
>> +#define LOWER_CHANNEL_DATA(x)   ((x) & LOWER_CHANNEL_MASK)
>> +#define UPPER_CHANNEL_DATA(x)   (((x) >> 16) & LOWER_CHANNEL_MASK)
>> +
>> +#define TO_REG(addr) (addr >> 2)
>> +
>> +#define ENGINE_CONTROL              TO_REG(0x00)
>> +#define INTERRUPT_CONTROL           TO_REG(0x04)
>> +#define VGA_DETECT_CONTROL          TO_REG(0x08)
>> +#define CLOCK_CONTROL               TO_REG(0x0C)
>> +#define DATA_CHANNEL_1_AND_0        TO_REG(0x10)
>> +#define DATA_CHANNEL_7_AND_6        TO_REG(0x1C)
>> +#define DATA_CHANNEL_9_AND_8        TO_REG(0x20)
>> +#define DATA_CHANNEL_15_AND_14      TO_REG(0x2C)
>> +#define BOUNDS_CHANNEL_0            TO_REG(0x30)
>> +#define BOUNDS_CHANNEL_7            TO_REG(0x4C)
>> +#define BOUNDS_CHANNEL_8            TO_REG(0x50)
>> +#define BOUNDS_CHANNEL_15           TO_REG(0x6C)
>> +#define HYSTERESIS_CHANNEL_0        TO_REG(0x70)
>> +#define HYSTERESIS_CHANNEL_7        TO_REG(0x8C)
>> +#define HYSTERESIS_CHANNEL_8        TO_REG(0x90)
>> +#define HYSTERESIS_CHANNEL_15       TO_REG(0xAC)
>> +#define INTERRUPT_SOURCE            TO_REG(0xC0)
>> +#define COMPENSATING_AND_TRIMMING   TO_REG(0xC4)
>> +
>> +static inline uint32_t update_channels(uint32_t current)
>> +{
>> +    return ((((current >> 16) & ASPEED_ADC_L_MASK) + 7) << 16) |
>> +        ((current + 5) & ASPEED_ADC_L_MASK);
>> +}
>> +
>> +static bool breaks_threshold(AspeedADCEngineState *s, int reg)
>> +{
>> +    assert(reg >= DATA_CHANNEL_1_AND_0 &&
>> +           reg < DATA_CHANNEL_1_AND_0 + s->nr_channels / 2);
>> +
>> +    int a_bounds_reg = BOUNDS_CHANNEL_0 + (reg - DATA_CHANNEL_1_AND_0) * 2;
>> +    int b_bounds_reg = a_bounds_reg + 1;
>> +    uint32_t a_and_b = s->regs[reg];
>> +    uint32_t a_bounds = s->regs[a_bounds_reg];
>> +    uint32_t b_bounds = s->regs[b_bounds_reg];
>> +    uint32_t a = ASPEED_ADC_L(a_and_b);
>> +    uint32_t b = ASPEED_ADC_H(a_and_b);
>> +    uint32_t a_lower = ASPEED_ADC_L(a_bounds);
>> +    uint32_t a_upper = ASPEED_ADC_H(a_bounds);
>> +    uint32_t b_lower = ASPEED_ADC_L(b_bounds);
>> +    uint32_t b_upper = ASPEED_ADC_H(b_bounds);
>> +
>> +    return (a < a_lower || a > a_upper) ||
>> +           (b < b_lower || b > b_upper);
>> +}
>> +
>> +static uint32_t read_channel_sample(AspeedADCEngineState *s, int reg)
>> +{
>> +    assert(reg >= DATA_CHANNEL_1_AND_0 &&
>> +           reg < DATA_CHANNEL_1_AND_0 + s->nr_channels / 2);
>> +
>> +    /* Poor man's sampling */
>> +    uint32_t value = s->regs[reg];
>> +    s->regs[reg] = update_channels(s->regs[reg]);
>> +
>> +    if (breaks_threshold(s, reg)) {
>> +        s->regs[INTERRUPT_CONTROL] |= BIT(reg - DATA_CHANNEL_1_AND_0);
>> +        qemu_irq_raise(s->irq);
>> +    }
>> +
>> +    return value;
>> +}
>> +
>> +static uint64_t aspeed_adc_engine_read(void *opaque, hwaddr addr,
>> +                                       unsigned int size)
>> +{
>> +    AspeedADCEngineState *s = ASPEED_ADC_ENGINE(opaque);
>> +    int reg = TO_REG(addr);
>> +    uint32_t value = 0;
>> +
>> +    switch (reg) {
>> +    case BOUNDS_CHANNEL_8 ... BOUNDS_CHANNEL_15:
>> +        if (s->nr_channels <= 8) {
>> +            qemu_log_mask(LOG_GUEST_ERROR, "%s: engine[%u]: "
>> +                          "bounds register %u invalid, only 0...7 valid\n",
>> +                          __func__, s->engine_id, reg - BOUNDS_CHANNEL_0);
>> +            break;
>> +        }
>> +        /* fallthrough */
>> +    case HYSTERESIS_CHANNEL_8 ... HYSTERESIS_CHANNEL_15:
>> +        if (s->nr_channels <= 8) {
>> +            qemu_log_mask(LOG_GUEST_ERROR, "%s: engine[%u]: "
>> +                          "hysteresis register %u invalid, only 0...7 valid\n",
>> +                          __func__, s->engine_id, reg - HYSTERESIS_CHANNEL_0);
>> +            break;
>> +        }
>> +        /* fallthrough */
>> +    case BOUNDS_CHANNEL_0 ... BOUNDS_CHANNEL_7:
>> +    case HYSTERESIS_CHANNEL_0 ... HYSTERESIS_CHANNEL_7:
>> +    case ENGINE_CONTROL:
>> +    case INTERRUPT_CONTROL:
>> +    case VGA_DETECT_CONTROL:
>> +    case CLOCK_CONTROL:
>> +    case INTERRUPT_SOURCE:
>> +    case COMPENSATING_AND_TRIMMING:
>> +        value = s->regs[reg];
>> +        break;
>> +    case DATA_CHANNEL_9_AND_8 ... DATA_CHANNEL_15_AND_14:
>> +        if (s->nr_channels <= 8) {
>> +            qemu_log_mask(LOG_GUEST_ERROR, "%s: engine[%u]: "
>> +                          "data register %u invalid, only 0...3 valid\n",
>> +                          __func__, s->engine_id, reg - DATA_CHANNEL_1_AND_0);
>> +            break;
>> +        }
>> +        /* fallthrough */
>> +    case DATA_CHANNEL_1_AND_0 ... DATA_CHANNEL_7_AND_6:
>> +        value = read_channel_sample(s, reg);
>> +        break;
>> +    default:
>> +        qemu_log_mask(LOG_UNIMP, "%s: engine[%u]: 0x%" HWADDR_PRIx "\n",
>> +                      __func__, s->engine_id, addr);
>> +        break;
>> +    }
>> +
>> +    trace_aspeed_adc_engine_read(s->engine_id, addr, value);
>> +    return value;
>> +}
>> +
>> +static void aspeed_adc_engine_write(void *opaque, hwaddr addr, uint64_t value,
>> +                                    unsigned int size)
>> +{
>> +    AspeedADCEngineState *s = ASPEED_ADC_ENGINE(opaque);
>> +    int reg = TO_REG(addr);
>> +    uint32_t init = 0;
>> +
>> +    trace_aspeed_adc_engine_write(s->engine_id, addr, value);
>> +
>> +    switch (reg) {
>> +    case ENGINE_CONTROL:
>> +        init = !!(value & ASPEED_ADC_ENGINE_EN);
>> +        init *= ASPEED_ADC_ENGINE_INIT;
>> +
>> +        value &= ~ASPEED_ADC_ENGINE_INIT;
>> +        value |= init;
>> +
>> +        value &= ~ASPEED_ADC_ENGINE_AUTO_COMP;
>> +        break;
>> +    case INTERRUPT_CONTROL:
>> +    case VGA_DETECT_CONTROL:
>> +    case CLOCK_CONTROL:
>> +        break;
>> +    case DATA_CHANNEL_9_AND_8 ... DATA_CHANNEL_15_AND_14:
>> +        if (s->nr_channels <= 8) {
>> +            qemu_log_mask(LOG_GUEST_ERROR, "%s: engine[%u]: "
>> +                          "data register %u invalid, only 0...3 valid\n",
>> +                          __func__, s->engine_id, reg - DATA_CHANNEL_1_AND_0);
>> +            return;
>> +        }
>> +        /* fallthrough */
>> +    case BOUNDS_CHANNEL_8 ... BOUNDS_CHANNEL_15:
>> +        if (s->nr_channels <= 8) {
>> +            qemu_log_mask(LOG_GUEST_ERROR, "%s: engine[%u]: "
>> +                          "bounds register %u invalid, only 0...7 valid\n",
>> +                          __func__, s->engine_id, reg - BOUNDS_CHANNEL_0);
>> +            return;
>> +        }
>> +        /* fallthrough */
>> +    case DATA_CHANNEL_1_AND_0 ... DATA_CHANNEL_7_AND_6:
>> +    case BOUNDS_CHANNEL_0 ... BOUNDS_CHANNEL_7:
>> +        value &= ASPEED_ADC_LH_MASK;
>> +        break;
>> +    case HYSTERESIS_CHANNEL_8 ... HYSTERESIS_CHANNEL_15:
>> +        if (s->nr_channels <= 8) {
>> +            qemu_log_mask(LOG_GUEST_ERROR, "%s: engine[%u]: "
>> +                          "hysteresis register %u invalid, only 0...7 valid\n",
>> +                          __func__, s->engine_id, reg - HYSTERESIS_CHANNEL_0);
>> +            return;
>> +        }
>> +        /* fallthrough */
>> +    case HYSTERESIS_CHANNEL_0 ... HYSTERESIS_CHANNEL_7:
>> +        value &= (ASPEED_ADC_HYST_EN | ASPEED_ADC_LH_MASK);
>> +        break;
>> +    case INTERRUPT_SOURCE:
>> +        value &= 0xffff;
>> +        break;
>> +    case COMPENSATING_AND_TRIMMING:
>> +        value &= 0xf;
>> +        break;
>> +    default:
>> +        qemu_log_mask(LOG_UNIMP, "%s: engine[%u]: "
>> +                      "0x%" HWADDR_PRIx " 0x%" PRIx64 "\n",
>> +                      __func__, s->engine_id, addr, value);
>> +        break;
>> +    }
>> +
>> +    s->regs[reg] = value;
>> +}
>> +
>> +static const MemoryRegionOps aspeed_adc_engine_ops = {
>> +    .read = aspeed_adc_engine_read,
>> +    .write = aspeed_adc_engine_write,
>> +    .endianness = DEVICE_LITTLE_ENDIAN,
>> +    .valid = {
>> +        .min_access_size = 4,
>> +        .max_access_size = 4,
>> +        .unaligned = false,
>> +    },
>> +};
>> +
>> +static const uint32_t aspeed_adc_resets[ASPEED_ADC_NR_REGS] = {
>> +    [ENGINE_CONTROL]     = 0x00000000,
>> +    [INTERRUPT_CONTROL]  = 0x00000000,
>> +    [VGA_DETECT_CONTROL] = 0x0000000f,
>> +    [CLOCK_CONTROL]      = 0x0000000f,
>> +};
>> +
>> +static void aspeed_adc_engine_reset(DeviceState *dev)
>> +{
>> +    AspeedADCEngineState *s = ASPEED_ADC_ENGINE(dev);
>> +
>> +    memcpy(s->regs, aspeed_adc_resets, sizeof(aspeed_adc_resets));
>> +}
>> +
>> +static void aspeed_adc_engine_realize(DeviceState *dev, Error **errp)
>> +{
>> +    AspeedADCEngineState *s = ASPEED_ADC_ENGINE(dev);
>> +    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
>> +    g_autofree char *name = g_strdup_printf(TYPE_ASPEED_ADC_ENGINE ".%d",
>> +                                            s->engine_id);
>> +
>> +    assert(s->engine_id < 2);
>> +
>> +    sysbus_init_irq(sbd, &s->irq);
>> +
>> +    memory_region_init_io(&s->mmio, OBJECT(s), &aspeed_adc_engine_ops, s, name,
>> +                          ASPEED_ADC_ENGINE_MEMORY_REGION_SIZE);
>> +
>> +    sysbus_init_mmio(sbd, &s->mmio);
>> +}
>> +
>> +static const VMStateDescription vmstate_aspeed_adc_engine = {
>> +    .name = TYPE_ASPEED_ADC,
>> +    .version_id = 1,
>> +    .minimum_version_id = 1,
>> +    .fields = (VMStateField[]) {
>> +        VMSTATE_UINT32_ARRAY(regs, AspeedADCEngineState, ASPEED_ADC_NR_REGS),
>> +        VMSTATE_END_OF_LIST(),
>> +    }
>> +};
>> +
>> +static Property aspeed_adc_engine_properties[] = {
>> +    DEFINE_PROP_UINT32("engine-id", AspeedADCEngineState, engine_id, 0),
>> +    DEFINE_PROP_UINT32("nr-channels", AspeedADCEngineState, nr_channels, 0),
>> +    DEFINE_PROP_END_OF_LIST(),
>> +};
>> +
>> +static void aspeed_adc_engine_class_init(ObjectClass *klass, void *data)
>> +{
>> +    DeviceClass *dc = DEVICE_CLASS(klass);
>> +
>> +    dc->realize = aspeed_adc_engine_realize;
>> +    dc->reset = aspeed_adc_engine_reset;
>> +    device_class_set_props(dc, aspeed_adc_engine_properties);
>> +    dc->desc = "Aspeed Analog-to-Digital Engine";
>> +    dc->vmsd = &vmstate_aspeed_adc_engine;
>> +}
>> +
>> +static const TypeInfo aspeed_adc_engine_info = {
>> +    .name = TYPE_ASPEED_ADC_ENGINE,
>> +    .parent = TYPE_SYS_BUS_DEVICE,
>> +    .instance_size = sizeof(AspeedADCEngineState),
>> +    .class_init = aspeed_adc_engine_class_init,
>> +};
>> +
>> +static void aspeed_adc_instance_init(Object *obj)
>> +{
>> +    AspeedADCState *s = ASPEED_ADC(obj);
>> +    AspeedADCClass *aac = ASPEED_ADC_GET_CLASS(obj);
>> +    uint32_t nr_channels = ASPEED_ADC_NR_CHANNELS / aac->nr_engines;
>> +
>> +    for (int i = 0; i < aac->nr_engines; i++) {
>> +        AspeedADCEngineState *engine = &s->engines[i];
>> +        object_initialize_child(obj, "engine[*]", engine,
>> +                                TYPE_ASPEED_ADC_ENGINE);
>> +        qdev_prop_set_uint32(DEVICE(engine), "engine-id", i);
>> +        qdev_prop_set_uint32(DEVICE(engine), "nr-channels", nr_channels);
>> +    }
>> +}
>> +
>> +static void aspeed_adc_set_irq(void *opaque, int n, int level)
>> +{
>> +    AspeedADCState *s = opaque;
>> +    AspeedADCClass *aac = ASPEED_ADC_GET_CLASS(s);
>> +    uint32_t pending = 0;
>> +
>> +    /* TODO: update Global IRQ status register on AST2600 (Need specs) */
>> +    for (int i = 0; i < aac->nr_engines; i++) {
>> +        uint32_t irq_status = s->engines[i].regs[INTERRUPT_CONTROL] & 0xFF;
>> +        pending |= irq_status << (i * 8);
>> +    }
>> +
>> +    qemu_set_irq(s->irq, !!pending);
>> +}
>> +
>> +static void aspeed_adc_realize(DeviceState *dev, Error **errp)
>> +{
>> +    AspeedADCState *s = ASPEED_ADC(dev);
>> +    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
>> +    AspeedADCClass *aac = ASPEED_ADC_GET_CLASS(dev);
>> +
>> +    qdev_init_gpio_in_named_with_opaque(DEVICE(sbd), aspeed_adc_set_irq,
>> +                                        s, NULL, aac->nr_engines);
>> +
>> +    sysbus_init_irq(sbd, &s->irq);
>> +
>> +    memory_region_init(&s->mmio, OBJECT(s), TYPE_ASPEED_ADC,
>> +                       ASPEED_ADC_MEMORY_REGION_SIZE);
>> +
>> +    sysbus_init_mmio(sbd, &s->mmio);
>> +
>> +    for (int i = 0; i < aac->nr_engines; i++) {
>> +        Object *eng = OBJECT(&s->engines[i]);
>> +
>> +        if (!sysbus_realize(SYS_BUS_DEVICE(eng), errp)) {
>> +            return;
>> +        }
>> +        sysbus_connect_irq(SYS_BUS_DEVICE(eng), 0,
>> +                           qdev_get_gpio_in(DEVICE(sbd), i));
>> +        memory_region_add_subregion(&s->mmio,
>> +                                    i * ASPEED_ADC_ENGINE_MEMORY_REGION_SIZE,
>> +                                    &s->engines[i].mmio);
>> +    }
>> +}
>> +
>> +static void aspeed_adc_class_init(ObjectClass *klass, void *data)
>> +{
>> +    DeviceClass *dc = DEVICE_CLASS(klass);
>> +    AspeedADCClass *aac = ASPEED_ADC_CLASS(klass);
>> +
>> +    dc->realize = aspeed_adc_realize;
>> +    dc->desc = "Aspeed Analog-to-Digital Converter";
>> +    aac->nr_engines = 1;
>> +}
>> +
>> +static void aspeed_2600_adc_class_init(ObjectClass *klass, void *data)
>> +{
>> +    DeviceClass *dc = DEVICE_CLASS(klass);
>> +    AspeedADCClass *aac = ASPEED_ADC_CLASS(klass);
>> +
>> +    dc->desc = "ASPEED 2600 ADC Controller";
>> +    aac->nr_engines = 2;
>> +}
>> +
>> +static const TypeInfo aspeed_adc_info = {
>> +    .name = TYPE_ASPEED_ADC,
>> +    .parent = TYPE_SYS_BUS_DEVICE,
>> +    .instance_init = aspeed_adc_instance_init,
>> +    .instance_size = sizeof(AspeedADCState),
>> +    .class_init = aspeed_adc_class_init,
>> +    .class_size = sizeof(AspeedADCClass),
>> +    .abstract   = true,
>> +};
>> +
>> +static const TypeInfo aspeed_2400_adc_info = {
>> +    .name = TYPE_ASPEED_2400_ADC,
>> +    .parent = TYPE_ASPEED_ADC,
>> +};
>> +
>> +static const TypeInfo aspeed_2500_adc_info = {
>> +    .name = TYPE_ASPEED_2500_ADC,
>> +    .parent = TYPE_ASPEED_ADC,
>> +};
>> +
>> +static const TypeInfo aspeed_2600_adc_info = {
>> +    .name = TYPE_ASPEED_2600_ADC,
>> +    .parent = TYPE_ASPEED_ADC,
>> +    .class_init = aspeed_2600_adc_class_init,
>> +};
>> +
>> +static void aspeed_adc_register_types(void)
>> +{
>> +    type_register_static(&aspeed_adc_engine_info);
>> +    type_register_static(&aspeed_adc_info);
>> +    type_register_static(&aspeed_2400_adc_info);
>> +    type_register_static(&aspeed_2500_adc_info);
>> +    type_register_static(&aspeed_2600_adc_info);
>> +}
>> +
>> +type_init(aspeed_adc_register_types);
>> diff --git a/hw/adc/meson.build b/hw/adc/meson.build
>> index ac4f093fea..b29ac7ccdf 100644
>> --- a/hw/adc/meson.build
>> +++ b/hw/adc/meson.build
>> @@ -1,4 +1,5 @@
>>  softmmu_ss.add(when: 'CONFIG_STM32F2XX_ADC', if_true: files('stm32f2xx_adc.c'))
>> +softmmu_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_adc.c'))
>>  softmmu_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_adc.c'))
>>  softmmu_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq-xadc.c'))
>>  softmmu_ss.add(when: 'CONFIG_MAX111X', if_true: files('max111x.c'))
>> diff --git a/hw/adc/trace-events b/hw/adc/trace-events
>> index 456f21c8f4..5a4c444d77 100644
>> --- a/hw/adc/trace-events
>> +++ b/hw/adc/trace-events
>> @@ -3,3 +3,6 @@
>>  # npcm7xx_adc.c
>>  npcm7xx_adc_read(const char *id, uint64_t offset, uint32_t value) " %s offset: 0x%04" PRIx64 " value 0x%04" PRIx32
>>  npcm7xx_adc_write(const char *id, uint64_t offset, uint32_t value) "%s offset: 0x%04" PRIx64 " value 0x%04" PRIx32
>> +
>> +aspeed_adc_engine_read(uint32_t engine_id, uint64_t addr, uint64_t value) "engine[%u] 0x%" PRIx64 " 0x%" PRIx64
>> +aspeed_adc_engine_write(uint32_t engine_id, uint64_t addr, uint64_t value) "engine[%u] 0x%" PRIx64 " 0x%" PRIx64
>> diff --git a/include/hw/adc/aspeed_adc.h b/include/hw/adc/aspeed_adc.h
>> new file mode 100644
>> index 0000000000..2f166e8be1
>> --- /dev/null
>> +++ b/include/hw/adc/aspeed_adc.h
>> @@ -0,0 +1,55 @@
>> +/*
>> + * Aspeed ADC
>> + *
>> + * Copyright 2017-2021 IBM Corp.
>> + *
>> + * Andrew Jeffery <andrew@aj.id.au>
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#ifndef HW_ADC_ASPEED_ADC_H
>> +#define HW_ADC_ASPEED_ADC_H
>> +
>> +#include "hw/sysbus.h"
>> +
>> +#define TYPE_ASPEED_ADC "aspeed.adc"
>> +#define TYPE_ASPEED_2400_ADC TYPE_ASPEED_ADC "-ast2400"
>> +#define TYPE_ASPEED_2500_ADC TYPE_ASPEED_ADC "-ast2500"
>> +#define TYPE_ASPEED_2600_ADC TYPE_ASPEED_ADC "-ast2600"
>> +OBJECT_DECLARE_TYPE(AspeedADCState, AspeedADCClass, ASPEED_ADC)
>> +
>> +#define TYPE_ASPEED_ADC_ENGINE "aspeed.adc.engine"
>> +OBJECT_DECLARE_SIMPLE_TYPE(AspeedADCEngineState, ASPEED_ADC_ENGINE)
>> +
>> +#define ASPEED_ADC_NR_CHANNELS 16
>> +#define ASPEED_ADC_NR_REGS     (0xD0 >> 2)
>> +
>> +struct AspeedADCEngineState {
>> +    /* <private> */
>> +    SysBusDevice parent;
>> +
>> +    MemoryRegion mmio;
>> +    qemu_irq irq;
>> +    uint32_t engine_id;
>> +    uint32_t nr_channels;
>> +    uint32_t regs[ASPEED_ADC_NR_REGS];
>> +};
>> +
>> +struct AspeedADCState {
>> +    /* <private> */
>> +    SysBusDevice parent;
>> +
>> +    MemoryRegion mmio;
>> +    qemu_irq irq;
>> +
>> +    AspeedADCEngineState engines[2];
>> +};
>> +
>> +struct AspeedADCClass {
>> +    SysBusDeviceClass parent_class;
>> +
>> +    uint32_t nr_engines;
>> +};
>> +
>> +#endif /* HW_ADC_ASPEED_ADC_H */
>
Cédric Le Goater Oct. 5, 2021, 7:33 a.m. UTC | #3
On 10/5/21 07:31, Peter Delevoryas wrote:
> 
> 
>> On Oct 4, 2021, at 12:49 AM, Cédric Le Goater <clg@kaod.org> wrote:
>>
>> On 10/3/21 21:18, pdel@fb.com wrote:
>>> From: Andrew Jeffery <andrew@aj.id.au>
>>> This model implements enough behaviour to do basic functionality tests
>>> such as device initialisation and read out of dummy sample values. The
>>> sample value generation strategy is similar to the STM ADC already in
>>> the tree.
>>> Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
>>> [clg : support for multiple engines (AST2600) ]
>>> Signed-off-by: Cédric Le Goater <clg@kaod.org>
>>> [pdel : refactored engine register struct fields to regs[] array field]
>>> [pdel : added guest-error checking for upper-8 channel regs in AST2600]
>>> Signed-off-by: Peter Delevoryas <pdel@fb.com>
>>
>> Reviewed-by: Cédric Le Goater <clg@kaod.org>
>>
>> Thanks,
>>
>> C.
> 
> Hey Cedric,
> 
> Actually, I have just submitted a v3 of this patch series to support 16-bit
> reads of the channel data registers. I don’t think I tested using the driver
> to read from the ADC, and that’s what Patrick found crashed with these
> changes. Since it’s relatively easy to enable 16-bit reads, I figured
> I would just include that.

OK.

A Tested-by: tag would be welcome !

Thanks,

C.
diff mbox series

Patch

diff --git a/hw/adc/aspeed_adc.c b/hw/adc/aspeed_adc.c
new file mode 100644
index 0000000000..fcd93d6853
--- /dev/null
+++ b/hw/adc/aspeed_adc.c
@@ -0,0 +1,422 @@ 
+/*
+ * Aspeed ADC
+ *
+ * Copyright 2017-2021 IBM Corp.
+ *
+ * Andrew Jeffery <andrew@aj.id.au>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/log.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "hw/adc/aspeed_adc.h"
+#include "trace.h"
+
+#define ASPEED_ADC_MEMORY_REGION_SIZE           0x1000
+#define ASPEED_ADC_ENGINE_MEMORY_REGION_SIZE    0x100
+#define  ASPEED_ADC_ENGINE_CH_EN_MASK           0xffff0000
+#define   ASPEED_ADC_ENGINE_CH_EN(x)            ((BIT(x)) << 16)
+#define  ASPEED_ADC_ENGINE_INIT                 BIT(8)
+#define  ASPEED_ADC_ENGINE_AUTO_COMP            BIT(5)
+#define  ASPEED_ADC_ENGINE_COMP                 BIT(4)
+#define  ASPEED_ADC_ENGINE_MODE_MASK            0x0000000e
+#define   ASPEED_ADC_ENGINE_MODE_OFF            (0b000 << 1)
+#define   ASPEED_ADC_ENGINE_MODE_STANDBY        (0b001 << 1)
+#define   ASPEED_ADC_ENGINE_MODE_NORMAL         (0b111 << 1)
+#define  ASPEED_ADC_ENGINE_EN                   BIT(0)
+#define ASPEED_ADC_HYST_EN                      BIT(31)
+
+#define ASPEED_ADC_L_MASK       ((1 << 10) - 1)
+#define ASPEED_ADC_L(x)         ((x) & ASPEED_ADC_L_MASK)
+#define ASPEED_ADC_H(x)         (((x) >> 16) & ASPEED_ADC_L_MASK)
+#define ASPEED_ADC_LH_MASK      (ASPEED_ADC_L_MASK << 16 | ASPEED_ADC_L_MASK)
+#define LOWER_CHANNEL_MASK      ((1 << 10) - 1)
+#define LOWER_CHANNEL_DATA(x)   ((x) & LOWER_CHANNEL_MASK)
+#define UPPER_CHANNEL_DATA(x)   (((x) >> 16) & LOWER_CHANNEL_MASK)
+
+#define TO_REG(addr) (addr >> 2)
+
+#define ENGINE_CONTROL              TO_REG(0x00)
+#define INTERRUPT_CONTROL           TO_REG(0x04)
+#define VGA_DETECT_CONTROL          TO_REG(0x08)
+#define CLOCK_CONTROL               TO_REG(0x0C)
+#define DATA_CHANNEL_1_AND_0        TO_REG(0x10)
+#define DATA_CHANNEL_7_AND_6        TO_REG(0x1C)
+#define DATA_CHANNEL_9_AND_8        TO_REG(0x20)
+#define DATA_CHANNEL_15_AND_14      TO_REG(0x2C)
+#define BOUNDS_CHANNEL_0            TO_REG(0x30)
+#define BOUNDS_CHANNEL_7            TO_REG(0x4C)
+#define BOUNDS_CHANNEL_8            TO_REG(0x50)
+#define BOUNDS_CHANNEL_15           TO_REG(0x6C)
+#define HYSTERESIS_CHANNEL_0        TO_REG(0x70)
+#define HYSTERESIS_CHANNEL_7        TO_REG(0x8C)
+#define HYSTERESIS_CHANNEL_8        TO_REG(0x90)
+#define HYSTERESIS_CHANNEL_15       TO_REG(0xAC)
+#define INTERRUPT_SOURCE            TO_REG(0xC0)
+#define COMPENSATING_AND_TRIMMING   TO_REG(0xC4)
+
+static inline uint32_t update_channels(uint32_t current)
+{
+    return ((((current >> 16) & ASPEED_ADC_L_MASK) + 7) << 16) |
+        ((current + 5) & ASPEED_ADC_L_MASK);
+}
+
+static bool breaks_threshold(AspeedADCEngineState *s, int reg)
+{
+    assert(reg >= DATA_CHANNEL_1_AND_0 &&
+           reg < DATA_CHANNEL_1_AND_0 + s->nr_channels / 2);
+
+    int a_bounds_reg = BOUNDS_CHANNEL_0 + (reg - DATA_CHANNEL_1_AND_0) * 2;
+    int b_bounds_reg = a_bounds_reg + 1;
+    uint32_t a_and_b = s->regs[reg];
+    uint32_t a_bounds = s->regs[a_bounds_reg];
+    uint32_t b_bounds = s->regs[b_bounds_reg];
+    uint32_t a = ASPEED_ADC_L(a_and_b);
+    uint32_t b = ASPEED_ADC_H(a_and_b);
+    uint32_t a_lower = ASPEED_ADC_L(a_bounds);
+    uint32_t a_upper = ASPEED_ADC_H(a_bounds);
+    uint32_t b_lower = ASPEED_ADC_L(b_bounds);
+    uint32_t b_upper = ASPEED_ADC_H(b_bounds);
+
+    return (a < a_lower || a > a_upper) ||
+           (b < b_lower || b > b_upper);
+}
+
+static uint32_t read_channel_sample(AspeedADCEngineState *s, int reg)
+{
+    assert(reg >= DATA_CHANNEL_1_AND_0 &&
+           reg < DATA_CHANNEL_1_AND_0 + s->nr_channels / 2);
+
+    /* Poor man's sampling */
+    uint32_t value = s->regs[reg];
+    s->regs[reg] = update_channels(s->regs[reg]);
+
+    if (breaks_threshold(s, reg)) {
+        s->regs[INTERRUPT_CONTROL] |= BIT(reg - DATA_CHANNEL_1_AND_0);
+        qemu_irq_raise(s->irq);
+    }
+
+    return value;
+}
+
+static uint64_t aspeed_adc_engine_read(void *opaque, hwaddr addr,
+                                       unsigned int size)
+{
+    AspeedADCEngineState *s = ASPEED_ADC_ENGINE(opaque);
+    int reg = TO_REG(addr);
+    uint32_t value = 0;
+
+    switch (reg) {
+    case BOUNDS_CHANNEL_8 ... BOUNDS_CHANNEL_15:
+        if (s->nr_channels <= 8) {
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: engine[%u]: "
+                          "bounds register %u invalid, only 0...7 valid\n",
+                          __func__, s->engine_id, reg - BOUNDS_CHANNEL_0);
+            break;
+        }
+        /* fallthrough */
+    case HYSTERESIS_CHANNEL_8 ... HYSTERESIS_CHANNEL_15:
+        if (s->nr_channels <= 8) {
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: engine[%u]: "
+                          "hysteresis register %u invalid, only 0...7 valid\n",
+                          __func__, s->engine_id, reg - HYSTERESIS_CHANNEL_0);
+            break;
+        }
+        /* fallthrough */
+    case BOUNDS_CHANNEL_0 ... BOUNDS_CHANNEL_7:
+    case HYSTERESIS_CHANNEL_0 ... HYSTERESIS_CHANNEL_7:
+    case ENGINE_CONTROL:
+    case INTERRUPT_CONTROL:
+    case VGA_DETECT_CONTROL:
+    case CLOCK_CONTROL:
+    case INTERRUPT_SOURCE:
+    case COMPENSATING_AND_TRIMMING:
+        value = s->regs[reg];
+        break;
+    case DATA_CHANNEL_9_AND_8 ... DATA_CHANNEL_15_AND_14:
+        if (s->nr_channels <= 8) {
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: engine[%u]: "
+                          "data register %u invalid, only 0...3 valid\n",
+                          __func__, s->engine_id, reg - DATA_CHANNEL_1_AND_0);
+            break;
+        }
+        /* fallthrough */
+    case DATA_CHANNEL_1_AND_0 ... DATA_CHANNEL_7_AND_6:
+        value = read_channel_sample(s, reg);
+        break;
+    default:
+        qemu_log_mask(LOG_UNIMP, "%s: engine[%u]: 0x%" HWADDR_PRIx "\n",
+                      __func__, s->engine_id, addr);
+        break;
+    }
+
+    trace_aspeed_adc_engine_read(s->engine_id, addr, value);
+    return value;
+}
+
+static void aspeed_adc_engine_write(void *opaque, hwaddr addr, uint64_t value,
+                                    unsigned int size)
+{
+    AspeedADCEngineState *s = ASPEED_ADC_ENGINE(opaque);
+    int reg = TO_REG(addr);
+    uint32_t init = 0;
+
+    trace_aspeed_adc_engine_write(s->engine_id, addr, value);
+
+    switch (reg) {
+    case ENGINE_CONTROL:
+        init = !!(value & ASPEED_ADC_ENGINE_EN);
+        init *= ASPEED_ADC_ENGINE_INIT;
+
+        value &= ~ASPEED_ADC_ENGINE_INIT;
+        value |= init;
+
+        value &= ~ASPEED_ADC_ENGINE_AUTO_COMP;
+        break;
+    case INTERRUPT_CONTROL:
+    case VGA_DETECT_CONTROL:
+    case CLOCK_CONTROL:
+        break;
+    case DATA_CHANNEL_9_AND_8 ... DATA_CHANNEL_15_AND_14:
+        if (s->nr_channels <= 8) {
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: engine[%u]: "
+                          "data register %u invalid, only 0...3 valid\n",
+                          __func__, s->engine_id, reg - DATA_CHANNEL_1_AND_0);
+            return;
+        }
+        /* fallthrough */
+    case BOUNDS_CHANNEL_8 ... BOUNDS_CHANNEL_15:
+        if (s->nr_channels <= 8) {
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: engine[%u]: "
+                          "bounds register %u invalid, only 0...7 valid\n",
+                          __func__, s->engine_id, reg - BOUNDS_CHANNEL_0);
+            return;
+        }
+        /* fallthrough */
+    case DATA_CHANNEL_1_AND_0 ... DATA_CHANNEL_7_AND_6:
+    case BOUNDS_CHANNEL_0 ... BOUNDS_CHANNEL_7:
+        value &= ASPEED_ADC_LH_MASK;
+        break;
+    case HYSTERESIS_CHANNEL_8 ... HYSTERESIS_CHANNEL_15:
+        if (s->nr_channels <= 8) {
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: engine[%u]: "
+                          "hysteresis register %u invalid, only 0...7 valid\n",
+                          __func__, s->engine_id, reg - HYSTERESIS_CHANNEL_0);
+            return;
+        }
+        /* fallthrough */
+    case HYSTERESIS_CHANNEL_0 ... HYSTERESIS_CHANNEL_7:
+        value &= (ASPEED_ADC_HYST_EN | ASPEED_ADC_LH_MASK);
+        break;
+    case INTERRUPT_SOURCE:
+        value &= 0xffff;
+        break;
+    case COMPENSATING_AND_TRIMMING:
+        value &= 0xf;
+        break;
+    default:
+        qemu_log_mask(LOG_UNIMP, "%s: engine[%u]: "
+                      "0x%" HWADDR_PRIx " 0x%" PRIx64 "\n",
+                      __func__, s->engine_id, addr, value);
+        break;
+    }
+
+    s->regs[reg] = value;
+}
+
+static const MemoryRegionOps aspeed_adc_engine_ops = {
+    .read = aspeed_adc_engine_read,
+    .write = aspeed_adc_engine_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false,
+    },
+};
+
+static const uint32_t aspeed_adc_resets[ASPEED_ADC_NR_REGS] = {
+    [ENGINE_CONTROL]     = 0x00000000,
+    [INTERRUPT_CONTROL]  = 0x00000000,
+    [VGA_DETECT_CONTROL] = 0x0000000f,
+    [CLOCK_CONTROL]      = 0x0000000f,
+};
+
+static void aspeed_adc_engine_reset(DeviceState *dev)
+{
+    AspeedADCEngineState *s = ASPEED_ADC_ENGINE(dev);
+
+    memcpy(s->regs, aspeed_adc_resets, sizeof(aspeed_adc_resets));
+}
+
+static void aspeed_adc_engine_realize(DeviceState *dev, Error **errp)
+{
+    AspeedADCEngineState *s = ASPEED_ADC_ENGINE(dev);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+    g_autofree char *name = g_strdup_printf(TYPE_ASPEED_ADC_ENGINE ".%d",
+                                            s->engine_id);
+
+    assert(s->engine_id < 2);
+
+    sysbus_init_irq(sbd, &s->irq);
+
+    memory_region_init_io(&s->mmio, OBJECT(s), &aspeed_adc_engine_ops, s, name,
+                          ASPEED_ADC_ENGINE_MEMORY_REGION_SIZE);
+
+    sysbus_init_mmio(sbd, &s->mmio);
+}
+
+static const VMStateDescription vmstate_aspeed_adc_engine = {
+    .name = TYPE_ASPEED_ADC,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(regs, AspeedADCEngineState, ASPEED_ADC_NR_REGS),
+        VMSTATE_END_OF_LIST(),
+    }
+};
+
+static Property aspeed_adc_engine_properties[] = {
+    DEFINE_PROP_UINT32("engine-id", AspeedADCEngineState, engine_id, 0),
+    DEFINE_PROP_UINT32("nr-channels", AspeedADCEngineState, nr_channels, 0),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void aspeed_adc_engine_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->realize = aspeed_adc_engine_realize;
+    dc->reset = aspeed_adc_engine_reset;
+    device_class_set_props(dc, aspeed_adc_engine_properties);
+    dc->desc = "Aspeed Analog-to-Digital Engine";
+    dc->vmsd = &vmstate_aspeed_adc_engine;
+}
+
+static const TypeInfo aspeed_adc_engine_info = {
+    .name = TYPE_ASPEED_ADC_ENGINE,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(AspeedADCEngineState),
+    .class_init = aspeed_adc_engine_class_init,
+};
+
+static void aspeed_adc_instance_init(Object *obj)
+{
+    AspeedADCState *s = ASPEED_ADC(obj);
+    AspeedADCClass *aac = ASPEED_ADC_GET_CLASS(obj);
+    uint32_t nr_channels = ASPEED_ADC_NR_CHANNELS / aac->nr_engines;
+
+    for (int i = 0; i < aac->nr_engines; i++) {
+        AspeedADCEngineState *engine = &s->engines[i];
+        object_initialize_child(obj, "engine[*]", engine,
+                                TYPE_ASPEED_ADC_ENGINE);
+        qdev_prop_set_uint32(DEVICE(engine), "engine-id", i);
+        qdev_prop_set_uint32(DEVICE(engine), "nr-channels", nr_channels);
+    }
+}
+
+static void aspeed_adc_set_irq(void *opaque, int n, int level)
+{
+    AspeedADCState *s = opaque;
+    AspeedADCClass *aac = ASPEED_ADC_GET_CLASS(s);
+    uint32_t pending = 0;
+
+    /* TODO: update Global IRQ status register on AST2600 (Need specs) */
+    for (int i = 0; i < aac->nr_engines; i++) {
+        uint32_t irq_status = s->engines[i].regs[INTERRUPT_CONTROL] & 0xFF;
+        pending |= irq_status << (i * 8);
+    }
+
+    qemu_set_irq(s->irq, !!pending);
+}
+
+static void aspeed_adc_realize(DeviceState *dev, Error **errp)
+{
+    AspeedADCState *s = ASPEED_ADC(dev);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+    AspeedADCClass *aac = ASPEED_ADC_GET_CLASS(dev);
+
+    qdev_init_gpio_in_named_with_opaque(DEVICE(sbd), aspeed_adc_set_irq,
+                                        s, NULL, aac->nr_engines);
+
+    sysbus_init_irq(sbd, &s->irq);
+
+    memory_region_init(&s->mmio, OBJECT(s), TYPE_ASPEED_ADC,
+                       ASPEED_ADC_MEMORY_REGION_SIZE);
+
+    sysbus_init_mmio(sbd, &s->mmio);
+
+    for (int i = 0; i < aac->nr_engines; i++) {
+        Object *eng = OBJECT(&s->engines[i]);
+
+        if (!sysbus_realize(SYS_BUS_DEVICE(eng), errp)) {
+            return;
+        }
+        sysbus_connect_irq(SYS_BUS_DEVICE(eng), 0,
+                           qdev_get_gpio_in(DEVICE(sbd), i));
+        memory_region_add_subregion(&s->mmio,
+                                    i * ASPEED_ADC_ENGINE_MEMORY_REGION_SIZE,
+                                    &s->engines[i].mmio);
+    }
+}
+
+static void aspeed_adc_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    AspeedADCClass *aac = ASPEED_ADC_CLASS(klass);
+
+    dc->realize = aspeed_adc_realize;
+    dc->desc = "Aspeed Analog-to-Digital Converter";
+    aac->nr_engines = 1;
+}
+
+static void aspeed_2600_adc_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    AspeedADCClass *aac = ASPEED_ADC_CLASS(klass);
+
+    dc->desc = "ASPEED 2600 ADC Controller";
+    aac->nr_engines = 2;
+}
+
+static const TypeInfo aspeed_adc_info = {
+    .name = TYPE_ASPEED_ADC,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_init = aspeed_adc_instance_init,
+    .instance_size = sizeof(AspeedADCState),
+    .class_init = aspeed_adc_class_init,
+    .class_size = sizeof(AspeedADCClass),
+    .abstract   = true,
+};
+
+static const TypeInfo aspeed_2400_adc_info = {
+    .name = TYPE_ASPEED_2400_ADC,
+    .parent = TYPE_ASPEED_ADC,
+};
+
+static const TypeInfo aspeed_2500_adc_info = {
+    .name = TYPE_ASPEED_2500_ADC,
+    .parent = TYPE_ASPEED_ADC,
+};
+
+static const TypeInfo aspeed_2600_adc_info = {
+    .name = TYPE_ASPEED_2600_ADC,
+    .parent = TYPE_ASPEED_ADC,
+    .class_init = aspeed_2600_adc_class_init,
+};
+
+static void aspeed_adc_register_types(void)
+{
+    type_register_static(&aspeed_adc_engine_info);
+    type_register_static(&aspeed_adc_info);
+    type_register_static(&aspeed_2400_adc_info);
+    type_register_static(&aspeed_2500_adc_info);
+    type_register_static(&aspeed_2600_adc_info);
+}
+
+type_init(aspeed_adc_register_types);
diff --git a/hw/adc/meson.build b/hw/adc/meson.build
index ac4f093fea..b29ac7ccdf 100644
--- a/hw/adc/meson.build
+++ b/hw/adc/meson.build
@@ -1,4 +1,5 @@ 
 softmmu_ss.add(when: 'CONFIG_STM32F2XX_ADC', if_true: files('stm32f2xx_adc.c'))
+softmmu_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_adc.c'))
 softmmu_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_adc.c'))
 softmmu_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq-xadc.c'))
 softmmu_ss.add(when: 'CONFIG_MAX111X', if_true: files('max111x.c'))
diff --git a/hw/adc/trace-events b/hw/adc/trace-events
index 456f21c8f4..5a4c444d77 100644
--- a/hw/adc/trace-events
+++ b/hw/adc/trace-events
@@ -3,3 +3,6 @@ 
 # npcm7xx_adc.c
 npcm7xx_adc_read(const char *id, uint64_t offset, uint32_t value) " %s offset: 0x%04" PRIx64 " value 0x%04" PRIx32
 npcm7xx_adc_write(const char *id, uint64_t offset, uint32_t value) "%s offset: 0x%04" PRIx64 " value 0x%04" PRIx32
+
+aspeed_adc_engine_read(uint32_t engine_id, uint64_t addr, uint64_t value) "engine[%u] 0x%" PRIx64 " 0x%" PRIx64
+aspeed_adc_engine_write(uint32_t engine_id, uint64_t addr, uint64_t value) "engine[%u] 0x%" PRIx64 " 0x%" PRIx64
diff --git a/include/hw/adc/aspeed_adc.h b/include/hw/adc/aspeed_adc.h
new file mode 100644
index 0000000000..2f166e8be1
--- /dev/null
+++ b/include/hw/adc/aspeed_adc.h
@@ -0,0 +1,55 @@ 
+/*
+ * Aspeed ADC
+ *
+ * Copyright 2017-2021 IBM Corp.
+ *
+ * Andrew Jeffery <andrew@aj.id.au>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_ADC_ASPEED_ADC_H
+#define HW_ADC_ASPEED_ADC_H
+
+#include "hw/sysbus.h"
+
+#define TYPE_ASPEED_ADC "aspeed.adc"
+#define TYPE_ASPEED_2400_ADC TYPE_ASPEED_ADC "-ast2400"
+#define TYPE_ASPEED_2500_ADC TYPE_ASPEED_ADC "-ast2500"
+#define TYPE_ASPEED_2600_ADC TYPE_ASPEED_ADC "-ast2600"
+OBJECT_DECLARE_TYPE(AspeedADCState, AspeedADCClass, ASPEED_ADC)
+
+#define TYPE_ASPEED_ADC_ENGINE "aspeed.adc.engine"
+OBJECT_DECLARE_SIMPLE_TYPE(AspeedADCEngineState, ASPEED_ADC_ENGINE)
+
+#define ASPEED_ADC_NR_CHANNELS 16
+#define ASPEED_ADC_NR_REGS     (0xD0 >> 2)
+
+struct AspeedADCEngineState {
+    /* <private> */
+    SysBusDevice parent;
+
+    MemoryRegion mmio;
+    qemu_irq irq;
+    uint32_t engine_id;
+    uint32_t nr_channels;
+    uint32_t regs[ASPEED_ADC_NR_REGS];
+};
+
+struct AspeedADCState {
+    /* <private> */
+    SysBusDevice parent;
+
+    MemoryRegion mmio;
+    qemu_irq irq;
+
+    AspeedADCEngineState engines[2];
+};
+
+struct AspeedADCClass {
+    SysBusDeviceClass parent_class;
+
+    uint32_t nr_engines;
+};
+
+#endif /* HW_ADC_ASPEED_ADC_H */