1854123bfSCédric Le Goater /* 2854123bfSCédric Le Goater * ASPEED Watchdog Controller 3854123bfSCédric Le Goater * 4854123bfSCédric Le Goater * Copyright (C) 2016-2017 IBM Corp. 5854123bfSCédric Le Goater * 6854123bfSCédric Le Goater * This code is licensed under the GPL version 2 or later. See the 7854123bfSCédric Le Goater * COPYING file in the top-level directory. 8854123bfSCédric Le Goater */ 9854123bfSCédric Le Goater 10854123bfSCédric Le Goater #include "qemu/osdep.h" 11*f55d613bSAndrew Jeffery 12*f55d613bSAndrew Jeffery #include "qapi/error.h" 13854123bfSCédric Le Goater #include "qemu/log.h" 14854123bfSCédric Le Goater #include "qemu/timer.h" 15*f55d613bSAndrew Jeffery #include "sysemu/watchdog.h" 16*f55d613bSAndrew Jeffery #include "hw/misc/aspeed_scu.h" 17*f55d613bSAndrew Jeffery #include "hw/sysbus.h" 18854123bfSCédric Le Goater #include "hw/watchdog/wdt_aspeed.h" 19854123bfSCédric Le Goater 20854123bfSCédric Le Goater #define WDT_STATUS (0x00 / 4) 21854123bfSCédric Le Goater #define WDT_RELOAD_VALUE (0x04 / 4) 22854123bfSCédric Le Goater #define WDT_RESTART (0x08 / 4) 23854123bfSCédric Le Goater #define WDT_CTRL (0x0C / 4) 24854123bfSCédric Le Goater #define WDT_CTRL_RESET_MODE_SOC (0x00 << 5) 25854123bfSCédric Le Goater #define WDT_CTRL_RESET_MODE_FULL_CHIP (0x01 << 5) 26854123bfSCédric Le Goater #define WDT_CTRL_1MHZ_CLK BIT(4) 27854123bfSCédric Le Goater #define WDT_CTRL_WDT_EXT BIT(3) 28854123bfSCédric Le Goater #define WDT_CTRL_WDT_INTR BIT(2) 29854123bfSCédric Le Goater #define WDT_CTRL_RESET_SYSTEM BIT(1) 30854123bfSCédric Le Goater #define WDT_CTRL_ENABLE BIT(0) 31*f55d613bSAndrew Jeffery #define WDT_RESET_WIDTH (0x18 / 4) 32*f55d613bSAndrew Jeffery #define WDT_RESET_WIDTH_ACTIVE_HIGH BIT(31) 33*f55d613bSAndrew Jeffery #define WDT_POLARITY_MASK (0xFF << 24) 34*f55d613bSAndrew Jeffery #define WDT_ACTIVE_HIGH_MAGIC (0xA5 << 24) 35*f55d613bSAndrew Jeffery #define WDT_ACTIVE_LOW_MAGIC (0x5A << 24) 36*f55d613bSAndrew Jeffery #define WDT_RESET_WIDTH_PUSH_PULL BIT(30) 37*f55d613bSAndrew Jeffery #define WDT_DRIVE_TYPE_MASK (0xFF << 24) 38*f55d613bSAndrew Jeffery #define WDT_PUSH_PULL_MAGIC (0xA8 << 24) 39*f55d613bSAndrew Jeffery #define WDT_OPEN_DRAIN_MAGIC (0x8A << 24) 40854123bfSCédric Le Goater 41854123bfSCédric Le Goater #define WDT_TIMEOUT_STATUS (0x10 / 4) 42854123bfSCédric Le Goater #define WDT_TIMEOUT_CLEAR (0x14 / 4) 43854123bfSCédric Le Goater 44854123bfSCédric Le Goater #define WDT_RESTART_MAGIC 0x4755 45854123bfSCédric Le Goater 46854123bfSCédric Le Goater static bool aspeed_wdt_is_enabled(const AspeedWDTState *s) 47854123bfSCédric Le Goater { 48854123bfSCédric Le Goater return s->regs[WDT_CTRL] & WDT_CTRL_ENABLE; 49854123bfSCédric Le Goater } 50854123bfSCédric Le Goater 51*f55d613bSAndrew Jeffery static bool is_ast2500(const AspeedWDTState *s) 52*f55d613bSAndrew Jeffery { 53*f55d613bSAndrew Jeffery switch (s->silicon_rev) { 54*f55d613bSAndrew Jeffery case AST2500_A0_SILICON_REV: 55*f55d613bSAndrew Jeffery case AST2500_A1_SILICON_REV: 56*f55d613bSAndrew Jeffery return true; 57*f55d613bSAndrew Jeffery case AST2400_A0_SILICON_REV: 58*f55d613bSAndrew Jeffery case AST2400_A1_SILICON_REV: 59*f55d613bSAndrew Jeffery default: 60*f55d613bSAndrew Jeffery break; 61*f55d613bSAndrew Jeffery } 62*f55d613bSAndrew Jeffery 63*f55d613bSAndrew Jeffery return false; 64*f55d613bSAndrew Jeffery } 65*f55d613bSAndrew Jeffery 66854123bfSCédric Le Goater static uint64_t aspeed_wdt_read(void *opaque, hwaddr offset, unsigned size) 67854123bfSCédric Le Goater { 68854123bfSCédric Le Goater AspeedWDTState *s = ASPEED_WDT(opaque); 69854123bfSCédric Le Goater 70854123bfSCédric Le Goater offset >>= 2; 71854123bfSCédric Le Goater 72854123bfSCédric Le Goater switch (offset) { 73854123bfSCédric Le Goater case WDT_STATUS: 74854123bfSCédric Le Goater return s->regs[WDT_STATUS]; 75854123bfSCédric Le Goater case WDT_RELOAD_VALUE: 76854123bfSCédric Le Goater return s->regs[WDT_RELOAD_VALUE]; 77854123bfSCédric Le Goater case WDT_RESTART: 78854123bfSCédric Le Goater qemu_log_mask(LOG_GUEST_ERROR, 79854123bfSCédric Le Goater "%s: read from write-only reg at offset 0x%" 80854123bfSCédric Le Goater HWADDR_PRIx "\n", __func__, offset); 81854123bfSCédric Le Goater return 0; 82854123bfSCédric Le Goater case WDT_CTRL: 83854123bfSCédric Le Goater return s->regs[WDT_CTRL]; 84*f55d613bSAndrew Jeffery case WDT_RESET_WIDTH: 85*f55d613bSAndrew Jeffery return s->regs[WDT_RESET_WIDTH]; 86854123bfSCédric Le Goater case WDT_TIMEOUT_STATUS: 87854123bfSCédric Le Goater case WDT_TIMEOUT_CLEAR: 88854123bfSCédric Le Goater qemu_log_mask(LOG_UNIMP, 89854123bfSCédric Le Goater "%s: uninmplemented read at offset 0x%" HWADDR_PRIx "\n", 90854123bfSCédric Le Goater __func__, offset); 91854123bfSCédric Le Goater return 0; 92854123bfSCédric Le Goater default: 93854123bfSCédric Le Goater qemu_log_mask(LOG_GUEST_ERROR, 94854123bfSCédric Le Goater "%s: Out-of-bounds read at offset 0x%" HWADDR_PRIx "\n", 95854123bfSCédric Le Goater __func__, offset); 96854123bfSCédric Le Goater return 0; 97854123bfSCédric Le Goater } 98854123bfSCédric Le Goater 99854123bfSCédric Le Goater } 100854123bfSCédric Le Goater 101854123bfSCédric Le Goater static void aspeed_wdt_reload(AspeedWDTState *s, bool pclk) 102854123bfSCédric Le Goater { 103854123bfSCédric Le Goater uint32_t reload; 104854123bfSCédric Le Goater 105854123bfSCédric Le Goater if (pclk) { 106854123bfSCédric Le Goater reload = muldiv64(s->regs[WDT_RELOAD_VALUE], NANOSECONDS_PER_SECOND, 107854123bfSCédric Le Goater s->pclk_freq); 108854123bfSCédric Le Goater } else { 109854123bfSCédric Le Goater reload = s->regs[WDT_RELOAD_VALUE] * 1000; 110854123bfSCédric Le Goater } 111854123bfSCédric Le Goater 112854123bfSCédric Le Goater if (aspeed_wdt_is_enabled(s)) { 113854123bfSCédric Le Goater timer_mod(s->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + reload); 114854123bfSCédric Le Goater } 115854123bfSCédric Le Goater } 116854123bfSCédric Le Goater 117854123bfSCédric Le Goater static void aspeed_wdt_write(void *opaque, hwaddr offset, uint64_t data, 118854123bfSCédric Le Goater unsigned size) 119854123bfSCédric Le Goater { 120854123bfSCédric Le Goater AspeedWDTState *s = ASPEED_WDT(opaque); 121854123bfSCédric Le Goater bool enable = data & WDT_CTRL_ENABLE; 122854123bfSCédric Le Goater 123854123bfSCédric Le Goater offset >>= 2; 124854123bfSCédric Le Goater 125854123bfSCédric Le Goater switch (offset) { 126854123bfSCédric Le Goater case WDT_STATUS: 127854123bfSCédric Le Goater qemu_log_mask(LOG_GUEST_ERROR, 128854123bfSCédric Le Goater "%s: write to read-only reg at offset 0x%" 129854123bfSCédric Le Goater HWADDR_PRIx "\n", __func__, offset); 130854123bfSCédric Le Goater break; 131854123bfSCédric Le Goater case WDT_RELOAD_VALUE: 132854123bfSCédric Le Goater s->regs[WDT_RELOAD_VALUE] = data; 133854123bfSCédric Le Goater break; 134854123bfSCédric Le Goater case WDT_RESTART: 135854123bfSCédric Le Goater if ((data & 0xFFFF) == WDT_RESTART_MAGIC) { 136854123bfSCédric Le Goater s->regs[WDT_STATUS] = s->regs[WDT_RELOAD_VALUE]; 137854123bfSCédric Le Goater aspeed_wdt_reload(s, !(data & WDT_CTRL_1MHZ_CLK)); 138854123bfSCédric Le Goater } 139854123bfSCédric Le Goater break; 140854123bfSCédric Le Goater case WDT_CTRL: 141854123bfSCédric Le Goater if (enable && !aspeed_wdt_is_enabled(s)) { 142854123bfSCédric Le Goater s->regs[WDT_CTRL] = data; 143854123bfSCédric Le Goater aspeed_wdt_reload(s, !(data & WDT_CTRL_1MHZ_CLK)); 144854123bfSCédric Le Goater } else if (!enable && aspeed_wdt_is_enabled(s)) { 145854123bfSCédric Le Goater s->regs[WDT_CTRL] = data; 146854123bfSCédric Le Goater timer_del(s->timer); 147854123bfSCédric Le Goater } 148854123bfSCédric Le Goater break; 149*f55d613bSAndrew Jeffery case WDT_RESET_WIDTH: 150*f55d613bSAndrew Jeffery { 151*f55d613bSAndrew Jeffery uint32_t property = data & WDT_POLARITY_MASK; 152*f55d613bSAndrew Jeffery 153*f55d613bSAndrew Jeffery if (property && is_ast2500(s)) { 154*f55d613bSAndrew Jeffery if (property == WDT_ACTIVE_HIGH_MAGIC) { 155*f55d613bSAndrew Jeffery s->regs[WDT_RESET_WIDTH] |= WDT_RESET_WIDTH_ACTIVE_HIGH; 156*f55d613bSAndrew Jeffery } else if (property == WDT_ACTIVE_LOW_MAGIC) { 157*f55d613bSAndrew Jeffery s->regs[WDT_RESET_WIDTH] &= ~WDT_RESET_WIDTH_ACTIVE_HIGH; 158*f55d613bSAndrew Jeffery } else if (property == WDT_PUSH_PULL_MAGIC) { 159*f55d613bSAndrew Jeffery s->regs[WDT_RESET_WIDTH] |= WDT_RESET_WIDTH_PUSH_PULL; 160*f55d613bSAndrew Jeffery } else if (property == WDT_OPEN_DRAIN_MAGIC) { 161*f55d613bSAndrew Jeffery s->regs[WDT_RESET_WIDTH] &= ~WDT_RESET_WIDTH_PUSH_PULL; 162*f55d613bSAndrew Jeffery } 163*f55d613bSAndrew Jeffery } 164*f55d613bSAndrew Jeffery s->regs[WDT_RESET_WIDTH] &= ~s->ext_pulse_width_mask; 165*f55d613bSAndrew Jeffery s->regs[WDT_RESET_WIDTH] |= data & s->ext_pulse_width_mask; 166*f55d613bSAndrew Jeffery break; 167*f55d613bSAndrew Jeffery } 168854123bfSCédric Le Goater case WDT_TIMEOUT_STATUS: 169854123bfSCédric Le Goater case WDT_TIMEOUT_CLEAR: 170854123bfSCédric Le Goater qemu_log_mask(LOG_UNIMP, 171854123bfSCédric Le Goater "%s: uninmplemented write at offset 0x%" HWADDR_PRIx "\n", 172854123bfSCédric Le Goater __func__, offset); 173854123bfSCédric Le Goater break; 174854123bfSCédric Le Goater default: 175854123bfSCédric Le Goater qemu_log_mask(LOG_GUEST_ERROR, 176854123bfSCédric Le Goater "%s: Out-of-bounds write at offset 0x%" HWADDR_PRIx "\n", 177854123bfSCédric Le Goater __func__, offset); 178854123bfSCédric Le Goater } 179854123bfSCédric Le Goater return; 180854123bfSCédric Le Goater } 181854123bfSCédric Le Goater 182854123bfSCédric Le Goater static WatchdogTimerModel model = { 183854123bfSCédric Le Goater .wdt_name = TYPE_ASPEED_WDT, 184854123bfSCédric Le Goater .wdt_description = "Aspeed watchdog device", 185854123bfSCédric Le Goater }; 186854123bfSCédric Le Goater 187854123bfSCédric Le Goater static const VMStateDescription vmstate_aspeed_wdt = { 188854123bfSCédric Le Goater .name = "vmstate_aspeed_wdt", 189854123bfSCédric Le Goater .version_id = 0, 190854123bfSCédric Le Goater .minimum_version_id = 0, 191854123bfSCédric Le Goater .fields = (VMStateField[]) { 192854123bfSCédric Le Goater VMSTATE_TIMER_PTR(timer, AspeedWDTState), 193854123bfSCédric Le Goater VMSTATE_UINT32_ARRAY(regs, AspeedWDTState, ASPEED_WDT_REGS_MAX), 194854123bfSCédric Le Goater VMSTATE_END_OF_LIST() 195854123bfSCédric Le Goater } 196854123bfSCédric Le Goater }; 197854123bfSCédric Le Goater 198854123bfSCédric Le Goater static const MemoryRegionOps aspeed_wdt_ops = { 199854123bfSCédric Le Goater .read = aspeed_wdt_read, 200854123bfSCédric Le Goater .write = aspeed_wdt_write, 201854123bfSCédric Le Goater .endianness = DEVICE_LITTLE_ENDIAN, 202854123bfSCédric Le Goater .valid.min_access_size = 4, 203854123bfSCédric Le Goater .valid.max_access_size = 4, 204854123bfSCédric Le Goater .valid.unaligned = false, 205854123bfSCédric Le Goater }; 206854123bfSCédric Le Goater 207854123bfSCédric Le Goater static void aspeed_wdt_reset(DeviceState *dev) 208854123bfSCédric Le Goater { 209854123bfSCédric Le Goater AspeedWDTState *s = ASPEED_WDT(dev); 210854123bfSCédric Le Goater 211854123bfSCédric Le Goater s->regs[WDT_STATUS] = 0x3EF1480; 212854123bfSCédric Le Goater s->regs[WDT_RELOAD_VALUE] = 0x03EF1480; 213854123bfSCédric Le Goater s->regs[WDT_RESTART] = 0; 214854123bfSCédric Le Goater s->regs[WDT_CTRL] = 0; 215*f55d613bSAndrew Jeffery s->regs[WDT_RESET_WIDTH] = 0xFF; 216854123bfSCédric Le Goater 217854123bfSCédric Le Goater timer_del(s->timer); 218854123bfSCédric Le Goater } 219854123bfSCédric Le Goater 220854123bfSCédric Le Goater static void aspeed_wdt_timer_expired(void *dev) 221854123bfSCédric Le Goater { 222854123bfSCédric Le Goater AspeedWDTState *s = ASPEED_WDT(dev); 223854123bfSCédric Le Goater 224854123bfSCédric Le Goater qemu_log_mask(CPU_LOG_RESET, "Watchdog timer expired.\n"); 225854123bfSCédric Le Goater watchdog_perform_action(); 226854123bfSCédric Le Goater timer_del(s->timer); 227854123bfSCédric Le Goater } 228854123bfSCédric Le Goater 229854123bfSCédric Le Goater #define PCLK_HZ 24000000 230854123bfSCédric Le Goater 231854123bfSCédric Le Goater static void aspeed_wdt_realize(DeviceState *dev, Error **errp) 232854123bfSCédric Le Goater { 233854123bfSCédric Le Goater SysBusDevice *sbd = SYS_BUS_DEVICE(dev); 234854123bfSCédric Le Goater AspeedWDTState *s = ASPEED_WDT(dev); 235854123bfSCédric Le Goater 236*f55d613bSAndrew Jeffery if (!is_supported_silicon_rev(s->silicon_rev)) { 237*f55d613bSAndrew Jeffery error_setg(errp, "Unknown silicon revision: 0x%" PRIx32, 238*f55d613bSAndrew Jeffery s->silicon_rev); 239*f55d613bSAndrew Jeffery return; 240*f55d613bSAndrew Jeffery } 241*f55d613bSAndrew Jeffery 242*f55d613bSAndrew Jeffery switch (s->silicon_rev) { 243*f55d613bSAndrew Jeffery case AST2400_A0_SILICON_REV: 244*f55d613bSAndrew Jeffery case AST2400_A1_SILICON_REV: 245*f55d613bSAndrew Jeffery s->ext_pulse_width_mask = 0xff; 246*f55d613bSAndrew Jeffery break; 247*f55d613bSAndrew Jeffery case AST2500_A0_SILICON_REV: 248*f55d613bSAndrew Jeffery case AST2500_A1_SILICON_REV: 249*f55d613bSAndrew Jeffery s->ext_pulse_width_mask = 0xfffff; 250*f55d613bSAndrew Jeffery break; 251*f55d613bSAndrew Jeffery default: 252*f55d613bSAndrew Jeffery g_assert_not_reached(); 253*f55d613bSAndrew Jeffery } 254*f55d613bSAndrew Jeffery 255854123bfSCédric Le Goater s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, aspeed_wdt_timer_expired, dev); 256854123bfSCédric Le Goater 257854123bfSCédric Le Goater /* FIXME: This setting should be derived from the SCU hw strapping 258854123bfSCédric Le Goater * register SCU70 259854123bfSCédric Le Goater */ 260854123bfSCédric Le Goater s->pclk_freq = PCLK_HZ; 261854123bfSCédric Le Goater 262854123bfSCédric Le Goater memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_wdt_ops, s, 263854123bfSCédric Le Goater TYPE_ASPEED_WDT, ASPEED_WDT_REGS_MAX * 4); 264854123bfSCédric Le Goater sysbus_init_mmio(sbd, &s->iomem); 265854123bfSCédric Le Goater } 266854123bfSCédric Le Goater 267*f55d613bSAndrew Jeffery static Property aspeed_wdt_properties[] = { 268*f55d613bSAndrew Jeffery DEFINE_PROP_UINT32("silicon-rev", AspeedWDTState, silicon_rev, 0), 269*f55d613bSAndrew Jeffery DEFINE_PROP_END_OF_LIST(), 270*f55d613bSAndrew Jeffery }; 271*f55d613bSAndrew Jeffery 272854123bfSCédric Le Goater static void aspeed_wdt_class_init(ObjectClass *klass, void *data) 273854123bfSCédric Le Goater { 274854123bfSCédric Le Goater DeviceClass *dc = DEVICE_CLASS(klass); 275854123bfSCédric Le Goater 276854123bfSCédric Le Goater dc->realize = aspeed_wdt_realize; 277854123bfSCédric Le Goater dc->reset = aspeed_wdt_reset; 278854123bfSCédric Le Goater set_bit(DEVICE_CATEGORY_MISC, dc->categories); 279854123bfSCédric Le Goater dc->vmsd = &vmstate_aspeed_wdt; 280*f55d613bSAndrew Jeffery dc->props = aspeed_wdt_properties; 281854123bfSCédric Le Goater } 282854123bfSCédric Le Goater 283854123bfSCédric Le Goater static const TypeInfo aspeed_wdt_info = { 284854123bfSCédric Le Goater .parent = TYPE_SYS_BUS_DEVICE, 285854123bfSCédric Le Goater .name = TYPE_ASPEED_WDT, 286854123bfSCédric Le Goater .instance_size = sizeof(AspeedWDTState), 287854123bfSCédric Le Goater .class_init = aspeed_wdt_class_init, 288854123bfSCédric Le Goater }; 289854123bfSCédric Le Goater 290854123bfSCédric Le Goater static void wdt_aspeed_register_types(void) 291854123bfSCédric Le Goater { 292854123bfSCédric Le Goater watchdog_add_model(&model); 293854123bfSCédric Le Goater type_register_static(&aspeed_wdt_info); 294854123bfSCédric Le Goater } 295854123bfSCédric Le Goater 296854123bfSCédric Le Goater type_init(wdt_aspeed_register_types) 297