| /* |
| * 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); |
| /* Allow 16-bit reads of the data registers */ |
| if (addr & 0x2) { |
| assert(size == 2); |
| value >>= 16; |
| } |
| 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 = 2, |
| .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); |