]>
Commit | Line | Data |
---|---|---|
77c05b0b HW |
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(14) | |
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); | |
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_WRITE); | |
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); |