177c05b0bSHao Wu /*
277c05b0bSHao Wu * Nuvoton NPCM7xx ADC Module
377c05b0bSHao Wu *
477c05b0bSHao Wu * Copyright 2020 Google LLC
577c05b0bSHao Wu *
677c05b0bSHao Wu * This program is free software; you can redistribute it and/or modify it
777c05b0bSHao Wu * under the terms of the GNU General Public License as published by the
877c05b0bSHao Wu * Free Software Foundation; either version 2 of the License, or
977c05b0bSHao Wu * (at your option) any later version.
1077c05b0bSHao Wu *
1177c05b0bSHao Wu * This program is distributed in the hope that it will be useful, but WITHOUT
1277c05b0bSHao Wu * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1377c05b0bSHao Wu * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
1477c05b0bSHao Wu * for more details.
1577c05b0bSHao Wu */
1677c05b0bSHao Wu
1777c05b0bSHao Wu #include "qemu/osdep.h"
1877c05b0bSHao Wu #include "hw/adc/npcm7xx_adc.h"
1977c05b0bSHao Wu #include "hw/qdev-clock.h"
2077c05b0bSHao Wu #include "hw/qdev-properties.h"
2177c05b0bSHao Wu #include "hw/registerfields.h"
2277c05b0bSHao Wu #include "migration/vmstate.h"
2377c05b0bSHao Wu #include "qemu/log.h"
2477c05b0bSHao Wu #include "qemu/module.h"
2577c05b0bSHao Wu #include "qemu/timer.h"
2677c05b0bSHao Wu #include "qemu/units.h"
2777c05b0bSHao Wu #include "trace.h"
2877c05b0bSHao Wu
2977c05b0bSHao Wu REG32(NPCM7XX_ADC_CON, 0x0)
3077c05b0bSHao Wu REG32(NPCM7XX_ADC_DATA, 0x4)
3177c05b0bSHao Wu
3277c05b0bSHao Wu /* Register field definitions. */
3377c05b0bSHao Wu #define NPCM7XX_ADC_CON_MUX(rv) extract32(rv, 24, 4)
3477c05b0bSHao Wu #define NPCM7XX_ADC_CON_INT_EN BIT(21)
3577c05b0bSHao Wu #define NPCM7XX_ADC_CON_REFSEL BIT(19)
3677c05b0bSHao Wu #define NPCM7XX_ADC_CON_INT BIT(18)
3777c05b0bSHao Wu #define NPCM7XX_ADC_CON_EN BIT(17)
3877c05b0bSHao Wu #define NPCM7XX_ADC_CON_RST BIT(16)
394a84e854SHao Wu #define NPCM7XX_ADC_CON_CONV BIT(13)
4077c05b0bSHao Wu #define NPCM7XX_ADC_CON_DIV(rv) extract32(rv, 1, 8)
4177c05b0bSHao Wu
4277c05b0bSHao Wu #define NPCM7XX_ADC_MAX_RESULT 1023
4377c05b0bSHao Wu #define NPCM7XX_ADC_DEFAULT_IREF 2000000
4477c05b0bSHao Wu #define NPCM7XX_ADC_CONV_CYCLES 20
4577c05b0bSHao Wu #define NPCM7XX_ADC_RESET_CYCLES 10
4677c05b0bSHao Wu #define NPCM7XX_ADC_R0_INPUT 500000
4777c05b0bSHao Wu #define NPCM7XX_ADC_R1_INPUT 1500000
4877c05b0bSHao Wu
npcm7xx_adc_reset(NPCM7xxADCState * s)4977c05b0bSHao Wu static void npcm7xx_adc_reset(NPCM7xxADCState *s)
5077c05b0bSHao Wu {
5177c05b0bSHao Wu timer_del(&s->conv_timer);
5277c05b0bSHao Wu s->con = 0x000c0001;
5377c05b0bSHao Wu s->data = 0x00000000;
5477c05b0bSHao Wu }
5577c05b0bSHao Wu
npcm7xx_adc_convert(uint32_t input,uint32_t ref)5677c05b0bSHao Wu static uint32_t npcm7xx_adc_convert(uint32_t input, uint32_t ref)
5777c05b0bSHao Wu {
5877c05b0bSHao Wu uint32_t result;
5977c05b0bSHao Wu
6077c05b0bSHao Wu result = input * (NPCM7XX_ADC_MAX_RESULT + 1) / ref;
6177c05b0bSHao Wu if (result > NPCM7XX_ADC_MAX_RESULT) {
6277c05b0bSHao Wu result = NPCM7XX_ADC_MAX_RESULT;
6377c05b0bSHao Wu }
6477c05b0bSHao Wu
6577c05b0bSHao Wu return result;
6677c05b0bSHao Wu }
6777c05b0bSHao Wu
npcm7xx_adc_prescaler(NPCM7xxADCState * s)6877c05b0bSHao Wu static uint32_t npcm7xx_adc_prescaler(NPCM7xxADCState *s)
6977c05b0bSHao Wu {
7077c05b0bSHao Wu return 2 * (NPCM7XX_ADC_CON_DIV(s->con) + 1);
7177c05b0bSHao Wu }
7277c05b0bSHao Wu
npcm7xx_adc_start_timer(Clock * clk,QEMUTimer * timer,uint32_t cycles,uint32_t prescaler)7377c05b0bSHao Wu static void npcm7xx_adc_start_timer(Clock *clk, QEMUTimer *timer,
7477c05b0bSHao Wu uint32_t cycles, uint32_t prescaler)
7577c05b0bSHao Wu {
7677c05b0bSHao Wu int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
7777c05b0bSHao Wu int64_t ticks = cycles;
7877c05b0bSHao Wu int64_t ns;
7977c05b0bSHao Wu
8077c05b0bSHao Wu ticks *= prescaler;
8177c05b0bSHao Wu ns = clock_ticks_to_ns(clk, ticks);
8277c05b0bSHao Wu ns += now;
8377c05b0bSHao Wu timer_mod(timer, ns);
8477c05b0bSHao Wu }
8577c05b0bSHao Wu
npcm7xx_adc_start_convert(NPCM7xxADCState * s)8677c05b0bSHao Wu static void npcm7xx_adc_start_convert(NPCM7xxADCState *s)
8777c05b0bSHao Wu {
8877c05b0bSHao Wu uint32_t prescaler = npcm7xx_adc_prescaler(s);
8977c05b0bSHao Wu
9077c05b0bSHao Wu npcm7xx_adc_start_timer(s->clock, &s->conv_timer, NPCM7XX_ADC_CONV_CYCLES,
9177c05b0bSHao Wu prescaler);
9277c05b0bSHao Wu }
9377c05b0bSHao Wu
npcm7xx_adc_convert_done(void * opaque)9477c05b0bSHao Wu static void npcm7xx_adc_convert_done(void *opaque)
9577c05b0bSHao Wu {
9677c05b0bSHao Wu NPCM7xxADCState *s = opaque;
9777c05b0bSHao Wu uint32_t input = NPCM7XX_ADC_CON_MUX(s->con);
9877c05b0bSHao Wu uint32_t ref = (s->con & NPCM7XX_ADC_CON_REFSEL)
9977c05b0bSHao Wu ? s->iref : s->vref;
10077c05b0bSHao Wu
10177c05b0bSHao Wu if (input >= NPCM7XX_ADC_NUM_INPUTS) {
10277c05b0bSHao Wu qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid input: %u\n",
10377c05b0bSHao Wu __func__, input);
10477c05b0bSHao Wu return;
10577c05b0bSHao Wu }
10677c05b0bSHao Wu s->data = npcm7xx_adc_convert(s->adci[input], ref);
10777c05b0bSHao Wu if (s->con & NPCM7XX_ADC_CON_INT_EN) {
10877c05b0bSHao Wu s->con |= NPCM7XX_ADC_CON_INT;
10977c05b0bSHao Wu qemu_irq_raise(s->irq);
11077c05b0bSHao Wu }
11177c05b0bSHao Wu s->con &= ~NPCM7XX_ADC_CON_CONV;
11277c05b0bSHao Wu }
11377c05b0bSHao Wu
npcm7xx_adc_calibrate(NPCM7xxADCState * adc)11477c05b0bSHao Wu static void npcm7xx_adc_calibrate(NPCM7xxADCState *adc)
11577c05b0bSHao Wu {
11677c05b0bSHao Wu adc->calibration_r_values[0] = npcm7xx_adc_convert(NPCM7XX_ADC_R0_INPUT,
11777c05b0bSHao Wu adc->iref);
11877c05b0bSHao Wu adc->calibration_r_values[1] = npcm7xx_adc_convert(NPCM7XX_ADC_R1_INPUT,
11977c05b0bSHao Wu adc->iref);
12077c05b0bSHao Wu }
12177c05b0bSHao Wu
npcm7xx_adc_write_con(NPCM7xxADCState * s,uint32_t new_con)12277c05b0bSHao Wu static void npcm7xx_adc_write_con(NPCM7xxADCState *s, uint32_t new_con)
12377c05b0bSHao Wu {
12477c05b0bSHao Wu uint32_t old_con = s->con;
12577c05b0bSHao Wu
12677c05b0bSHao Wu /* Write ADC_INT to 1 to clear it */
12777c05b0bSHao Wu if (new_con & NPCM7XX_ADC_CON_INT) {
12877c05b0bSHao Wu new_con &= ~NPCM7XX_ADC_CON_INT;
12977c05b0bSHao Wu qemu_irq_lower(s->irq);
13077c05b0bSHao Wu } else if (old_con & NPCM7XX_ADC_CON_INT) {
13177c05b0bSHao Wu new_con |= NPCM7XX_ADC_CON_INT;
13277c05b0bSHao Wu }
13377c05b0bSHao Wu
13477c05b0bSHao Wu s->con = new_con;
13577c05b0bSHao Wu
13677c05b0bSHao Wu if (s->con & NPCM7XX_ADC_CON_RST) {
13777c05b0bSHao Wu npcm7xx_adc_reset(s);
13877c05b0bSHao Wu return;
13977c05b0bSHao Wu }
14077c05b0bSHao Wu
14177c05b0bSHao Wu if ((s->con & NPCM7XX_ADC_CON_EN)) {
14277c05b0bSHao Wu if (s->con & NPCM7XX_ADC_CON_CONV) {
14377c05b0bSHao Wu if (!(old_con & NPCM7XX_ADC_CON_CONV)) {
14477c05b0bSHao Wu npcm7xx_adc_start_convert(s);
14577c05b0bSHao Wu }
14677c05b0bSHao Wu } else {
14777c05b0bSHao Wu timer_del(&s->conv_timer);
14877c05b0bSHao Wu }
14977c05b0bSHao Wu }
15077c05b0bSHao Wu }
15177c05b0bSHao Wu
npcm7xx_adc_read(void * opaque,hwaddr offset,unsigned size)15277c05b0bSHao Wu static uint64_t npcm7xx_adc_read(void *opaque, hwaddr offset, unsigned size)
15377c05b0bSHao Wu {
15477c05b0bSHao Wu uint64_t value = 0;
15577c05b0bSHao Wu NPCM7xxADCState *s = opaque;
15677c05b0bSHao Wu
15777c05b0bSHao Wu switch (offset) {
15877c05b0bSHao Wu case A_NPCM7XX_ADC_CON:
15977c05b0bSHao Wu value = s->con;
16077c05b0bSHao Wu break;
16177c05b0bSHao Wu
16277c05b0bSHao Wu case A_NPCM7XX_ADC_DATA:
16377c05b0bSHao Wu value = s->data;
16477c05b0bSHao Wu break;
16577c05b0bSHao Wu
16677c05b0bSHao Wu default:
16777c05b0bSHao Wu qemu_log_mask(LOG_GUEST_ERROR,
16877c05b0bSHao Wu "%s: invalid offset 0x%04" HWADDR_PRIx "\n",
16977c05b0bSHao Wu __func__, offset);
17077c05b0bSHao Wu break;
17177c05b0bSHao Wu }
17277c05b0bSHao Wu
17377c05b0bSHao Wu trace_npcm7xx_adc_read(DEVICE(s)->canonical_path, offset, value);
17477c05b0bSHao Wu return value;
17577c05b0bSHao Wu }
17677c05b0bSHao Wu
npcm7xx_adc_write(void * opaque,hwaddr offset,uint64_t v,unsigned size)17777c05b0bSHao Wu static void npcm7xx_adc_write(void *opaque, hwaddr offset, uint64_t v,
17877c05b0bSHao Wu unsigned size)
17977c05b0bSHao Wu {
18077c05b0bSHao Wu NPCM7xxADCState *s = opaque;
18177c05b0bSHao Wu
18277c05b0bSHao Wu trace_npcm7xx_adc_write(DEVICE(s)->canonical_path, offset, v);
18377c05b0bSHao Wu switch (offset) {
18477c05b0bSHao Wu case A_NPCM7XX_ADC_CON:
18577c05b0bSHao Wu npcm7xx_adc_write_con(s, v);
18677c05b0bSHao Wu break;
18777c05b0bSHao Wu
18877c05b0bSHao Wu case A_NPCM7XX_ADC_DATA:
18977c05b0bSHao Wu qemu_log_mask(LOG_GUEST_ERROR,
19077c05b0bSHao Wu "%s: register @ 0x%04" HWADDR_PRIx " is read-only\n",
19177c05b0bSHao Wu __func__, offset);
19277c05b0bSHao Wu break;
19377c05b0bSHao Wu
19477c05b0bSHao Wu default:
19577c05b0bSHao Wu qemu_log_mask(LOG_GUEST_ERROR,
19677c05b0bSHao Wu "%s: invalid offset 0x%04" HWADDR_PRIx "\n",
19777c05b0bSHao Wu __func__, offset);
19877c05b0bSHao Wu break;
19977c05b0bSHao Wu }
20077c05b0bSHao Wu
20177c05b0bSHao Wu }
20277c05b0bSHao Wu
20377c05b0bSHao Wu static const struct MemoryRegionOps npcm7xx_adc_ops = {
20477c05b0bSHao Wu .read = npcm7xx_adc_read,
20577c05b0bSHao Wu .write = npcm7xx_adc_write,
20677c05b0bSHao Wu .endianness = DEVICE_LITTLE_ENDIAN,
20777c05b0bSHao Wu .valid = {
20877c05b0bSHao Wu .min_access_size = 4,
20977c05b0bSHao Wu .max_access_size = 4,
21077c05b0bSHao Wu .unaligned = false,
21177c05b0bSHao Wu },
21277c05b0bSHao Wu };
21377c05b0bSHao Wu
npcm7xx_adc_enter_reset(Object * obj,ResetType type)21477c05b0bSHao Wu static void npcm7xx_adc_enter_reset(Object *obj, ResetType type)
21577c05b0bSHao Wu {
21677c05b0bSHao Wu NPCM7xxADCState *s = NPCM7XX_ADC(obj);
21777c05b0bSHao Wu
21877c05b0bSHao Wu npcm7xx_adc_reset(s);
21977c05b0bSHao Wu }
22077c05b0bSHao Wu
npcm7xx_adc_hold_reset(Object * obj,ResetType type)221*ad80e367SPeter Maydell static void npcm7xx_adc_hold_reset(Object *obj, ResetType type)
22277c05b0bSHao Wu {
22377c05b0bSHao Wu NPCM7xxADCState *s = NPCM7XX_ADC(obj);
22477c05b0bSHao Wu
22577c05b0bSHao Wu qemu_irq_lower(s->irq);
22677c05b0bSHao Wu }
22777c05b0bSHao Wu
npcm7xx_adc_init(Object * obj)22877c05b0bSHao Wu static void npcm7xx_adc_init(Object *obj)
22977c05b0bSHao Wu {
23077c05b0bSHao Wu NPCM7xxADCState *s = NPCM7XX_ADC(obj);
23177c05b0bSHao Wu SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
23277c05b0bSHao Wu int i;
23377c05b0bSHao Wu
23477c05b0bSHao Wu sysbus_init_irq(sbd, &s->irq);
23577c05b0bSHao Wu
23677c05b0bSHao Wu timer_init_ns(&s->conv_timer, QEMU_CLOCK_VIRTUAL,
23777c05b0bSHao Wu npcm7xx_adc_convert_done, s);
23877c05b0bSHao Wu memory_region_init_io(&s->iomem, obj, &npcm7xx_adc_ops, s,
23977c05b0bSHao Wu TYPE_NPCM7XX_ADC, 4 * KiB);
24077c05b0bSHao Wu sysbus_init_mmio(sbd, &s->iomem);
2415ee0abedSPeter Maydell s->clock = qdev_init_clock_in(DEVICE(s), "clock", NULL, NULL, 0);
24277c05b0bSHao Wu
24377c05b0bSHao Wu for (i = 0; i < NPCM7XX_ADC_NUM_INPUTS; ++i) {
24477c05b0bSHao Wu object_property_add_uint32_ptr(obj, "adci[*]",
24599638ba9SHao Wu &s->adci[i], OBJ_PROP_FLAG_READWRITE);
24677c05b0bSHao Wu }
24777c05b0bSHao Wu object_property_add_uint32_ptr(obj, "vref",
24877c05b0bSHao Wu &s->vref, OBJ_PROP_FLAG_WRITE);
24977c05b0bSHao Wu npcm7xx_adc_calibrate(s);
25077c05b0bSHao Wu }
25177c05b0bSHao Wu
25277c05b0bSHao Wu static const VMStateDescription vmstate_npcm7xx_adc = {
25377c05b0bSHao Wu .name = "npcm7xx-adc",
25477c05b0bSHao Wu .version_id = 0,
25577c05b0bSHao Wu .minimum_version_id = 0,
25699367627SRichard Henderson .fields = (const VMStateField[]) {
25777c05b0bSHao Wu VMSTATE_TIMER(conv_timer, NPCM7xxADCState),
25877c05b0bSHao Wu VMSTATE_UINT32(con, NPCM7xxADCState),
25977c05b0bSHao Wu VMSTATE_UINT32(data, NPCM7xxADCState),
26077c05b0bSHao Wu VMSTATE_CLOCK(clock, NPCM7xxADCState),
26177c05b0bSHao Wu VMSTATE_UINT32_ARRAY(adci, NPCM7xxADCState, NPCM7XX_ADC_NUM_INPUTS),
26277c05b0bSHao Wu VMSTATE_UINT32(vref, NPCM7xxADCState),
26377c05b0bSHao Wu VMSTATE_UINT32(iref, NPCM7xxADCState),
26477c05b0bSHao Wu VMSTATE_UINT16_ARRAY(calibration_r_values, NPCM7xxADCState,
26577c05b0bSHao Wu NPCM7XX_ADC_NUM_CALIB),
26677c05b0bSHao Wu VMSTATE_END_OF_LIST(),
26777c05b0bSHao Wu },
26877c05b0bSHao Wu };
26977c05b0bSHao Wu
27077c05b0bSHao Wu static Property npcm7xx_timer_properties[] = {
27177c05b0bSHao Wu DEFINE_PROP_UINT32("iref", NPCM7xxADCState, iref, NPCM7XX_ADC_DEFAULT_IREF),
27277c05b0bSHao Wu DEFINE_PROP_END_OF_LIST(),
27377c05b0bSHao Wu };
27477c05b0bSHao Wu
npcm7xx_adc_class_init(ObjectClass * klass,void * data)27577c05b0bSHao Wu static void npcm7xx_adc_class_init(ObjectClass *klass, void *data)
27677c05b0bSHao Wu {
27777c05b0bSHao Wu ResettableClass *rc = RESETTABLE_CLASS(klass);
27877c05b0bSHao Wu DeviceClass *dc = DEVICE_CLASS(klass);
27977c05b0bSHao Wu
28077c05b0bSHao Wu dc->desc = "NPCM7xx ADC Module";
28177c05b0bSHao Wu dc->vmsd = &vmstate_npcm7xx_adc;
28277c05b0bSHao Wu rc->phases.enter = npcm7xx_adc_enter_reset;
28377c05b0bSHao Wu rc->phases.hold = npcm7xx_adc_hold_reset;
28477c05b0bSHao Wu
28577c05b0bSHao Wu device_class_set_props(dc, npcm7xx_timer_properties);
28677c05b0bSHao Wu }
28777c05b0bSHao Wu
28877c05b0bSHao Wu static const TypeInfo npcm7xx_adc_info = {
28977c05b0bSHao Wu .name = TYPE_NPCM7XX_ADC,
29077c05b0bSHao Wu .parent = TYPE_SYS_BUS_DEVICE,
29177c05b0bSHao Wu .instance_size = sizeof(NPCM7xxADCState),
29277c05b0bSHao Wu .class_init = npcm7xx_adc_class_init,
29377c05b0bSHao Wu .instance_init = npcm7xx_adc_init,
29477c05b0bSHao Wu };
29577c05b0bSHao Wu
npcm7xx_adc_register_types(void)29677c05b0bSHao Wu static void npcm7xx_adc_register_types(void)
29777c05b0bSHao Wu {
29877c05b0bSHao Wu type_register_static(&npcm7xx_adc_info);
29977c05b0bSHao Wu }
30077c05b0bSHao Wu
30177c05b0bSHao Wu type_init(npcm7xx_adc_register_types);
302