1 /* 2 * Allwinner Watchdog emulation 3 * 4 * Copyright (C) 2023 Strahinja Jankovic <strahinja.p.jankovic@gmail.com> 5 * 6 * This file is derived from Allwinner RTC, 7 * by Niek Linnenbank. 8 * 9 * This program is free software: you can redistribute it and/or modify 10 * it under the terms of the GNU General Public License as published by 11 * the Free Software Foundation, either version 2 of the License, or 12 * (at your option) any later version. 13 * 14 * This program is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 * GNU General Public License for more details. 18 * 19 * You should have received a copy of the GNU General Public License 20 * along with this program. If not, see <http://www.gnu.org/licenses/>. 21 */ 22 23 #include "qemu/osdep.h" 24 #include "qemu/log.h" 25 #include "qemu/units.h" 26 #include "qemu/module.h" 27 #include "trace.h" 28 #include "hw/sysbus.h" 29 #include "hw/registerfields.h" 30 #include "hw/watchdog/allwinner-wdt.h" 31 #include "sysemu/watchdog.h" 32 #include "migration/vmstate.h" 33 34 /* WDT registers */ 35 enum { 36 REG_IRQ_EN = 0, /* Watchdog interrupt enable */ 37 REG_IRQ_STA, /* Watchdog interrupt status */ 38 REG_CTRL, /* Watchdog control register */ 39 REG_CFG, /* Watchdog configuration register */ 40 REG_MODE, /* Watchdog mode register */ 41 }; 42 43 /* Universal WDT register flags */ 44 #define WDT_RESTART_MASK (1 << 0) 45 #define WDT_EN_MASK (1 << 0) 46 47 /* sun4i specific WDT register flags */ 48 #define RST_EN_SUN4I_MASK (1 << 1) 49 #define INTV_VALUE_SUN4I_SHIFT (3) 50 #define INTV_VALUE_SUN4I_MASK (0xfu << INTV_VALUE_SUN4I_SHIFT) 51 52 /* sun6i specific WDT register flags */ 53 #define RST_EN_SUN6I_MASK (1 << 0) 54 #define KEY_FIELD_SUN6I_SHIFT (1) 55 #define KEY_FIELD_SUN6I_MASK (0xfffu << KEY_FIELD_SUN6I_SHIFT) 56 #define KEY_FIELD_SUN6I (0xA57u) 57 #define INTV_VALUE_SUN6I_SHIFT (4) 58 #define INTV_VALUE_SUN6I_MASK (0xfu << INTV_VALUE_SUN6I_SHIFT) 59 60 /* Map of INTV_VALUE to 0.5s units. */ 61 static const uint8_t allwinner_wdt_count_map[] = { 62 1, 63 2, 64 4, 65 6, 66 8, 67 10, 68 12, 69 16, 70 20, 71 24, 72 28, 73 32 74 }; 75 76 /* WDT sun4i register map (offset to name) */ 77 const uint8_t allwinner_wdt_sun4i_regmap[] = { 78 [0x0000] = REG_CTRL, 79 [0x0004] = REG_MODE, 80 }; 81 82 /* WDT sun6i register map (offset to name) */ 83 const uint8_t allwinner_wdt_sun6i_regmap[] = { 84 [0x0000] = REG_IRQ_EN, 85 [0x0004] = REG_IRQ_STA, 86 [0x0010] = REG_CTRL, 87 [0x0014] = REG_CFG, 88 [0x0018] = REG_MODE, 89 }; 90 91 static bool allwinner_wdt_sun4i_read(AwWdtState *s, uint32_t offset) 92 { 93 /* no sun4i specific registers currently implemented */ 94 return false; 95 } 96 97 static bool allwinner_wdt_sun4i_write(AwWdtState *s, uint32_t offset, 98 uint32_t data) 99 { 100 /* no sun4i specific registers currently implemented */ 101 return false; 102 } 103 104 static bool allwinner_wdt_sun4i_can_reset_system(AwWdtState *s) 105 { 106 if (s->regs[REG_MODE] & RST_EN_SUN4I_MASK) { 107 return true; 108 } else { 109 return false; 110 } 111 } 112 113 static bool allwinner_wdt_sun4i_is_key_valid(AwWdtState *s, uint32_t val) 114 { 115 /* sun4i has no key */ 116 return true; 117 } 118 119 static uint8_t allwinner_wdt_sun4i_get_intv_value(AwWdtState *s) 120 { 121 return ((s->regs[REG_MODE] & INTV_VALUE_SUN4I_MASK) >> 122 INTV_VALUE_SUN4I_SHIFT); 123 } 124 125 static bool allwinner_wdt_sun6i_read(AwWdtState *s, uint32_t offset) 126 { 127 const AwWdtClass *c = AW_WDT_GET_CLASS(s); 128 129 switch (c->regmap[offset]) { 130 case REG_IRQ_EN: 131 case REG_IRQ_STA: 132 case REG_CFG: 133 return true; 134 default: 135 break; 136 } 137 return false; 138 } 139 140 static bool allwinner_wdt_sun6i_write(AwWdtState *s, uint32_t offset, 141 uint32_t data) 142 { 143 const AwWdtClass *c = AW_WDT_GET_CLASS(s); 144 145 switch (c->regmap[offset]) { 146 case REG_IRQ_EN: 147 case REG_IRQ_STA: 148 case REG_CFG: 149 return true; 150 default: 151 break; 152 } 153 return false; 154 } 155 156 static bool allwinner_wdt_sun6i_can_reset_system(AwWdtState *s) 157 { 158 if (s->regs[REG_CFG] & RST_EN_SUN6I_MASK) { 159 return true; 160 } else { 161 return false; 162 } 163 } 164 165 static bool allwinner_wdt_sun6i_is_key_valid(AwWdtState *s, uint32_t val) 166 { 167 uint16_t key = (val & KEY_FIELD_SUN6I_MASK) >> KEY_FIELD_SUN6I_SHIFT; 168 return (key == KEY_FIELD_SUN6I); 169 } 170 171 static uint8_t allwinner_wdt_sun6i_get_intv_value(AwWdtState *s) 172 { 173 return ((s->regs[REG_MODE] & INTV_VALUE_SUN6I_MASK) >> 174 INTV_VALUE_SUN6I_SHIFT); 175 } 176 177 static void allwinner_wdt_update_timer(AwWdtState *s) 178 { 179 const AwWdtClass *c = AW_WDT_GET_CLASS(s); 180 uint8_t count = c->get_intv_value(s); 181 182 ptimer_transaction_begin(s->timer); 183 ptimer_stop(s->timer); 184 185 /* Use map to convert. */ 186 if (count < sizeof(allwinner_wdt_count_map)) { 187 ptimer_set_count(s->timer, allwinner_wdt_count_map[count]); 188 } else { 189 qemu_log_mask(LOG_GUEST_ERROR, "%s: incorrect INTV_VALUE 0x%02x\n", 190 __func__, count); 191 } 192 193 ptimer_run(s->timer, 1); 194 ptimer_transaction_commit(s->timer); 195 196 trace_allwinner_wdt_update_timer(count); 197 } 198 199 static uint64_t allwinner_wdt_read(void *opaque, hwaddr offset, 200 unsigned size) 201 { 202 AwWdtState *s = AW_WDT(opaque); 203 const AwWdtClass *c = AW_WDT_GET_CLASS(s); 204 uint64_t r; 205 206 if (offset >= c->regmap_size) { 207 qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n", 208 __func__, (uint32_t)offset); 209 return 0; 210 } 211 212 switch (c->regmap[offset]) { 213 case REG_CTRL: 214 case REG_MODE: 215 r = s->regs[c->regmap[offset]]; 216 break; 217 default: 218 if (!c->read(s, offset)) { 219 qemu_log_mask(LOG_UNIMP, "%s: unimplemented register 0x%04x\n", 220 __func__, (uint32_t)offset); 221 return 0; 222 } 223 r = s->regs[c->regmap[offset]]; 224 break; 225 } 226 227 trace_allwinner_wdt_read(offset, r, size); 228 229 return r; 230 } 231 232 static void allwinner_wdt_write(void *opaque, hwaddr offset, 233 uint64_t val, unsigned size) 234 { 235 AwWdtState *s = AW_WDT(opaque); 236 const AwWdtClass *c = AW_WDT_GET_CLASS(s); 237 uint32_t old_val; 238 239 if (offset >= c->regmap_size) { 240 qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n", 241 __func__, (uint32_t)offset); 242 return; 243 } 244 245 trace_allwinner_wdt_write(offset, val, size); 246 247 switch (c->regmap[offset]) { 248 case REG_CTRL: 249 if (c->is_key_valid(s, val)) { 250 if (val & WDT_RESTART_MASK) { 251 /* Kick timer */ 252 allwinner_wdt_update_timer(s); 253 } 254 } 255 break; 256 case REG_MODE: 257 old_val = s->regs[REG_MODE]; 258 s->regs[REG_MODE] = (uint32_t)val; 259 260 /* Check for rising edge on WDOG_MODE_EN */ 261 if ((s->regs[REG_MODE] & ~old_val) & WDT_EN_MASK) { 262 allwinner_wdt_update_timer(s); 263 } 264 break; 265 default: 266 if (!c->write(s, offset, val)) { 267 qemu_log_mask(LOG_UNIMP, "%s: unimplemented register 0x%04x\n", 268 __func__, (uint32_t)offset); 269 } 270 s->regs[c->regmap[offset]] = (uint32_t)val; 271 break; 272 } 273 } 274 275 static const MemoryRegionOps allwinner_wdt_ops = { 276 .read = allwinner_wdt_read, 277 .write = allwinner_wdt_write, 278 .endianness = DEVICE_NATIVE_ENDIAN, 279 .valid = { 280 .min_access_size = 4, 281 .max_access_size = 4, 282 }, 283 .impl.min_access_size = 4, 284 }; 285 286 static void allwinner_wdt_expired(void *opaque) 287 { 288 AwWdtState *s = AW_WDT(opaque); 289 const AwWdtClass *c = AW_WDT_GET_CLASS(s); 290 291 bool enabled = s->regs[REG_MODE] & WDT_EN_MASK; 292 bool reset_enabled = c->can_reset_system(s); 293 294 trace_allwinner_wdt_expired(enabled, reset_enabled); 295 296 /* Perform watchdog action if watchdog is enabled and can trigger reset */ 297 if (enabled && reset_enabled) { 298 watchdog_perform_action(); 299 } 300 } 301 302 static void allwinner_wdt_reset_enter(Object *obj, ResetType type) 303 { 304 AwWdtState *s = AW_WDT(obj); 305 306 trace_allwinner_wdt_reset_enter(); 307 308 /* Clear registers */ 309 memset(s->regs, 0, sizeof(s->regs)); 310 } 311 312 static const VMStateDescription allwinner_wdt_vmstate = { 313 .name = "allwinner-wdt", 314 .version_id = 1, 315 .minimum_version_id = 1, 316 .fields = (VMStateField[]) { 317 VMSTATE_PTIMER(timer, AwWdtState), 318 VMSTATE_UINT32_ARRAY(regs, AwWdtState, AW_WDT_REGS_NUM), 319 VMSTATE_END_OF_LIST() 320 } 321 }; 322 323 static void allwinner_wdt_init(Object *obj) 324 { 325 SysBusDevice *sbd = SYS_BUS_DEVICE(obj); 326 AwWdtState *s = AW_WDT(obj); 327 const AwWdtClass *c = AW_WDT_GET_CLASS(s); 328 329 /* Memory mapping */ 330 memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_wdt_ops, s, 331 TYPE_AW_WDT, c->regmap_size * 4); 332 sysbus_init_mmio(sbd, &s->iomem); 333 } 334 335 static void allwinner_wdt_realize(DeviceState *dev, Error **errp) 336 { 337 AwWdtState *s = AW_WDT(dev); 338 339 s->timer = ptimer_init(allwinner_wdt_expired, s, 340 PTIMER_POLICY_NO_IMMEDIATE_TRIGGER | 341 PTIMER_POLICY_NO_IMMEDIATE_RELOAD | 342 PTIMER_POLICY_NO_COUNTER_ROUND_DOWN); 343 344 ptimer_transaction_begin(s->timer); 345 /* Set to 2Hz (0.5s period); other periods are multiples of 0.5s. */ 346 ptimer_set_freq(s->timer, 2); 347 ptimer_set_limit(s->timer, 0xff, 1); 348 ptimer_transaction_commit(s->timer); 349 } 350 351 static void allwinner_wdt_class_init(ObjectClass *klass, void *data) 352 { 353 DeviceClass *dc = DEVICE_CLASS(klass); 354 ResettableClass *rc = RESETTABLE_CLASS(klass); 355 356 rc->phases.enter = allwinner_wdt_reset_enter; 357 dc->realize = allwinner_wdt_realize; 358 dc->vmsd = &allwinner_wdt_vmstate; 359 } 360 361 static void allwinner_wdt_sun4i_class_init(ObjectClass *klass, void *data) 362 { 363 AwWdtClass *awc = AW_WDT_CLASS(klass); 364 365 awc->regmap = allwinner_wdt_sun4i_regmap; 366 awc->regmap_size = sizeof(allwinner_wdt_sun4i_regmap); 367 awc->read = allwinner_wdt_sun4i_read; 368 awc->write = allwinner_wdt_sun4i_write; 369 awc->can_reset_system = allwinner_wdt_sun4i_can_reset_system; 370 awc->is_key_valid = allwinner_wdt_sun4i_is_key_valid; 371 awc->get_intv_value = allwinner_wdt_sun4i_get_intv_value; 372 } 373 374 static void allwinner_wdt_sun6i_class_init(ObjectClass *klass, void *data) 375 { 376 AwWdtClass *awc = AW_WDT_CLASS(klass); 377 378 awc->regmap = allwinner_wdt_sun6i_regmap; 379 awc->regmap_size = sizeof(allwinner_wdt_sun6i_regmap); 380 awc->read = allwinner_wdt_sun6i_read; 381 awc->write = allwinner_wdt_sun6i_write; 382 awc->can_reset_system = allwinner_wdt_sun6i_can_reset_system; 383 awc->is_key_valid = allwinner_wdt_sun6i_is_key_valid; 384 awc->get_intv_value = allwinner_wdt_sun6i_get_intv_value; 385 } 386 387 static const TypeInfo allwinner_wdt_info = { 388 .name = TYPE_AW_WDT, 389 .parent = TYPE_SYS_BUS_DEVICE, 390 .instance_init = allwinner_wdt_init, 391 .instance_size = sizeof(AwWdtState), 392 .class_init = allwinner_wdt_class_init, 393 .class_size = sizeof(AwWdtClass), 394 .abstract = true, 395 }; 396 397 static const TypeInfo allwinner_wdt_sun4i_info = { 398 .name = TYPE_AW_WDT_SUN4I, 399 .parent = TYPE_AW_WDT, 400 .class_init = allwinner_wdt_sun4i_class_init, 401 }; 402 403 static const TypeInfo allwinner_wdt_sun6i_info = { 404 .name = TYPE_AW_WDT_SUN6I, 405 .parent = TYPE_AW_WDT, 406 .class_init = allwinner_wdt_sun6i_class_init, 407 }; 408 409 static void allwinner_wdt_register(void) 410 { 411 type_register_static(&allwinner_wdt_info); 412 type_register_static(&allwinner_wdt_sun4i_info); 413 type_register_static(&allwinner_wdt_sun6i_info); 414 } 415 416 type_init(allwinner_wdt_register) 417