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