xref: /openbmc/qemu/hw/adc/npcm7xx_adc.c (revision 76eb88b1)
1 /*
2  * Nuvoton NPCM7xx ADC Module
3  *
4  * Copyright 2020 Google LLC
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14  * for more details.
15  */
16 
17 #include "qemu/osdep.h"
18 #include "hw/adc/npcm7xx_adc.h"
19 #include "hw/qdev-clock.h"
20 #include "hw/qdev-properties.h"
21 #include "hw/registerfields.h"
22 #include "migration/vmstate.h"
23 #include "qemu/log.h"
24 #include "qemu/module.h"
25 #include "qemu/timer.h"
26 #include "qemu/units.h"
27 #include "trace.h"
28 
29 REG32(NPCM7XX_ADC_CON, 0x0)
30 REG32(NPCM7XX_ADC_DATA, 0x4)
31 
32 /* Register field definitions. */
33 #define NPCM7XX_ADC_CON_MUX(rv) extract32(rv, 24, 4)
34 #define NPCM7XX_ADC_CON_INT_EN  BIT(21)
35 #define NPCM7XX_ADC_CON_REFSEL  BIT(19)
36 #define NPCM7XX_ADC_CON_INT     BIT(18)
37 #define NPCM7XX_ADC_CON_EN      BIT(17)
38 #define NPCM7XX_ADC_CON_RST     BIT(16)
39 #define NPCM7XX_ADC_CON_CONV    BIT(13)
40 #define NPCM7XX_ADC_CON_DIV(rv) extract32(rv, 1, 8)
41 
42 #define NPCM7XX_ADC_MAX_RESULT      1023
43 #define NPCM7XX_ADC_DEFAULT_IREF    2000000
44 #define NPCM7XX_ADC_CONV_CYCLES     20
45 #define NPCM7XX_ADC_RESET_CYCLES    10
46 #define NPCM7XX_ADC_R0_INPUT        500000
47 #define NPCM7XX_ADC_R1_INPUT        1500000
48 
49 static void npcm7xx_adc_reset(NPCM7xxADCState *s)
50 {
51     timer_del(&s->conv_timer);
52     s->con = 0x000c0001;
53     s->data = 0x00000000;
54 }
55 
56 static uint32_t npcm7xx_adc_convert(uint32_t input, uint32_t ref)
57 {
58     uint32_t result;
59 
60     result = input * (NPCM7XX_ADC_MAX_RESULT + 1) / ref;
61     if (result > NPCM7XX_ADC_MAX_RESULT) {
62         result = NPCM7XX_ADC_MAX_RESULT;
63     }
64 
65     return result;
66 }
67 
68 static uint32_t npcm7xx_adc_prescaler(NPCM7xxADCState *s)
69 {
70     return 2 * (NPCM7XX_ADC_CON_DIV(s->con) + 1);
71 }
72 
73 static void npcm7xx_adc_start_timer(Clock *clk, QEMUTimer *timer,
74         uint32_t cycles, uint32_t prescaler)
75 {
76     int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
77     int64_t ticks = cycles;
78     int64_t ns;
79 
80     ticks *= prescaler;
81     ns = clock_ticks_to_ns(clk, ticks);
82     ns += now;
83     timer_mod(timer, ns);
84 }
85 
86 static void npcm7xx_adc_start_convert(NPCM7xxADCState *s)
87 {
88     uint32_t prescaler = npcm7xx_adc_prescaler(s);
89 
90     npcm7xx_adc_start_timer(s->clock, &s->conv_timer, NPCM7XX_ADC_CONV_CYCLES,
91             prescaler);
92 }
93 
94 static void npcm7xx_adc_convert_done(void *opaque)
95 {
96     NPCM7xxADCState *s = opaque;
97     uint32_t input = NPCM7XX_ADC_CON_MUX(s->con);
98     uint32_t ref = (s->con & NPCM7XX_ADC_CON_REFSEL)
99         ? s->iref : s->vref;
100 
101     if (input >= NPCM7XX_ADC_NUM_INPUTS) {
102         qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid input: %u\n",
103                       __func__, input);
104         return;
105     }
106     s->data = npcm7xx_adc_convert(s->adci[input], ref);
107     if (s->con & NPCM7XX_ADC_CON_INT_EN) {
108         s->con |= NPCM7XX_ADC_CON_INT;
109         qemu_irq_raise(s->irq);
110     }
111     s->con &= ~NPCM7XX_ADC_CON_CONV;
112 }
113 
114 static void npcm7xx_adc_calibrate(NPCM7xxADCState *adc)
115 {
116     adc->calibration_r_values[0] = npcm7xx_adc_convert(NPCM7XX_ADC_R0_INPUT,
117             adc->iref);
118     adc->calibration_r_values[1] = npcm7xx_adc_convert(NPCM7XX_ADC_R1_INPUT,
119             adc->iref);
120 }
121 
122 static void npcm7xx_adc_write_con(NPCM7xxADCState *s, uint32_t new_con)
123 {
124     uint32_t old_con = s->con;
125 
126     /* Write ADC_INT to 1 to clear it */
127     if (new_con & NPCM7XX_ADC_CON_INT) {
128         new_con &= ~NPCM7XX_ADC_CON_INT;
129         qemu_irq_lower(s->irq);
130     } else if (old_con & NPCM7XX_ADC_CON_INT) {
131         new_con |= NPCM7XX_ADC_CON_INT;
132     }
133 
134     s->con = new_con;
135 
136     if (s->con & NPCM7XX_ADC_CON_RST) {
137         npcm7xx_adc_reset(s);
138         return;
139     }
140 
141     if ((s->con & NPCM7XX_ADC_CON_EN)) {
142         if (s->con & NPCM7XX_ADC_CON_CONV) {
143             if (!(old_con & NPCM7XX_ADC_CON_CONV)) {
144                 npcm7xx_adc_start_convert(s);
145             }
146         } else {
147             timer_del(&s->conv_timer);
148         }
149     }
150 }
151 
152 static uint64_t npcm7xx_adc_read(void *opaque, hwaddr offset, unsigned size)
153 {
154     uint64_t value = 0;
155     NPCM7xxADCState *s = opaque;
156 
157     switch (offset) {
158     case A_NPCM7XX_ADC_CON:
159         value = s->con;
160         break;
161 
162     case A_NPCM7XX_ADC_DATA:
163         value = s->data;
164         break;
165 
166     default:
167         qemu_log_mask(LOG_GUEST_ERROR,
168                       "%s: invalid offset 0x%04" HWADDR_PRIx "\n",
169                       __func__, offset);
170         break;
171     }
172 
173     trace_npcm7xx_adc_read(DEVICE(s)->canonical_path, offset, value);
174     return value;
175 }
176 
177 static void npcm7xx_adc_write(void *opaque, hwaddr offset, uint64_t v,
178         unsigned size)
179 {
180     NPCM7xxADCState *s = opaque;
181 
182     trace_npcm7xx_adc_write(DEVICE(s)->canonical_path, offset, v);
183     switch (offset) {
184     case A_NPCM7XX_ADC_CON:
185         npcm7xx_adc_write_con(s, v);
186         break;
187 
188     case A_NPCM7XX_ADC_DATA:
189         qemu_log_mask(LOG_GUEST_ERROR,
190                       "%s: register @ 0x%04" HWADDR_PRIx " is read-only\n",
191                       __func__, offset);
192         break;
193 
194     default:
195         qemu_log_mask(LOG_GUEST_ERROR,
196                       "%s: invalid offset 0x%04" HWADDR_PRIx "\n",
197                       __func__, offset);
198         break;
199     }
200 
201 }
202 
203 static const struct MemoryRegionOps npcm7xx_adc_ops = {
204     .read       = npcm7xx_adc_read,
205     .write      = npcm7xx_adc_write,
206     .endianness = DEVICE_LITTLE_ENDIAN,
207     .valid      = {
208         .min_access_size        = 4,
209         .max_access_size        = 4,
210         .unaligned              = false,
211     },
212 };
213 
214 static void npcm7xx_adc_enter_reset(Object *obj, ResetType type)
215 {
216     NPCM7xxADCState *s = NPCM7XX_ADC(obj);
217 
218     npcm7xx_adc_reset(s);
219 }
220 
221 static void npcm7xx_adc_hold_reset(Object *obj)
222 {
223     NPCM7xxADCState *s = NPCM7XX_ADC(obj);
224 
225     qemu_irq_lower(s->irq);
226 }
227 
228 static void npcm7xx_adc_init(Object *obj)
229 {
230     NPCM7xxADCState *s = NPCM7XX_ADC(obj);
231     SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
232     int i;
233 
234     sysbus_init_irq(sbd, &s->irq);
235 
236     timer_init_ns(&s->conv_timer, QEMU_CLOCK_VIRTUAL,
237             npcm7xx_adc_convert_done, s);
238     memory_region_init_io(&s->iomem, obj, &npcm7xx_adc_ops, s,
239                           TYPE_NPCM7XX_ADC, 4 * KiB);
240     sysbus_init_mmio(sbd, &s->iomem);
241     s->clock = qdev_init_clock_in(DEVICE(s), "clock", NULL, NULL, 0);
242 
243     for (i = 0; i < NPCM7XX_ADC_NUM_INPUTS; ++i) {
244         object_property_add_uint32_ptr(obj, "adci[*]",
245                 &s->adci[i], OBJ_PROP_FLAG_READWRITE);
246     }
247     object_property_add_uint32_ptr(obj, "vref",
248             &s->vref, OBJ_PROP_FLAG_WRITE);
249     npcm7xx_adc_calibrate(s);
250 }
251 
252 static const VMStateDescription vmstate_npcm7xx_adc = {
253     .name = "npcm7xx-adc",
254     .version_id = 0,
255     .minimum_version_id = 0,
256     .fields = (VMStateField[]) {
257         VMSTATE_TIMER(conv_timer, NPCM7xxADCState),
258         VMSTATE_UINT32(con, NPCM7xxADCState),
259         VMSTATE_UINT32(data, NPCM7xxADCState),
260         VMSTATE_CLOCK(clock, NPCM7xxADCState),
261         VMSTATE_UINT32_ARRAY(adci, NPCM7xxADCState, NPCM7XX_ADC_NUM_INPUTS),
262         VMSTATE_UINT32(vref, NPCM7xxADCState),
263         VMSTATE_UINT32(iref, NPCM7xxADCState),
264         VMSTATE_UINT16_ARRAY(calibration_r_values, NPCM7xxADCState,
265                 NPCM7XX_ADC_NUM_CALIB),
266         VMSTATE_END_OF_LIST(),
267     },
268 };
269 
270 static Property npcm7xx_timer_properties[] = {
271     DEFINE_PROP_UINT32("iref", NPCM7xxADCState, iref, NPCM7XX_ADC_DEFAULT_IREF),
272     DEFINE_PROP_END_OF_LIST(),
273 };
274 
275 static void npcm7xx_adc_class_init(ObjectClass *klass, void *data)
276 {
277     ResettableClass *rc = RESETTABLE_CLASS(klass);
278     DeviceClass *dc = DEVICE_CLASS(klass);
279 
280     dc->desc = "NPCM7xx ADC Module";
281     dc->vmsd = &vmstate_npcm7xx_adc;
282     rc->phases.enter = npcm7xx_adc_enter_reset;
283     rc->phases.hold = npcm7xx_adc_hold_reset;
284 
285     device_class_set_props(dc, npcm7xx_timer_properties);
286 }
287 
288 static const TypeInfo npcm7xx_adc_info = {
289     .name               = TYPE_NPCM7XX_ADC,
290     .parent             = TYPE_SYS_BUS_DEVICE,
291     .instance_size      = sizeof(NPCM7xxADCState),
292     .class_init         = npcm7xx_adc_class_init,
293     .instance_init      = npcm7xx_adc_init,
294 };
295 
296 static void npcm7xx_adc_register_types(void)
297 {
298     type_register_static(&npcm7xx_adc_info);
299 }
300 
301 type_init(npcm7xx_adc_register_types);
302