608 lines
16 KiB
C
608 lines
16 KiB
C
/* 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 },
|
|
};
|
|
|