| /* |
| * TI OMAP2 general purpose timers emulation. |
| * |
| * Copyright (C) 2007-2008 Nokia Corporation |
| * Written by Andrzej Zaborowski <andrew@openedhand.com> |
| * |
| * 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 <http://www.gnu.org/licenses/>. |
| */ |
| #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; |
| } |