/* * TI OMAP2 general purpose timers emulation. * * Copyright (C) 2007-2008 Nokia Corporation * Written by Andrzej Zaborowski * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 or * (at your option) any later version of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, see . */ #include "hw.h" #include "qemu-timer.h" #include "omap.h" /* GP timers */ struct omap_gp_timer_s { MemoryRegion iomem; qemu_irq irq; qemu_irq wkup; qemu_irq in; qemu_irq out; omap_clk clk; QEMUTimer *timer; QEMUTimer *match; struct omap_target_agent_s *ta; int in_val; int out_val; int64_t time; int64_t rate; int64_t ticks_per_sec; int16_t config; int status; int it_ena; int wu_ena; int enable; int inout; int capt2; int pt; enum { gpt_trigger_none, gpt_trigger_overflow, gpt_trigger_both } trigger; enum { gpt_capture_none, gpt_capture_rising, gpt_capture_falling, gpt_capture_both } capture; int scpwm; int ce; int pre; int ptv; int ar; int st; int posted; uint32_t val; uint32_t load_val; uint32_t capture_val[2]; uint32_t match_val; int capt_num; uint16_t writeh; /* LSB */ uint16_t readh; /* MSB */ }; #define GPT_TCAR_IT (1 << 2) #define GPT_OVF_IT (1 << 1) #define GPT_MAT_IT (1 << 0) static inline void omap_gp_timer_intr(struct omap_gp_timer_s *timer, int it) { if (timer->it_ena & it) { if (!timer->status) qemu_irq_raise(timer->irq); timer->status |= it; /* Or are the status bits set even when masked? * i.e. is masking applied before or after the status register? */ } if (timer->wu_ena & it) qemu_irq_pulse(timer->wkup); } static inline void omap_gp_timer_out(struct omap_gp_timer_s *timer, int level) { if (!timer->inout && timer->out_val != level) { timer->out_val = level; qemu_set_irq(timer->out, level); } } static inline uint32_t omap_gp_timer_read(struct omap_gp_timer_s *timer) { uint64_t distance; if (timer->st && timer->rate) { distance = qemu_get_clock_ns(vm_clock) - timer->time; distance = muldiv64(distance, timer->rate, timer->ticks_per_sec); if (distance >= 0xffffffff - timer->val) return 0xffffffff; else return timer->val + distance; } else return timer->val; } static inline void omap_gp_timer_sync(struct omap_gp_timer_s *timer) { if (timer->st) { timer->val = omap_gp_timer_read(timer); timer->time = qemu_get_clock_ns(vm_clock); } } static inline void omap_gp_timer_update(struct omap_gp_timer_s *timer) { int64_t expires, matches; if (timer->st && timer->rate) { expires = muldiv64(0x100000000ll - timer->val, timer->ticks_per_sec, timer->rate); qemu_mod_timer(timer->timer, timer->time + expires); if (timer->ce && timer->match_val >= timer->val) { matches = muldiv64(timer->match_val - timer->val, timer->ticks_per_sec, timer->rate); qemu_mod_timer(timer->match, timer->time + matches); } else qemu_del_timer(timer->match); } else { qemu_del_timer(timer->timer); qemu_del_timer(timer->match); omap_gp_timer_out(timer, timer->scpwm); } } static inline void omap_gp_timer_trigger(struct omap_gp_timer_s *timer) { if (timer->pt) /* TODO in overflow-and-match mode if the first event to * occur is the match, don't toggle. */ omap_gp_timer_out(timer, !timer->out_val); else /* TODO inverted pulse on timer->out_val == 1? */ qemu_irq_pulse(timer->out); } static void omap_gp_timer_tick(void *opaque) { struct omap_gp_timer_s *timer = (struct omap_gp_timer_s *) opaque; if (!timer->ar) { timer->st = 0; timer->val = 0; } else { timer->val = timer->load_val; timer->time = qemu_get_clock_ns(vm_clock); } if (timer->trigger == gpt_trigger_overflow || timer->trigger == gpt_trigger_both) omap_gp_timer_trigger(timer); omap_gp_timer_intr(timer, GPT_OVF_IT); omap_gp_timer_update(timer); } static void omap_gp_timer_match(void *opaque) { struct omap_gp_timer_s *timer = (struct omap_gp_timer_s *) opaque; if (timer->trigger == gpt_trigger_both) omap_gp_timer_trigger(timer); omap_gp_timer_intr(timer, GPT_MAT_IT); } static void omap_gp_timer_input(void *opaque, int line, int on) { struct omap_gp_timer_s *s = (struct omap_gp_timer_s *) opaque; int trigger; switch (s->capture) { default: case gpt_capture_none: trigger = 0; break; case gpt_capture_rising: trigger = !s->in_val && on; break; case gpt_capture_falling: trigger = s->in_val && !on; break; case gpt_capture_both: trigger = (s->in_val == !on); break; } s->in_val = on; if (s->inout && trigger && s->capt_num < 2) { s->capture_val[s->capt_num] = omap_gp_timer_read(s); if (s->capt2 == s->capt_num ++) omap_gp_timer_intr(s, GPT_TCAR_IT); } } static void omap_gp_timer_clk_update(void *opaque, int line, int on) { struct omap_gp_timer_s *timer = (struct omap_gp_timer_s *) opaque; omap_gp_timer_sync(timer); timer->rate = on ? omap_clk_getrate(timer->clk) : 0; omap_gp_timer_update(timer); } static void omap_gp_timer_clk_setup(struct omap_gp_timer_s *timer) { omap_clk_adduser(timer->clk, qemu_allocate_irqs(omap_gp_timer_clk_update, timer, 1)[0]); timer->rate = omap_clk_getrate(timer->clk); } void omap_gp_timer_reset(struct omap_gp_timer_s *s) { s->config = 0x000; s->status = 0; s->it_ena = 0; s->wu_ena = 0; s->inout = 0; s->capt2 = 0; s->capt_num = 0; s->pt = 0; s->trigger = gpt_trigger_none; s->capture = gpt_capture_none; s->scpwm = 0; s->ce = 0; s->pre = 0; s->ptv = 0; s->ar = 0; s->st = 0; s->posted = 1; s->val = 0x00000000; s->load_val = 0x00000000; s->capture_val[0] = 0x00000000; s->capture_val[1] = 0x00000000; s->match_val = 0x00000000; omap_gp_timer_update(s); } static uint32_t omap_gp_timer_readw(void *opaque, target_phys_addr_t addr) { struct omap_gp_timer_s *s = (struct omap_gp_timer_s *) opaque; switch (addr) { case 0x00: /* TIDR */ return 0x21; case 0x10: /* TIOCP_CFG */ return s->config; case 0x14: /* TISTAT */ /* ??? When's this bit reset? */ return 1; /* RESETDONE */ case 0x18: /* TISR */ return s->status; case 0x1c: /* TIER */ return s->it_ena; case 0x20: /* TWER */ return s->wu_ena; case 0x24: /* TCLR */ return (s->inout << 14) | (s->capt2 << 13) | (s->pt << 12) | (s->trigger << 10) | (s->capture << 8) | (s->scpwm << 7) | (s->ce << 6) | (s->pre << 5) | (s->ptv << 2) | (s->ar << 1) | (s->st << 0); case 0x28: /* TCRR */ return omap_gp_timer_read(s); case 0x2c: /* TLDR */ return s->load_val; case 0x30: /* TTGR */ return 0xffffffff; case 0x34: /* TWPS */ return 0x00000000; /* No posted writes pending. */ case 0x38: /* TMAR */ return s->match_val; case 0x3c: /* TCAR1 */ return s->capture_val[0]; case 0x40: /* TSICR */ return s->posted << 2; case 0x44: /* TCAR2 */ return s->capture_val[1]; } OMAP_BAD_REG(addr); return 0; } static uint32_t omap_gp_timer_readh(void *opaque, target_phys_addr_t addr) { struct omap_gp_timer_s *s = (struct omap_gp_timer_s *) opaque; uint32_t ret; if (addr & 2) return s->readh; else { ret = omap_gp_timer_readw(opaque, addr); s->readh = ret >> 16; return ret & 0xffff; } } static void omap_gp_timer_write(void *opaque, target_phys_addr_t addr, uint32_t value) { struct omap_gp_timer_s *s = (struct omap_gp_timer_s *) opaque; switch (addr) { case 0x00: /* TIDR */ case 0x14: /* TISTAT */ case 0x34: /* TWPS */ case 0x3c: /* TCAR1 */ case 0x44: /* TCAR2 */ OMAP_RO_REG(addr); break; case 0x10: /* TIOCP_CFG */ s->config = value & 0x33d; if (((value >> 3) & 3) == 3) /* IDLEMODE */ fprintf(stderr, "%s: illegal IDLEMODE value in TIOCP_CFG\n", __FUNCTION__); if (value & 2) /* SOFTRESET */ omap_gp_timer_reset(s); break; case 0x18: /* TISR */ if (value & GPT_TCAR_IT) s->capt_num = 0; if (s->status && !(s->status &= ~value)) qemu_irq_lower(s->irq); break; case 0x1c: /* TIER */ s->it_ena = value & 7; break; case 0x20: /* TWER */ s->wu_ena = value & 7; break; case 0x24: /* TCLR */ omap_gp_timer_sync(s); s->inout = (value >> 14) & 1; s->capt2 = (value >> 13) & 1; s->pt = (value >> 12) & 1; s->trigger = (value >> 10) & 3; if (s->capture == gpt_capture_none && ((value >> 8) & 3) != gpt_capture_none) s->capt_num = 0; s->capture = (value >> 8) & 3; s->scpwm = (value >> 7) & 1; s->ce = (value >> 6) & 1; s->pre = (value >> 5) & 1; s->ptv = (value >> 2) & 7; s->ar = (value >> 1) & 1; s->st = (value >> 0) & 1; if (s->inout && s->trigger != gpt_trigger_none) fprintf(stderr, "%s: GP timer pin must be an output " "for this trigger mode\n", __FUNCTION__); if (!s->inout && s->capture != gpt_capture_none) fprintf(stderr, "%s: GP timer pin must be an input " "for this capture mode\n", __FUNCTION__); if (s->trigger == gpt_trigger_none) omap_gp_timer_out(s, s->scpwm); /* TODO: make sure this doesn't overflow 32-bits */ s->ticks_per_sec = get_ticks_per_sec() << (s->pre ? s->ptv + 1 : 0); omap_gp_timer_update(s); break; case 0x28: /* TCRR */ s->time = qemu_get_clock_ns(vm_clock); s->val = value; omap_gp_timer_update(s); break; case 0x2c: /* TLDR */ s->load_val = value; break; case 0x30: /* TTGR */ s->time = qemu_get_clock_ns(vm_clock); s->val = s->load_val; omap_gp_timer_update(s); break; case 0x38: /* TMAR */ omap_gp_timer_sync(s); s->match_val = value; omap_gp_timer_update(s); break; case 0x40: /* TSICR */ s->posted = (value >> 2) & 1; if (value & 2) /* How much exactly are we supposed to reset? */ omap_gp_timer_reset(s); break; default: OMAP_BAD_REG(addr); } } static void omap_gp_timer_writeh(void *opaque, target_phys_addr_t addr, uint32_t value) { struct omap_gp_timer_s *s = (struct omap_gp_timer_s *) opaque; if (addr & 2) return omap_gp_timer_write(opaque, addr, (value << 16) | s->writeh); else s->writeh = (uint16_t) value; } static const MemoryRegionOps omap_gp_timer_ops = { .old_mmio = { .read = { omap_badwidth_read32, omap_gp_timer_readh, omap_gp_timer_readw, }, .write = { omap_badwidth_write32, omap_gp_timer_writeh, omap_gp_timer_write, }, }, .endianness = DEVICE_NATIVE_ENDIAN, }; struct omap_gp_timer_s *omap_gp_timer_init(struct omap_target_agent_s *ta, qemu_irq irq, omap_clk fclk, omap_clk iclk) { struct omap_gp_timer_s *s = (struct omap_gp_timer_s *) g_malloc0(sizeof(struct omap_gp_timer_s)); s->ta = ta; s->irq = irq; s->clk = fclk; s->timer = qemu_new_timer_ns(vm_clock, omap_gp_timer_tick, s); s->match = qemu_new_timer_ns(vm_clock, omap_gp_timer_match, s); s->in = qemu_allocate_irqs(omap_gp_timer_input, s, 1)[0]; omap_gp_timer_reset(s); omap_gp_timer_clk_setup(s); memory_region_init_io(&s->iomem, &omap_gp_timer_ops, s, "omap.gptimer", omap_l4_region_size(ta, 0)); omap_l4_attach_region(ta, 0, &s->iomem); return s; }