1 /* 2 * Allwinner A10 timer device emulation 3 * 4 * Copyright (C) 2013 Li Guang 5 * Written by Li Guang <lig.fnst@cn.fujitsu.com> 6 * 7 * This program is free software; you can redistribute it and/or modify it 8 * under the terms of the GNU General Public License as published by the 9 * Free Software Foundation; either version 2 of the License, or 10 * (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, but WITHOUT 13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 * for more details. 16 */ 17 18 #include "qemu/osdep.h" 19 #include "hw/irq.h" 20 #include "hw/qdev-properties.h" 21 #include "hw/sysbus.h" 22 #include "hw/timer/allwinner-a10-pit.h" 23 #include "migration/vmstate.h" 24 #include "qemu/log.h" 25 #include "qemu/main-loop.h" 26 #include "qemu/module.h" 27 28 static void a10_pit_update_irq(AwA10PITState *s) 29 { 30 int i; 31 32 for (i = 0; i < AW_A10_PIT_TIMER_NR; i++) { 33 qemu_set_irq(s->irq[i], !!(s->irq_status & s->irq_enable & (1 << i))); 34 } 35 } 36 37 static uint64_t a10_pit_read(void *opaque, hwaddr offset, unsigned size) 38 { 39 AwA10PITState *s = AW_A10_PIT(opaque); 40 uint8_t index; 41 42 switch (offset) { 43 case AW_A10_PIT_TIMER_IRQ_EN: 44 return s->irq_enable; 45 case AW_A10_PIT_TIMER_IRQ_ST: 46 return s->irq_status; 47 case AW_A10_PIT_TIMER_BASE ... AW_A10_PIT_TIMER_BASE_END: 48 index = offset & 0xf0; 49 index >>= 4; 50 index -= 1; 51 switch (offset & 0x0f) { 52 case AW_A10_PIT_TIMER_CONTROL: 53 return s->control[index]; 54 case AW_A10_PIT_TIMER_INTERVAL: 55 return s->interval[index]; 56 case AW_A10_PIT_TIMER_COUNT: 57 s->count[index] = ptimer_get_count(s->timer[index]); 58 return s->count[index]; 59 default: 60 qemu_log_mask(LOG_GUEST_ERROR, 61 "%s: Bad offset 0x%x\n", __func__, (int)offset); 62 break; 63 } 64 case AW_A10_PIT_WDOG_CONTROL: 65 break; 66 case AW_A10_PIT_WDOG_MODE: 67 break; 68 case AW_A10_PIT_COUNT_LO: 69 return s->count_lo; 70 case AW_A10_PIT_COUNT_HI: 71 return s->count_hi; 72 case AW_A10_PIT_COUNT_CTL: 73 return s->count_ctl; 74 default: 75 qemu_log_mask(LOG_GUEST_ERROR, 76 "%s: Bad offset 0x%x\n", __func__, (int)offset); 77 break; 78 } 79 80 return 0; 81 } 82 83 static void a10_pit_set_freq(AwA10PITState *s, int index) 84 { 85 uint32_t prescaler, source, source_freq; 86 87 prescaler = 1 << extract32(s->control[index], 4, 3); 88 source = extract32(s->control[index], 2, 2); 89 source_freq = s->clk_freq[source]; 90 91 if (source_freq) { 92 ptimer_set_freq(s->timer[index], source_freq / prescaler); 93 } else { 94 qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid clock source %u\n", 95 __func__, source); 96 } 97 } 98 99 static void a10_pit_write(void *opaque, hwaddr offset, uint64_t value, 100 unsigned size) 101 { 102 AwA10PITState *s = AW_A10_PIT(opaque); 103 uint8_t index; 104 105 switch (offset) { 106 case AW_A10_PIT_TIMER_IRQ_EN: 107 s->irq_enable = value; 108 a10_pit_update_irq(s); 109 break; 110 case AW_A10_PIT_TIMER_IRQ_ST: 111 s->irq_status &= ~value; 112 a10_pit_update_irq(s); 113 break; 114 case AW_A10_PIT_TIMER_BASE ... AW_A10_PIT_TIMER_BASE_END: 115 index = offset & 0xf0; 116 index >>= 4; 117 index -= 1; 118 switch (offset & 0x0f) { 119 case AW_A10_PIT_TIMER_CONTROL: 120 s->control[index] = value; 121 a10_pit_set_freq(s, index); 122 if (s->control[index] & AW_A10_PIT_TIMER_RELOAD) { 123 ptimer_set_count(s->timer[index], s->interval[index]); 124 } 125 if (s->control[index] & AW_A10_PIT_TIMER_EN) { 126 int oneshot = 0; 127 if (s->control[index] & AW_A10_PIT_TIMER_MODE) { 128 oneshot = 1; 129 } 130 ptimer_run(s->timer[index], oneshot); 131 } else { 132 ptimer_stop(s->timer[index]); 133 } 134 break; 135 case AW_A10_PIT_TIMER_INTERVAL: 136 s->interval[index] = value; 137 ptimer_set_limit(s->timer[index], s->interval[index], 1); 138 break; 139 case AW_A10_PIT_TIMER_COUNT: 140 s->count[index] = value; 141 break; 142 default: 143 qemu_log_mask(LOG_GUEST_ERROR, 144 "%s: Bad offset 0x%x\n", __func__, (int)offset); 145 } 146 break; 147 case AW_A10_PIT_WDOG_CONTROL: 148 s->watch_dog_control = value; 149 break; 150 case AW_A10_PIT_WDOG_MODE: 151 s->watch_dog_mode = value; 152 break; 153 case AW_A10_PIT_COUNT_LO: 154 s->count_lo = value; 155 break; 156 case AW_A10_PIT_COUNT_HI: 157 s->count_hi = value; 158 break; 159 case AW_A10_PIT_COUNT_CTL: 160 s->count_ctl = value; 161 if (s->count_ctl & AW_A10_PIT_COUNT_RL_EN) { 162 uint64_t tmp_count = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); 163 164 s->count_lo = tmp_count; 165 s->count_hi = tmp_count >> 32; 166 s->count_ctl &= ~AW_A10_PIT_COUNT_RL_EN; 167 } 168 if (s->count_ctl & AW_A10_PIT_COUNT_CLR_EN) { 169 s->count_lo = 0; 170 s->count_hi = 0; 171 s->count_ctl &= ~AW_A10_PIT_COUNT_CLR_EN; 172 } 173 break; 174 default: 175 qemu_log_mask(LOG_GUEST_ERROR, 176 "%s: Bad offset 0x%x\n", __func__, (int)offset); 177 break; 178 } 179 } 180 181 static const MemoryRegionOps a10_pit_ops = { 182 .read = a10_pit_read, 183 .write = a10_pit_write, 184 .endianness = DEVICE_NATIVE_ENDIAN, 185 }; 186 187 static Property a10_pit_properties[] = { 188 DEFINE_PROP_UINT32("clk0-freq", AwA10PITState, clk_freq[0], 0), 189 DEFINE_PROP_UINT32("clk1-freq", AwA10PITState, clk_freq[1], 0), 190 DEFINE_PROP_UINT32("clk2-freq", AwA10PITState, clk_freq[2], 0), 191 DEFINE_PROP_UINT32("clk3-freq", AwA10PITState, clk_freq[3], 0), 192 DEFINE_PROP_END_OF_LIST(), 193 }; 194 195 static const VMStateDescription vmstate_a10_pit = { 196 .name = "a10.pit", 197 .version_id = 1, 198 .minimum_version_id = 1, 199 .fields = (VMStateField[]) { 200 VMSTATE_UINT32(irq_enable, AwA10PITState), 201 VMSTATE_UINT32(irq_status, AwA10PITState), 202 VMSTATE_UINT32_ARRAY(control, AwA10PITState, AW_A10_PIT_TIMER_NR), 203 VMSTATE_UINT32_ARRAY(interval, AwA10PITState, AW_A10_PIT_TIMER_NR), 204 VMSTATE_UINT32_ARRAY(count, AwA10PITState, AW_A10_PIT_TIMER_NR), 205 VMSTATE_UINT32(watch_dog_mode, AwA10PITState), 206 VMSTATE_UINT32(watch_dog_control, AwA10PITState), 207 VMSTATE_UINT32(count_lo, AwA10PITState), 208 VMSTATE_UINT32(count_hi, AwA10PITState), 209 VMSTATE_UINT32(count_ctl, AwA10PITState), 210 VMSTATE_PTIMER_ARRAY(timer, AwA10PITState, AW_A10_PIT_TIMER_NR), 211 VMSTATE_END_OF_LIST() 212 } 213 }; 214 215 static void a10_pit_reset(DeviceState *dev) 216 { 217 AwA10PITState *s = AW_A10_PIT(dev); 218 uint8_t i; 219 220 s->irq_enable = 0; 221 s->irq_status = 0; 222 a10_pit_update_irq(s); 223 224 for (i = 0; i < 6; i++) { 225 s->control[i] = AW_A10_PIT_DEFAULT_CLOCK; 226 s->interval[i] = 0; 227 s->count[i] = 0; 228 ptimer_stop(s->timer[i]); 229 a10_pit_set_freq(s, i); 230 } 231 s->watch_dog_mode = 0; 232 s->watch_dog_control = 0; 233 s->count_lo = 0; 234 s->count_hi = 0; 235 s->count_ctl = 0; 236 } 237 238 static void a10_pit_timer_cb(void *opaque) 239 { 240 AwA10TimerContext *tc = opaque; 241 AwA10PITState *s = tc->container; 242 uint8_t i = tc->index; 243 244 if (s->control[i] & AW_A10_PIT_TIMER_EN) { 245 s->irq_status |= 1 << i; 246 if (s->control[i] & AW_A10_PIT_TIMER_MODE) { 247 ptimer_stop(s->timer[i]); 248 s->control[i] &= ~AW_A10_PIT_TIMER_EN; 249 } 250 a10_pit_update_irq(s); 251 } 252 } 253 254 static void a10_pit_init(Object *obj) 255 { 256 AwA10PITState *s = AW_A10_PIT(obj); 257 SysBusDevice *sbd = SYS_BUS_DEVICE(obj); 258 QEMUBH * bh[AW_A10_PIT_TIMER_NR]; 259 uint8_t i; 260 261 for (i = 0; i < AW_A10_PIT_TIMER_NR; i++) { 262 sysbus_init_irq(sbd, &s->irq[i]); 263 } 264 memory_region_init_io(&s->iomem, OBJECT(s), &a10_pit_ops, s, 265 TYPE_AW_A10_PIT, 0x400); 266 sysbus_init_mmio(sbd, &s->iomem); 267 268 for (i = 0; i < AW_A10_PIT_TIMER_NR; i++) { 269 AwA10TimerContext *tc = &s->timer_context[i]; 270 271 tc->container = s; 272 tc->index = i; 273 bh[i] = qemu_bh_new(a10_pit_timer_cb, tc); 274 s->timer[i] = ptimer_init(bh[i], PTIMER_POLICY_DEFAULT); 275 } 276 } 277 278 static void a10_pit_class_init(ObjectClass *klass, void *data) 279 { 280 DeviceClass *dc = DEVICE_CLASS(klass); 281 282 dc->reset = a10_pit_reset; 283 dc->props = a10_pit_properties; 284 dc->desc = "allwinner a10 timer"; 285 dc->vmsd = &vmstate_a10_pit; 286 } 287 288 static const TypeInfo a10_pit_info = { 289 .name = TYPE_AW_A10_PIT, 290 .parent = TYPE_SYS_BUS_DEVICE, 291 .instance_size = sizeof(AwA10PITState), 292 .instance_init = a10_pit_init, 293 .class_init = a10_pit_class_init, 294 }; 295 296 static void a10_register_types(void) 297 { 298 type_register_static(&a10_pit_info); 299 } 300 301 type_init(a10_register_types); 302