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 "hw/sysbus.h" 19 #include "sysemu/sysemu.h" 20 #include "hw/timer/allwinner-a10-pit.h" 21 22 static uint64_t a10_pit_read(void *opaque, hwaddr offset, unsigned size) 23 { 24 AwA10PITState *s = AW_A10_PIT(opaque); 25 uint8_t index; 26 27 switch (offset) { 28 case AW_A10_PIT_TIMER_IRQ_EN: 29 return s->irq_enable; 30 case AW_A10_PIT_TIMER_IRQ_ST: 31 return s->irq_status; 32 case AW_A10_PIT_TIMER_BASE ... AW_A10_PIT_TIMER_BASE_END: 33 index = offset & 0xf0; 34 index >>= 4; 35 index -= 1; 36 switch (offset & 0x0f) { 37 case AW_A10_PIT_TIMER_CONTROL: 38 return s->control[index]; 39 case AW_A10_PIT_TIMER_INTERVAL: 40 return s->interval[index]; 41 case AW_A10_PIT_TIMER_COUNT: 42 s->count[index] = ptimer_get_count(s->timer[index]); 43 return s->count[index]; 44 default: 45 qemu_log_mask(LOG_GUEST_ERROR, 46 "%s: Bad offset 0x%x\n", __func__, (int)offset); 47 break; 48 } 49 case AW_A10_PIT_WDOG_CONTROL: 50 break; 51 case AW_A10_PIT_WDOG_MODE: 52 break; 53 case AW_A10_PIT_COUNT_LO: 54 return s->count_lo; 55 case AW_A10_PIT_COUNT_HI: 56 return s->count_hi; 57 case AW_A10_PIT_COUNT_CTL: 58 return s->count_ctl; 59 default: 60 qemu_log_mask(LOG_GUEST_ERROR, 61 "%s: Bad offset 0x%x\n", __func__, (int)offset); 62 break; 63 } 64 65 return 0; 66 } 67 68 static void a10_pit_write(void *opaque, hwaddr offset, uint64_t value, 69 unsigned size) 70 { 71 AwA10PITState *s = AW_A10_PIT(opaque); 72 uint8_t index; 73 74 switch (offset) { 75 case AW_A10_PIT_TIMER_IRQ_EN: 76 s->irq_enable = value; 77 break; 78 case AW_A10_PIT_TIMER_IRQ_ST: 79 s->irq_status &= ~value; 80 break; 81 case AW_A10_PIT_TIMER_BASE ... AW_A10_PIT_TIMER_BASE_END: 82 index = offset & 0xf0; 83 index >>= 4; 84 index -= 1; 85 switch (offset & 0x0f) { 86 case AW_A10_PIT_TIMER_CONTROL: 87 s->control[index] = value; 88 if (s->control[index] & AW_A10_PIT_TIMER_RELOAD) { 89 ptimer_set_count(s->timer[index], s->interval[index]); 90 } 91 if (s->control[index] & AW_A10_PIT_TIMER_EN) { 92 int oneshot = 0; 93 if (s->control[index] & AW_A10_PIT_TIMER_MODE) { 94 oneshot = 1; 95 } 96 ptimer_run(s->timer[index], oneshot); 97 } else { 98 ptimer_stop(s->timer[index]); 99 } 100 break; 101 case AW_A10_PIT_TIMER_INTERVAL: 102 s->interval[index] = value; 103 ptimer_set_limit(s->timer[index], s->interval[index], 1); 104 break; 105 case AW_A10_PIT_TIMER_COUNT: 106 s->count[index] = value; 107 break; 108 default: 109 qemu_log_mask(LOG_GUEST_ERROR, 110 "%s: Bad offset 0x%x\n", __func__, (int)offset); 111 } 112 break; 113 case AW_A10_PIT_WDOG_CONTROL: 114 s->watch_dog_control = value; 115 break; 116 case AW_A10_PIT_WDOG_MODE: 117 s->watch_dog_mode = value; 118 break; 119 case AW_A10_PIT_COUNT_LO: 120 s->count_lo = value; 121 break; 122 case AW_A10_PIT_COUNT_HI: 123 s->count_hi = value; 124 break; 125 case AW_A10_PIT_COUNT_CTL: 126 s->count_ctl = value; 127 if (s->count_ctl & AW_A10_PIT_COUNT_RL_EN) { 128 uint64_t tmp_count = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); 129 130 s->count_lo = tmp_count; 131 s->count_hi = tmp_count >> 32; 132 s->count_ctl &= ~AW_A10_PIT_COUNT_RL_EN; 133 } 134 if (s->count_ctl & AW_A10_PIT_COUNT_CLR_EN) { 135 s->count_lo = 0; 136 s->count_hi = 0; 137 s->count_ctl &= ~AW_A10_PIT_COUNT_CLR_EN; 138 } 139 break; 140 default: 141 qemu_log_mask(LOG_GUEST_ERROR, 142 "%s: Bad offset 0x%x\n", __func__, (int)offset); 143 break; 144 } 145 } 146 147 static const MemoryRegionOps a10_pit_ops = { 148 .read = a10_pit_read, 149 .write = a10_pit_write, 150 .endianness = DEVICE_NATIVE_ENDIAN, 151 }; 152 153 static const VMStateDescription vmstate_a10_pit = { 154 .name = "a10.pit", 155 .version_id = 1, 156 .minimum_version_id = 1, 157 .minimum_version_id_old = 1, 158 .fields = (VMStateField[]) { 159 VMSTATE_UINT32(irq_enable, AwA10PITState), 160 VMSTATE_UINT32(irq_status, AwA10PITState), 161 VMSTATE_UINT32_ARRAY(control, AwA10PITState, AW_A10_PIT_TIMER_NR), 162 VMSTATE_UINT32_ARRAY(interval, AwA10PITState, AW_A10_PIT_TIMER_NR), 163 VMSTATE_UINT32_ARRAY(count, AwA10PITState, AW_A10_PIT_TIMER_NR), 164 VMSTATE_UINT32(watch_dog_mode, AwA10PITState), 165 VMSTATE_UINT32(watch_dog_control, AwA10PITState), 166 VMSTATE_UINT32(count_lo, AwA10PITState), 167 VMSTATE_UINT32(count_hi, AwA10PITState), 168 VMSTATE_UINT32(count_ctl, AwA10PITState), 169 VMSTATE_PTIMER_ARRAY(timer, AwA10PITState, AW_A10_PIT_TIMER_NR), 170 VMSTATE_END_OF_LIST() 171 } 172 }; 173 174 static void a10_pit_reset(DeviceState *dev) 175 { 176 AwA10PITState *s = AW_A10_PIT(dev); 177 uint8_t i; 178 179 s->irq_enable = 0; 180 s->irq_status = 0; 181 for (i = 0; i < 6; i++) { 182 s->control[i] = AW_A10_PIT_DEFAULT_CLOCK; 183 s->interval[i] = 0; 184 s->count[i] = 0; 185 ptimer_stop(s->timer[i]); 186 } 187 s->watch_dog_mode = 0; 188 s->watch_dog_control = 0; 189 s->count_lo = 0; 190 s->count_hi = 0; 191 s->count_ctl = 0; 192 } 193 194 static void a10_pit_timer_cb(void *opaque) 195 { 196 AwA10PITState *s = AW_A10_PIT(opaque); 197 uint8_t i; 198 199 for (i = 0; i < AW_A10_PIT_TIMER_NR; i++) { 200 if (s->control[i] & AW_A10_PIT_TIMER_EN) { 201 s->irq_status |= 1 << i; 202 if (s->control[i] & AW_A10_PIT_TIMER_MODE) { 203 ptimer_stop(s->timer[i]); 204 s->control[i] &= ~AW_A10_PIT_TIMER_EN; 205 } 206 qemu_irq_pulse(s->irq[i]); 207 } 208 } 209 } 210 211 static void a10_pit_init(Object *obj) 212 { 213 AwA10PITState *s = AW_A10_PIT(obj); 214 SysBusDevice *sbd = SYS_BUS_DEVICE(obj); 215 QEMUBH * bh[AW_A10_PIT_TIMER_NR]; 216 uint8_t i; 217 218 for (i = 0; i < AW_A10_PIT_TIMER_NR; i++) { 219 sysbus_init_irq(sbd, &s->irq[i]); 220 } 221 memory_region_init_io(&s->iomem, OBJECT(s), &a10_pit_ops, s, 222 TYPE_AW_A10_PIT, 0x400); 223 sysbus_init_mmio(sbd, &s->iomem); 224 225 for (i = 0; i < AW_A10_PIT_TIMER_NR; i++) { 226 bh[i] = qemu_bh_new(a10_pit_timer_cb, s); 227 s->timer[i] = ptimer_init(bh[i]); 228 ptimer_set_freq(s->timer[i], 240000); 229 } 230 } 231 232 static void a10_pit_class_init(ObjectClass *klass, void *data) 233 { 234 DeviceClass *dc = DEVICE_CLASS(klass); 235 236 dc->reset = a10_pit_reset; 237 dc->desc = "allwinner a10 timer"; 238 dc->vmsd = &vmstate_a10_pit; 239 } 240 241 static const TypeInfo a10_pit_info = { 242 .name = TYPE_AW_A10_PIT, 243 .parent = TYPE_SYS_BUS_DEVICE, 244 .instance_size = sizeof(AwA10PITState), 245 .instance_init = a10_pit_init, 246 .class_init = a10_pit_class_init, 247 }; 248 249 static void a10_register_types(void) 250 { 251 type_register_static(&a10_pit_info); 252 } 253 254 type_init(a10_register_types); 255