537 lines
13 KiB
C
537 lines
13 KiB
C
/* dv-m68hc11spi.c -- Simulation of the 68HC11 SPI
|
|
Copyright (C) 2000, 2002 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 version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
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 "dv-sockser.h"
|
|
#include "sim-assert.h"
|
|
|
|
|
|
/* DEVICE
|
|
|
|
m68hc11spi - m68hc11 SPI interface
|
|
|
|
|
|
DESCRIPTION
|
|
|
|
Implements the m68hc11 Synchronous Serial Peripheral Interface
|
|
described in the m68hc11 user guide (Chapter 8 in pink book).
|
|
The SPI I/O controller is directly connected to the CPU
|
|
interrupt. The simulator implements:
|
|
|
|
- SPI clock emulation
|
|
- Data transfer
|
|
- Write collision detection
|
|
|
|
|
|
PROPERTIES
|
|
|
|
None
|
|
|
|
|
|
PORTS
|
|
|
|
reset (input)
|
|
|
|
Reset port. This port is only used to simulate a reset of the SPI
|
|
I/O controller. It should be connected to the RESET output of the cpu.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* port ID's */
|
|
|
|
enum
|
|
{
|
|
RESET_PORT
|
|
};
|
|
|
|
|
|
static const struct hw_port_descriptor m68hc11spi_ports[] =
|
|
{
|
|
{ "reset", RESET_PORT, 0, input_port, },
|
|
{ NULL, },
|
|
};
|
|
|
|
|
|
/* SPI */
|
|
struct m68hc11spi
|
|
{
|
|
/* Information about next character to be transmited. */
|
|
unsigned char tx_char;
|
|
int tx_bit;
|
|
unsigned char mode;
|
|
|
|
unsigned char rx_char;
|
|
unsigned char rx_clear_scsr;
|
|
unsigned char clk_pin;
|
|
|
|
/* SPI clock rate (twice the real clock). */
|
|
unsigned int clock;
|
|
|
|
/* Periodic SPI event. */
|
|
struct hw_event* spi_event;
|
|
};
|
|
|
|
|
|
|
|
/* Finish off the partially created hw device. Attach our local
|
|
callbacks. Wire up our port names etc */
|
|
|
|
static hw_io_read_buffer_method m68hc11spi_io_read_buffer;
|
|
static hw_io_write_buffer_method m68hc11spi_io_write_buffer;
|
|
static hw_port_event_method m68hc11spi_port_event;
|
|
static hw_ioctl_method m68hc11spi_ioctl;
|
|
|
|
#define M6811_SPI_FIRST_REG (M6811_SPCR)
|
|
#define M6811_SPI_LAST_REG (M6811_SPDR)
|
|
|
|
|
|
static void
|
|
attach_m68hc11spi_regs (struct hw *me,
|
|
struct m68hc11spi *controller)
|
|
{
|
|
hw_attach_address (hw_parent (me), M6811_IO_LEVEL, io_map,
|
|
M6811_SPI_FIRST_REG,
|
|
M6811_SPI_LAST_REG - M6811_SPI_FIRST_REG + 1,
|
|
me);
|
|
}
|
|
|
|
static void
|
|
m68hc11spi_finish (struct hw *me)
|
|
{
|
|
struct m68hc11spi *controller;
|
|
|
|
controller = HW_ZALLOC (me, struct m68hc11spi);
|
|
set_hw_data (me, controller);
|
|
set_hw_io_read_buffer (me, m68hc11spi_io_read_buffer);
|
|
set_hw_io_write_buffer (me, m68hc11spi_io_write_buffer);
|
|
set_hw_ports (me, m68hc11spi_ports);
|
|
set_hw_port_event (me, m68hc11spi_port_event);
|
|
#ifdef set_hw_ioctl
|
|
set_hw_ioctl (me, m68hc11spi_ioctl);
|
|
#else
|
|
me->to_ioctl = m68hc11spi_ioctl;
|
|
#endif
|
|
|
|
/* Attach ourself to our parent bus. */
|
|
attach_m68hc11spi_regs (me, controller);
|
|
|
|
/* Initialize to reset state. */
|
|
controller->spi_event = NULL;
|
|
controller->rx_clear_scsr = 0;
|
|
}
|
|
|
|
|
|
|
|
/* An event arrives on an interrupt port */
|
|
|
|
static void
|
|
m68hc11spi_port_event (struct hw *me,
|
|
int my_port,
|
|
struct hw *source,
|
|
int source_port,
|
|
int level)
|
|
{
|
|
SIM_DESC sd;
|
|
struct m68hc11spi *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, "SPI reset"));
|
|
|
|
/* Reset the state of SPI registers. */
|
|
controller->rx_clear_scsr = 0;
|
|
if (controller->spi_event)
|
|
{
|
|
hw_event_queue_deschedule (me, controller->spi_event);
|
|
controller->spi_event = 0;
|
|
}
|
|
|
|
val = 0;
|
|
m68hc11spi_io_write_buffer (me, &val, io_map,
|
|
(unsigned_word) M6811_SPCR, 1);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
hw_abort (me, "Event on unknown port %d", my_port);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
set_bit_port (struct hw *me, sim_cpu *cpu, int port, int mask, int value)
|
|
{
|
|
uint8 val;
|
|
|
|
if (value)
|
|
val = cpu->ios[port] | mask;
|
|
else
|
|
val = cpu->ios[port] & ~mask;
|
|
|
|
/* Set the new value and post an event to inform other devices
|
|
that pin 'port' changed. */
|
|
m68hc11cpu_set_port (me, cpu, port, val);
|
|
}
|
|
|
|
|
|
/* When a character is sent/received by the SPI, the PD2..PD5 line
|
|
are driven by the following signals:
|
|
|
|
B7 B6
|
|
-----+---------+--------+---/-+-------
|
|
MOSI | | | | | |
|
|
MISO +---------+--------+---/-+
|
|
____ ___
|
|
CLK _______/ \____/ \__ CPOL=0, CPHA=0
|
|
_______ ____ __
|
|
\____/ \___/ CPOL=1, CPHA=0
|
|
____ ____ __
|
|
__/ \____/ \___/ CPOL=0, CPHA=1
|
|
__ ____ ___
|
|
\____/ \____/ \__ CPOL=1, CPHA=1
|
|
|
|
SS ___ ____
|
|
\__________________________//___/
|
|
|
|
MISO = PD2
|
|
MOSI = PD3
|
|
SCK = PD4
|
|
SS = PD5
|
|
|
|
*/
|
|
|
|
#define SPI_START_BYTE 0
|
|
#define SPI_START_BIT 1
|
|
#define SPI_MIDDLE_BIT 2
|
|
|
|
void
|
|
m68hc11spi_clock (struct hw *me, void *data)
|
|
{
|
|
SIM_DESC sd;
|
|
struct m68hc11spi* controller;
|
|
sim_cpu *cpu;
|
|
int check_interrupt = 0;
|
|
|
|
controller = hw_data (me);
|
|
sd = hw_system (me);
|
|
cpu = STATE_CPU (sd, 0);
|
|
|
|
/* Cleanup current event. */
|
|
if (controller->spi_event)
|
|
{
|
|
hw_event_queue_deschedule (me, controller->spi_event);
|
|
controller->spi_event = 0;
|
|
}
|
|
|
|
/* Change a bit of data at each two SPI event. */
|
|
if (controller->mode == SPI_START_BIT)
|
|
{
|
|
/* Reflect the bit value on bit 2 of port D. */
|
|
set_bit_port (me, cpu, M6811_PORTD, (1 << 2),
|
|
(controller->tx_char & (1 << controller->tx_bit)));
|
|
controller->tx_bit--;
|
|
controller->mode = SPI_MIDDLE_BIT;
|
|
}
|
|
else if (controller->mode == SPI_MIDDLE_BIT)
|
|
{
|
|
controller->mode = SPI_START_BIT;
|
|
}
|
|
|
|
if (controller->mode == SPI_START_BYTE)
|
|
{
|
|
/* Start a new SPI transfer. */
|
|
|
|
/* TBD: clear SS output. */
|
|
controller->mode = SPI_START_BIT;
|
|
controller->tx_bit = 7;
|
|
set_bit_port (me, cpu, M6811_PORTD, (1 << 4), ~controller->clk_pin);
|
|
}
|
|
else
|
|
{
|
|
/* Change the SPI clock at each event on bit 4 of port D. */
|
|
controller->clk_pin = ~controller->clk_pin;
|
|
set_bit_port (me, cpu, M6811_PORTD, (1 << 4), controller->clk_pin);
|
|
}
|
|
|
|
/* Transmit is now complete for this byte. */
|
|
if (controller->mode == SPI_START_BIT && controller->tx_bit < 0)
|
|
{
|
|
controller->rx_clear_scsr = 0;
|
|
cpu->ios[M6811_SPSR] |= M6811_SPIF;
|
|
if (cpu->ios[M6811_SPCR] & M6811_SPIE)
|
|
check_interrupt = 1;
|
|
}
|
|
else
|
|
{
|
|
controller->spi_event = hw_event_queue_schedule (me, controller->clock,
|
|
m68hc11spi_clock,
|
|
NULL);
|
|
}
|
|
|
|
if (check_interrupt)
|
|
interrupts_update_pending (&cpu->cpu_interrupts);
|
|
}
|
|
|
|
/* Flags of the SPCR register. */
|
|
io_reg_desc spcr_desc[] = {
|
|
{ M6811_SPIE, "SPIE ", "Serial Peripheral Interrupt Enable" },
|
|
{ M6811_SPE, "SPE ", "Serial Peripheral System Enable" },
|
|
{ M6811_DWOM, "DWOM ", "Port D Wire-OR mode option" },
|
|
{ M6811_MSTR, "MSTR ", "Master Mode Select" },
|
|
{ M6811_CPOL, "CPOL ", "Clock Polarity" },
|
|
{ M6811_CPHA, "CPHA ", "Clock Phase" },
|
|
{ M6811_SPR1, "SPR1 ", "SPI Clock Rate Select" },
|
|
{ M6811_SPR0, "SPR0 ", "SPI Clock Rate Select" },
|
|
{ 0, 0, 0 }
|
|
};
|
|
|
|
|
|
/* Flags of the SPSR register. */
|
|
io_reg_desc spsr_desc[] = {
|
|
{ M6811_SPIF, "SPIF ", "SPI Transfer Complete flag" },
|
|
{ M6811_WCOL, "WCOL ", "Write Collision" },
|
|
{ M6811_MODF, "MODF ", "Mode Fault" },
|
|
{ 0, 0, 0 }
|
|
};
|
|
|
|
static void
|
|
m68hc11spi_info (struct hw *me)
|
|
{
|
|
SIM_DESC sd;
|
|
uint16 base = 0;
|
|
sim_cpu *cpu;
|
|
struct m68hc11spi *controller;
|
|
uint8 val;
|
|
|
|
sd = hw_system (me);
|
|
cpu = STATE_CPU (sd, 0);
|
|
controller = hw_data (me);
|
|
|
|
sim_io_printf (sd, "M68HC11 SPI:\n");
|
|
|
|
base = cpu_get_io_base (cpu);
|
|
|
|
val = cpu->ios[M6811_SPCR];
|
|
print_io_byte (sd, "SPCR", spcr_desc, val, base + M6811_SPCR);
|
|
sim_io_printf (sd, "\n");
|
|
|
|
val = cpu->ios[M6811_SPSR];
|
|
print_io_byte (sd, "SPSR", spsr_desc, val, base + M6811_SPSR);
|
|
sim_io_printf (sd, "\n");
|
|
|
|
if (controller->spi_event)
|
|
{
|
|
signed64 t;
|
|
|
|
sim_io_printf (sd, " SPI has %d bits to send\n",
|
|
controller->tx_bit + 1);
|
|
t = hw_event_remain_time (me, controller->spi_event);
|
|
sim_io_printf (sd, " SPI current bit-cycle finished in %s\n",
|
|
cycle_to_string (cpu, t));
|
|
|
|
t += (controller->tx_bit + 1) * 2 * controller->clock;
|
|
sim_io_printf (sd, " SPI operation finished in %s\n",
|
|
cycle_to_string (cpu, t));
|
|
}
|
|
}
|
|
|
|
static int
|
|
m68hc11spi_ioctl (struct hw *me,
|
|
hw_ioctl_request request,
|
|
va_list ap)
|
|
{
|
|
m68hc11spi_info (me);
|
|
return 0;
|
|
}
|
|
|
|
/* generic read/write */
|
|
|
|
static unsigned
|
|
m68hc11spi_io_read_buffer (struct hw *me,
|
|
void *dest,
|
|
int space,
|
|
unsigned_word base,
|
|
unsigned nr_bytes)
|
|
{
|
|
SIM_DESC sd;
|
|
struct m68hc11spi *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)
|
|
{
|
|
case M6811_SPSR:
|
|
controller->rx_clear_scsr = cpu->ios[M6811_SCSR]
|
|
& (M6811_SPIF | M6811_WCOL | M6811_MODF);
|
|
|
|
case M6811_SPCR:
|
|
val = cpu->ios[base];
|
|
break;
|
|
|
|
case M6811_SPDR:
|
|
if (controller->rx_clear_scsr)
|
|
{
|
|
cpu->ios[M6811_SPSR] &= ~controller->rx_clear_scsr;
|
|
controller->rx_clear_scsr = 0;
|
|
interrupts_update_pending (&cpu->cpu_interrupts);
|
|
}
|
|
val = controller->rx_char;
|
|
break;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
*((unsigned8*) dest) = val;
|
|
return 1;
|
|
}
|
|
|
|
static unsigned
|
|
m68hc11spi_io_write_buffer (struct hw *me,
|
|
const void *source,
|
|
int space,
|
|
unsigned_word base,
|
|
unsigned nr_bytes)
|
|
{
|
|
SIM_DESC sd;
|
|
struct m68hc11spi *controller;
|
|
sim_cpu *cpu;
|
|
unsigned8 val;
|
|
|
|
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)
|
|
{
|
|
case M6811_SPCR:
|
|
cpu->ios[M6811_SPCR] = val;
|
|
|
|
/* The SPI clock rate is 2, 4, 16, 32 of the internal CPU clock.
|
|
We have to drive the clock pin and need a 2x faster clock. */
|
|
switch (val & (M6811_SPR1 | M6811_SPR0))
|
|
{
|
|
case 0:
|
|
controller->clock = 1;
|
|
break;
|
|
|
|
case 1:
|
|
controller->clock = 2;
|
|
break;
|
|
|
|
case 2:
|
|
controller->clock = 8;
|
|
break;
|
|
|
|
default:
|
|
controller->clock = 16;
|
|
break;
|
|
}
|
|
|
|
/* Set the clock pin. */
|
|
if ((val & M6811_CPOL)
|
|
&& (controller->spi_event == 0
|
|
|| ((val & M6811_CPHA) && controller->mode == 1)))
|
|
controller->clk_pin = 1;
|
|
else
|
|
controller->clk_pin = 0;
|
|
|
|
set_bit_port (me, cpu, M6811_PORTD, (1 << 4), controller->clk_pin);
|
|
break;
|
|
|
|
/* Can't write to SPSR. */
|
|
case M6811_SPSR:
|
|
break;
|
|
|
|
case M6811_SPDR:
|
|
if (!(cpu->ios[M6811_SPCR] & M6811_SPE))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (controller->rx_clear_scsr)
|
|
{
|
|
cpu->ios[M6811_SPSR] &= ~controller->rx_clear_scsr;
|
|
controller->rx_clear_scsr = 0;
|
|
interrupts_update_pending (&cpu->cpu_interrupts);
|
|
}
|
|
|
|
/* If transfer is taking place, a write to SPDR
|
|
generates a collision. */
|
|
if (controller->spi_event)
|
|
{
|
|
cpu->ios[M6811_SPSR] |= M6811_WCOL;
|
|
break;
|
|
}
|
|
|
|
/* Refuse the write if there was no read of SPSR. */
|
|
/* ???? TBD. */
|
|
|
|
/* Prepare to send a byte. */
|
|
controller->tx_char = val;
|
|
controller->mode = SPI_START_BYTE;
|
|
|
|
/* Toggle clock pin internal value when CPHA is 0 so that
|
|
it will really change in the middle of a bit. */
|
|
if (!(cpu->ios[M6811_SPCR] & M6811_CPHA))
|
|
controller->clk_pin = ~controller->clk_pin;
|
|
|
|
cpu->ios[M6811_SPDR] = val;
|
|
|
|
/* Activate transmission. */
|
|
m68hc11spi_clock (me, NULL);
|
|
break;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
return nr_bytes;
|
|
}
|
|
|
|
|
|
const struct hw_descriptor dv_m68hc11spi_descriptor[] = {
|
|
{ "m68hc11spi", m68hc11spi_finish },
|
|
{ "m68hc12spi", m68hc11spi_finish },
|
|
{ NULL },
|
|
};
|
|
|