xref: /openbmc/qemu/hw/adc/npcm7xx_adc.c (revision 83baec642a13a69398a2643a1f905606c13cd363)
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