summaryrefslogtreecommitdiff
path: root/sim/m68hc11/dv-m68hc11tim.c
diff options
context:
space:
mode:
Diffstat (limited to 'sim/m68hc11/dv-m68hc11tim.c')
-rw-r--r--sim/m68hc11/dv-m68hc11tim.c607
1 files changed, 607 insertions, 0 deletions
diff --git a/sim/m68hc11/dv-m68hc11tim.c b/sim/m68hc11/dv-m68hc11tim.c
new file mode 100644
index 00000000000..c672d3a5e2a
--- /dev/null
+++ b/sim/m68hc11/dv-m68hc11tim.c
@@ -0,0 +1,607 @@
+/* dv-m68hc11tim.c -- Simulation of the 68HC11 timer devices.
+ Copyright (C) 1999, 2000 Free Software Foundation, Inc.
+ Written by Stephane Carrez (stcarrez@worldnet.fr)
+ (From a driver model Contributed by Cygnus Solutions.)
+
+ This file is part of the program GDB, the GNU debugger.
+
+ 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 vertimn 2 of the License, or
+ (at your option) any later vertimn.
+
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+ */
+
+
+#include "sim-main.h"
+#include "hw-main.h"
+#include "sim-assert.h"
+
+
+/* DEVICE
+
+ m68hc11tim - m68hc11 timer devices
+
+
+ DESCRIPTION
+
+ Implements the m68hc11 timer as described in Chapter 10
+ of the pink book.
+
+
+ PROPERTIES
+
+ none
+
+
+ PORTS
+
+ reset (input)
+
+ Reset the timer device. This port must be connected to
+ the cpu-reset output port.
+
+ */
+
+
+
+/* port ID's */
+
+enum
+{
+ RESET_PORT
+};
+
+
+static const struct hw_port_descriptor m68hc11tim_ports[] =
+{
+ { "reset", RESET_PORT, 0, input_port, },
+ { NULL, },
+};
+
+
+/* Timer Controller information. */
+struct m68hc11tim
+{
+ unsigned long cop_delay;
+ unsigned long rti_delay;
+ unsigned long ovf_delay;
+ signed64 clock_prescaler;
+ signed64 tcnt_adjust;
+
+ /* Periodic timers. */
+ struct hw_event *rti_timer_event;
+ struct hw_event *cop_timer_event;
+ struct hw_event *tof_timer_event;
+ struct hw_event *cmp_timer_event;
+};
+
+
+
+/* Finish off the partially created hw device. Attach our local
+ callbacks. Wire up our port names etc. */
+
+static hw_io_read_buffer_method m68hc11tim_io_read_buffer;
+static hw_io_write_buffer_method m68hc11tim_io_write_buffer;
+static hw_port_event_method m68hc11tim_port_event;
+static hw_ioctl_method m68hc11tim_ioctl;
+
+#define M6811_TIMER_FIRST_REG (M6811_TCTN)
+#define M6811_TIMER_LAST_REG (M6811_PACNT)
+
+
+static void
+attach_m68hc11tim_regs (struct hw *me,
+ struct m68hc11tim *controller)
+{
+ hw_attach_address (hw_parent (me), 0, io_map,
+ M6811_TIMER_FIRST_REG,
+ M6811_TIMER_LAST_REG - M6811_TIMER_FIRST_REG + 1,
+ me);
+}
+
+
+static void
+m68hc11tim_finish (struct hw *me)
+{
+ struct m68hc11tim *controller;
+
+ controller = HW_ZALLOC (me, struct m68hc11tim);
+ me->overlap_mode_hw = 1;
+ set_hw_data (me, controller);
+ set_hw_io_read_buffer (me, m68hc11tim_io_read_buffer);
+ set_hw_io_write_buffer (me, m68hc11tim_io_write_buffer);
+ set_hw_ports (me, m68hc11tim_ports);
+ set_hw_port_event (me, m68hc11tim_port_event);
+#ifdef set_hw_ioctl
+ set_hw_ioctl (me, m68hc11tim_ioctl);
+#else
+ me->to_ioctl = m68hc11tim_ioctl;
+#endif
+
+ /* Preset defaults. */
+ controller->clock_prescaler = 1;
+ controller->tcnt_adjust = 0;
+
+ /* Attach ourself to our parent bus. */
+ attach_m68hc11tim_regs (me, controller);
+}
+
+
+
+/* An event arrives on an interrupt port. */
+
+static void
+m68hc11tim_port_event (struct hw *me,
+ int my_port,
+ struct hw *source,
+ int source_port,
+ int level)
+{
+ SIM_DESC sd;
+ struct m68hc11tim *controller;
+ sim_cpu *cpu;
+ unsigned8 val;
+
+ controller = hw_data (me);
+ sd = hw_system (me);
+ cpu = STATE_CPU (sd, 0);
+ switch (my_port)
+ {
+ case RESET_PORT:
+ {
+ HW_TRACE ((me, "Timer reset"));
+
+ /* Cancel all timer events. */
+ if (controller->rti_timer_event)
+ {
+ hw_event_queue_deschedule (me, controller->rti_timer_event);
+ controller->rti_timer_event = 0;
+ }
+ if (controller->cop_timer_event)
+ {
+ hw_event_queue_deschedule (me, controller->cop_timer_event);
+ controller->cop_timer_event = 0;
+ }
+ if (controller->tof_timer_event)
+ {
+ hw_event_queue_deschedule (me, controller->tof_timer_event);
+ controller->tof_timer_event = 0;
+ }
+ if (controller->cmp_timer_event)
+ {
+ hw_event_queue_deschedule (me, controller->cmp_timer_event);
+ controller->cmp_timer_event = 0;
+ }
+
+ /* Reset the state of Timer registers. This also restarts
+ the timer events (overflow and RTI clock). */
+ val = 0;
+ m68hc11tim_io_write_buffer (me, &val, io_map,
+ (unsigned_word) M6811_TMSK2, 1);
+ m68hc11tim_io_write_buffer (me, &val, io_map,
+ (unsigned_word) M6811_TFLG2, 1);
+ m68hc11tim_io_write_buffer (me, &val, io_map,
+ (unsigned_word) M6811_PACTL, 1);
+ break;
+ }
+
+ default:
+ hw_abort (me, "Event on unknown port %d", my_port);
+ break;
+ }
+}
+
+enum event_type
+{
+ COP_EVENT,
+ RTI_EVENT,
+ OVERFLOW_EVENT,
+ COMPARE_EVENT
+};
+
+void
+m68hc11tim_timer_event (struct hw *me, void *data)
+{
+ SIM_DESC sd;
+ struct m68hc11tim *controller;
+ sim_cpu *cpu;
+ enum event_type type;
+ unsigned long delay;
+ struct hw_event **eventp;
+ int check_interrupt = 0;
+ unsigned mask;
+ unsigned flags;
+ unsigned long tcnt;
+ int i;
+
+ controller = hw_data (me);
+ sd = hw_system (me);
+ cpu = STATE_CPU (sd, 0);
+ type = (enum event_type) ((long) data) & 0x0FF;
+
+ delay = 0;
+ switch (type)
+ {
+ case COP_EVENT:
+ eventp = &controller->cop_timer_event;
+ delay = controller->cop_delay;
+ check_interrupt = 1;
+ break;
+
+ case RTI_EVENT:
+ eventp = &controller->rti_timer_event;
+ delay = controller->rti_delay;
+ if (((long) (data) & 0x0100) == 0)
+ {
+ cpu->ios[M6811_TFLG2] |= M6811_RTIF;
+ check_interrupt = 1;
+ }
+ break;
+
+ case OVERFLOW_EVENT:
+ eventp = &controller->tof_timer_event;
+ delay = controller->ovf_delay;
+ cpu->ios[M6811_TFLG2] |= M6811_TOF;
+ break;
+
+ case COMPARE_EVENT:
+ eventp = &controller->cmp_timer_event;
+
+ /* Get current free running counter. */
+ tcnt = ((cpu->cpu_absolute_cycle - controller->tcnt_adjust)
+ / controller->clock_prescaler);
+ tcnt &= 0x0ffffL;
+
+ flags = cpu->ios[M6811_TMSK1];
+ mask = 0x80;
+ delay = 65536;
+
+ /* Scan each output compare register to see if one matches
+ the free running counter. Set the corresponding OCi flag
+ if the output compare is enabled. */
+ for (i = M6811_TOC1; i <= M6811_TOC5; i += 2, mask >>= 1)
+ {
+ unsigned short compare;
+
+ compare = (cpu->ios[i] << 8) + cpu->ios[i+1];
+ if (compare == tcnt && (flags & mask))
+ {
+ cpu->ios[M6811_TFLG1] |= mask;
+ check_interrupt++;
+ }
+
+ /* Compute how many times for the next match. */
+ if (compare > tcnt)
+ compare = compare - tcnt;
+ else
+ compare = compare - tcnt + 65536;
+
+ if (compare < delay)
+ delay = compare;
+ }
+ delay = delay * controller->clock_prescaler;
+
+ /* Deactivate the compare timer if no output compare is enabled. */
+ if ((flags & 0xF0) == 0)
+ delay = 0;
+ break;
+
+ default:
+ eventp = 0;
+ break;
+ }
+
+ if (*eventp)
+ {
+ hw_event_queue_deschedule (me, *eventp);
+ *eventp = 0;
+ }
+
+ if (delay != 0)
+ {
+ *eventp = hw_event_queue_schedule (me, delay,
+ m68hc11tim_timer_event,
+ (void*) type);
+ }
+
+ if (check_interrupt)
+ interrupts_update_pending (&cpu->cpu_interrupts);
+}
+
+
+/* Descriptions of the Timer I/O ports. These descriptions are only used to
+ give information of the Timer device under GDB. */
+io_reg_desc tmsk2_desc[] = {
+ { M6811_TOI, "TOI ", "Timer Overflow Interrupt Enable" },
+ { M6811_RTII, "RTII ", "RTI Interrupt Enable" },
+ { M6811_PAOVI, "PAOVI ", "Pulse Accumulator Overflow Interrupt Enable" },
+ { M6811_PAII, "PAII ", "Pulse Accumulator Interrupt Enable" },
+ { M6811_PR1, "PR1 ", "Timer prescaler (PR1)" },
+ { M6811_PR0, "PR0 ", "Timer prescaler (PR0)" },
+ { M6811_TPR_1, "TPR_1 ", "Timer prescaler div 1" },
+ { M6811_TPR_4, "TPR_4 ", "Timer prescaler div 4" },
+ { M6811_TPR_8, "TPR_8 ", "Timer prescaler div 8" },
+ { M6811_TPR_16, "TPR_16", "Timer prescaler div 16" },
+ { 0, 0, 0 }
+};
+
+io_reg_desc tflg2_desc[] = {
+ { M6811_TOF, "TOF ", "Timer Overflow Bit" },
+ { M6811_RTIF, "RTIF ", "Read Time Interrupt Flag" },
+ { M6811_PAOVF, "PAOVF ", "Pulse Accumulator Overflow Interrupt Flag" },
+ { M6811_PAIF, "PAIF ", "Pulse Accumulator Input Edge" },
+ { 0, 0, 0 }
+};
+
+io_reg_desc pactl_desc[] = {
+ { M6811_DDRA7, "DDRA7 ", "Data Direction for Port A bit-7" },
+ { M6811_PAEN, "PAEN ", "Pulse Accumulator System Enable" },
+ { M6811_PAMOD, "PAMOD ", "Pulse Accumulator Mode" },
+ { M6811_PEDGE, "PEDGE ", "Pulse Accumulator Edge Control" },
+ { M6811_RTR1, "RTR1 ", "RTI Interrupt rate select (RTR1)" },
+ { M6811_RTR0, "RTR0 ", "RTI Interrupt rate select (RTR0)" },
+ { 0, 0, 0 }
+};
+
+static double
+to_realtime (sim_cpu *cpu, signed64 t)
+{
+ return (double) (t) / (double) (cpu->cpu_frequency / 4);
+}
+
+static void
+m68hc11tim_print_timer (struct hw *me, const char *name,
+ struct hw_event *event)
+{
+ SIM_DESC sd;
+
+ sd = hw_system (me);
+ if (event == 0)
+ {
+ sim_io_printf (sd, " No %s interrupt will be raised.\n", name);
+ }
+ else
+ {
+ signed64 t;
+ double dt;
+ sim_cpu* cpu;
+
+ cpu = STATE_CPU (sd, 0);
+
+ t = hw_event_remain_time (me, event);
+ dt = to_realtime (cpu, t) * 1000.0;
+ sim_io_printf (sd, " Next %s interrupt in %ld cycles (%3.3f ms)\n",
+ name, (long) t, dt);
+ }
+}
+
+static void
+m68hc11tim_info (struct hw *me)
+{
+ SIM_DESC sd;
+ uint16 base = 0;
+ sim_cpu *cpu;
+ struct m68hc11tim *controller;
+ uint8 val;
+
+ sd = hw_system (me);
+ cpu = STATE_CPU (sd, 0);
+ controller = hw_data (me);
+
+ sim_io_printf (sd, "M68HC11 Timer:\n");
+
+ base = cpu_get_io_base (cpu);
+
+ val = cpu->ios[M6811_TMSK2];
+ print_io_byte (sd, "TMSK2 ", tmsk2_desc, val, base + M6811_TMSK2);
+ sim_io_printf (sd, "\n");
+
+ val = cpu->ios[M6811_TFLG2];
+ print_io_byte (sd, "TFLG2", tflg2_desc, val, base + M6811_TFLG2);
+ sim_io_printf (sd, "\n");
+
+ val = cpu->ios[M6811_PACTL];
+ print_io_byte (sd, "PACTL", pactl_desc, val, base + M6811_PACTL);
+ sim_io_printf (sd, "\n");
+
+ /* Give info about the next timer interrupts. */
+ m68hc11tim_print_timer (me, "RTI", controller->rti_timer_event);
+ m68hc11tim_print_timer (me, "COP", controller->cop_timer_event);
+ m68hc11tim_print_timer (me, "OVERFLOW", controller->tof_timer_event);
+ m68hc11tim_print_timer (me, "COMPARE", controller->cmp_timer_event);
+}
+
+static int
+m68hc11tim_ioctl (struct hw *me,
+ hw_ioctl_request request,
+ va_list ap)
+{
+ m68hc11tim_info (me);
+ return 0;
+}
+
+/* generic read/write */
+
+static unsigned
+m68hc11tim_io_read_buffer (struct hw *me,
+ void *dest,
+ int space,
+ unsigned_word base,
+ unsigned nr_bytes)
+{
+ SIM_DESC sd;
+ struct m68hc11tim *controller;
+ sim_cpu *cpu;
+ unsigned8 val;
+
+ HW_TRACE ((me, "read 0x%08lx %d", (long) base, (int) nr_bytes));
+
+ sd = hw_system (me);
+ cpu = STATE_CPU (sd, 0);
+ controller = hw_data (me);
+
+ switch (base)
+ {
+ /* The cpu_absolute_cycle is updated after each instruction.
+ Reading in a 16-bit register will be split in two accesses
+ but this will be atomic within the simulator. */
+ case M6811_TCTN_H:
+ val = (uint8) ((cpu->cpu_absolute_cycle - controller->tcnt_adjust)
+ / (controller->clock_prescaler * 256));
+ break;
+
+ case M6811_TCTN_L:
+ val = (uint8) ((cpu->cpu_absolute_cycle - controller->tcnt_adjust)
+ / controller->clock_prescaler);
+ break;
+
+ default:
+ val = cpu->ios[base];
+ break;
+ }
+ *((unsigned8*) dest) = val;
+ return 1;
+}
+
+static unsigned
+m68hc11tim_io_write_buffer (struct hw *me,
+ const void *source,
+ int space,
+ unsigned_word base,
+ unsigned nr_bytes)
+{
+ SIM_DESC sd;
+ struct m68hc11tim *controller;
+ sim_cpu *cpu;
+ unsigned8 val, n;
+ signed64 adj;
+ int reset_compare = 0;
+
+ HW_TRACE ((me, "write 0x%08lx %d", (long) base, (int) nr_bytes));
+
+ sd = hw_system (me);
+ cpu = STATE_CPU (sd, 0);
+ controller = hw_data (me);
+
+ val = *((const unsigned8*) source);
+ switch (base)
+ {
+ /* Set the timer counter low part, trying to preserve the low part.
+ We compute the absolute cycle adjustment that we have to apply
+ to obtain the timer current value. Computation must be made
+ in 64-bit to avoid overflow problems. */
+ case M6811_TCTN_L:
+ adj = ((cpu->cpu_absolute_cycle - controller->tcnt_adjust)
+ / (controller->clock_prescaler * (signed64) 256)) & 0x0FF;
+ adj = cpu->cpu_absolute_cycle
+ - (adj * controller->clock_prescaler * (signed64) 256)
+ - ((signed64) adj * controller->clock_prescaler);
+ controller->tcnt_adjust = adj;
+ reset_compare = 1;
+ break;
+
+ case M6811_TCTN_H:
+ adj = ((cpu->cpu_absolute_cycle - controller->tcnt_adjust)
+ / controller->clock_prescaler) & 0x0ff;
+ adj = cpu->cpu_absolute_cycle
+ - ((signed64) val * controller->clock_prescaler * (signed64) 256)
+ - (adj * controller->clock_prescaler);
+ controller->tcnt_adjust = adj;
+ reset_compare = 1;
+ break;
+
+ case M6811_TMSK2:
+
+ /* Timer prescaler cannot be changed after 64 bus cycles. */
+ if (cpu->cpu_absolute_cycle >= 64)
+ {
+ val &= ~(M6811_PR1 | M6811_PR0);
+ val |= cpu->ios[M6811_TMSK2] & (M6811_PR1 | M6811_PR0);
+ }
+ switch (val & (M6811_PR1 | M6811_PR0))
+ {
+ case 0:
+ n = 1;
+ break;
+ case M6811_PR0:
+ n = 4;
+ break;
+ case M6811_PR1:
+ n = 8;
+ break;
+ default:
+ case M6811_PR1 | M6811_PR0:
+ n = 16;
+ break;
+ }
+ if (controller->clock_prescaler != n)
+ {
+ controller->clock_prescaler = n;
+ controller->ovf_delay = n * 65536;
+ m68hc11tim_timer_event (me, (void*) (OVERFLOW_EVENT| 0x100));
+ }
+ cpu->ios[base] = val;
+ interrupts_update_pending (&cpu->cpu_interrupts);
+ break;
+
+ case M6811_PACTL:
+ n = (1 << ((val & (M6811_RTR1 | M6811_RTR0))));
+ cpu->ios[base] = val;
+
+ controller->rti_delay = (long) (n) * 8192;
+ m68hc11tim_timer_event (me, (void*) (RTI_EVENT| 0x100));
+ break;
+
+ case M6811_TFLG2:
+ if (val & M6811_TOF)
+ val &= ~M6811_TOF;
+ else
+ val |= cpu->ios[M6811_TFLG2] & M6811_TOF;
+
+ /* Clear the Real Time interrupt flag. */
+ if (val & M6811_RTIF)
+ val &= ~M6811_RTIF;
+ else
+ val |= cpu->ios[M6811_TFLG2] & M6811_RTIF;
+
+ cpu->ios[base] = val;
+ interrupts_update_pending (&cpu->cpu_interrupts);
+ break;
+
+ case M6811_TOC1:
+ case M6811_TOC2:
+ case M6811_TOC3:
+ case M6811_TOC4:
+ case M6811_TOC5:
+ cpu->ios[base] = val;
+ reset_compare = 1;
+ break;
+
+ default:
+ return 0;
+ }
+
+ /* Re-compute the next timer compare event. */
+ if (reset_compare)
+ {
+ m68hc11tim_timer_event (me, (void*) (COMPARE_EVENT));
+ }
+ return nr_bytes;
+}
+
+
+const struct hw_descriptor dv_m68hc11tim_descriptor[] = {
+ { "m68hc11tim", m68hc11tim_finish, },
+ { NULL },
+};
+