diff --git a/src/tapeport/Makefile.am b/src/tapeport/Makefile.am index cd38b6d..cf10121 100644 --- a/src/tapeport/Makefile.am +++ b/src/tapeport/Makefile.am @@ -30,4 +30,6 @@ libtapeport_a_SOURCES = \ tapecart.h \ tapecart-loader.h \ tapeport.c \ - tapeport.h + tapeport.h \ + tape-rs232.c \ + tape-rs232.h diff --git a/src/tapeport/tape-rs232.c b/src/tapeport/tape-rs232.c new file mode 100644 index 0000000..680a1c4 --- /dev/null +++ b/src/tapeport/tape-rs232.c @@ -0,0 +1,270 @@ +/* + * tape-rs232.h: RS-232 interface for the PET, connected to the tape port + * + * Written by + * Gregor Riepl + * + * This file is part of VICE, the Versatile Commodore Emulator. + * See README for copyright notice. + * + * 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 "vice.h" + +#include +#include +#include + +#include "cmdline.h" +#include "machine.h" +#include "resources.h" +#include "snapshot.h" +#include "tapeport.h" +#include "log.h" +#include "charset.h" +#include "alarm.h" +#include "maincpu.h" + +#include "tape-rs232.h" + + +typedef struct { + /* true if the initialization routine has been called */ + bool initialized; + /* the line transmission rate in bit/s (includes frame bits) */ + unsigned int baudrate; + /* alarm callbacks */ + alarm_t *write_alarm; + alarm_t *read_alarm; + /* current state of the inputs/outputs */ + uint8_t write_level; + uint8_t read_level; + /* these are 10-bit shift registers (1 start bit + 8 data bits + 1 stop bit) */ + uint16_t write_register; + uint16_t read_register; + uint8_t write_shift; + uint8_t read_shift; +} tape_rs232_state_t; +static tape_rs232_state_t tape_rs232_state[TAPEPORT_MAX_PORTS] = { { false, }, }; + +/* ------------------------------------------------------------------------- */ + +/* device interface prototypes */ +static void tape_rs232_powerup(int port); +static int tape_rs232_enable(int port, int val); +static void tape_rs232_motor(int port, int flag); +static void tape_rs232_write(int port, int write_bit); +static void tape_rs232_sense(int port, int sense); +static void tape_rs232_read(int port, int val); +/* callbacks */ +static void tape_rs232_write_sample(CLOCK offset, void *data); + +static tapeport_device_t tape_rs232_device = { + "RS-232 interface", /* device name */ + TAPEPORT_DEVICE_TYPE_SERIAL, /* device is a 'serial port' type device */ + VICE_MACHINE_PET, /* device works only on the PET */ + TAPEPORT_PORT_1_MASK | TAPEPORT_PORT_2_MASK, /* device works on tape port 1 and 2 */ + tape_rs232_enable, /* device enable function */ + tape_rs232_powerup, /* device specific hard reset function */ + NULL, /* NO device shutdown function */ + tape_rs232_motor, /* device specific set motor line function (DCE CTS) */ + tape_rs232_write, /* device specific set write line function (DCE RXD) */ + tape_rs232_sense, /* device specific set sense line function (DCE RTS) */ + tape_rs232_read, /* device specific set read line function (DCE TXD) */ + NULL, /* NO device snapshot write function */ + NULL /* NO device snapshot read function */ +}; + +/* ------------------------------------------------------------------------- */ + +static int tape_rs232_enable(int port, int value) +{ + bool enable = value != 0; + + if (port >= TAPEPORT_MAX_PORTS || port < 0) { + /* port no. out of range */ + return -1; + } + tape_rs232_state_t *dev = &tape_rs232_state[port]; + if (dev->initialized == enable) { + /* already (de)initialized */ + return 0; + } + + if (enable) { + /* TODO initialize */ + log_debug("tape_rs232: enable port %d", port); + dev->read_shift = 0; + dev->write_shift = 0; + /* TODO make the baud rate customizable */ + dev->baudrate = 600; + if (dev->write_alarm != NULL) { + alarm_destroy(dev->write_alarm); + } + dev->write_alarm = alarm_new(maincpu_alarm_context, "tape_rs232_write_sample", tape_rs232_write_sample, dev); + /* TODO */ + dev->read_alarm = NULL; + } else { + log_debug("tape_rs232: disable port %d", port); + /* ensure no alarms are pending, and clear them out */ + if (dev->write_alarm != NULL) { + alarm_unset(dev->write_alarm); + dev->write_alarm = NULL; + } + if (dev->read_alarm != NULL) { + alarm_unset(dev->read_alarm); + dev->read_alarm = NULL; + } + } + + tape_rs232_state[port].initialized = enable; + return 0; +} + +int tape_rs232_init(int amount) +{ + log_debug("tape_rs232: initializing %d ports", amount); + /* clear all state data on startup */ + memset(tape_rs232_state, 0, sizeof(tape_rs232_state)); + return tapeport_device_register(TAPEPORT_DEVICE_RS232, &tape_rs232_device); +} + +/* ---------------------------------------------------------------------*/ + +static void tape_rs232_powerup(int port) +{ + if (port < 0 || port >= TAPEPORT_MAX_PORTS) { + log_error(LOG_ERR, "tape_rs232: invalid port %d", port); + return; + } + tape_rs232_state_t *dev = &tape_rs232_state[port]; + if (!dev->initialized) { + log_error(LOG_ERR, "tape_rs232: port %d not initialized", port); + return; + } + log_debug("tape_rs232: powerup port %d", port); + dev->read_shift = 0; + dev->write_shift = 0; + /* ensure no alarms are pending */ + if (dev->write_alarm != NULL) { + alarm_unset(dev->write_alarm); + } + if (dev->read_alarm != NULL) { + alarm_unset(dev->read_alarm); + } +} +CLOCK last_maincpu_clk = 0; +static void tape_rs232_write(int port, int write_bit) +{ + if (port < 0 || port >= TAPEPORT_MAX_PORTS) { + log_error(LOG_ERR, "tape_rs232: invalid port %d", port); + return; + } + tape_rs232_state_t *dev = &tape_rs232_state[port]; + if (!dev->initialized) { + log_error(LOG_ERR, "tape_rs232: port %d not initialized", port); + return; + } + /* FIXME RS-232 bit-clocking must be done on a time base, not on level change interrupts */ + /* write_bit is actually the contents of VIA_PB, and the write bit is PB3, i.e. 0x08 */ + dev->write_level = (write_bit & 0x08) != 0 ? 1 : 0; + long delta = (long) maincpu_clk - last_maincpu_clk; + last_maincpu_clk = maincpu_clk; + log_debug("tape_rs232: write port %d level %d clock %lu delta %ld", port, dev->write_level, maincpu_clk, delta); + /* write in progress? */ + if (dev->write_shift == 0) { + /* no, start a new transmission - but only if this was a high->low transition (start bit) */ + if (dev->write_level == 0) { + /* initialize the shift register */ + dev->write_shift = 10; + dev->write_register = 0; + /* arm the first baud rate timer: sample in the middle of each bit, + * so the delay must be half a baud clock cycle */ + clock_t delay = machine_get_cycles_per_second() / (dev->baudrate * 2); + //log_debug("tape_rs232: schedule start bit alarm after %ld clock cycles", delay); + alarm_set(dev->write_alarm, (CLOCK) (maincpu_clk + delay)); + } + } +} + +static void tape_rs232_write_sample(CLOCK offset, void *data) +{ + if (data == NULL) { + return; + } + tape_rs232_state_t *dev = (tape_rs232_state_t *) data; + /* prepare the next alarm, at baudrate interval */ + alarm_unset(dev->write_alarm); + clock_t delay = machine_get_cycles_per_second() / dev->baudrate; + //log_debug("tape_rs232: schedule regular bit alarm after %ld clock cycles", delay); + alarm_set(dev->write_alarm, (CLOCK) (maincpu_clk + delay)); + /* shift in the next bit, from the head */ + dev->write_register = (dev->write_register >> 1) | (dev->write_level ? 0x200 : 0x000); + /* and decrement the counter */ + --dev->write_shift; + long delta = (long) maincpu_clk - last_maincpu_clk; + last_maincpu_clk = maincpu_clk; + log_debug("tape_rs232: sample %d reg 0x%04x offset %lu clock %lu delta %ld shift %u", dev->write_level, dev->write_register, offset, maincpu_clk, delta, dev->write_shift); + /* transmission complete? */ + if (dev->write_shift == 0) { + /* yes, unarm the timer */ + alarm_unset(dev->write_alarm); + /* check if start and stop bits are valid */ + if ((dev->write_register & 0x0200) == 0x0200 && (dev->write_register & 0x0001) == 0x0000) { + /* yes, output data byte */ + uint8_t wbyte = (uint8_t) (dev->write_register >> 1); + /* utf-8 codes generated by the petcii conversion routine can be max. 4 bytes (plus terminator) */ + char udata[5]; + size_t udatalen = charset_ucs_to_utf8((uint8_t *) udata, wbyte, sizeof(udata)); + if (udatalen <= 0) { + log_debug("tape_rs232: write hex 0x%02x", wbyte); + } else { + /* ensure there is a terminator */ + udata[udatalen] = 0; + log_debug("tape_rs232: write hex 0x%02x char %s", wbyte, udata); + } + } else { + log_warning(LOG_ERR, "tape_rs232: invalid RS-232 transmission 0x%04x", dev->write_register); + } + } +} + +static void tape_rs232_read(int port, int val) +{ + if (port < 0 || port >= TAPEPORT_MAX_PORTS) { + log_error(LOG_ERR, "tape_rs232: invalid port %d", port); + return; + } + tape_rs232_state_t *dev = &tape_rs232_state[port]; + if (!dev->initialized) { + log_error(LOG_ERR, "tape_rs232: port %d not initialized", port); + return; + } + log_debug("tape_rs232: read port %d level %d", port, val); + /* TODO */ +} + +/* FIXME RTS/CTS not supported yet */ +static void tape_rs232_motor(int port, int flag) +{ + /*log_debug("tape_rs232: motor port %d level %d", port, flag);*/ +} +static void tape_rs232_sense(int port, int sense) +{ + /*log_debug("tape_rs232: sense port %d level %d", port, sense);*/ +} diff --git a/src/tapeport/tape-rs232.h b/src/tapeport/tape-rs232.h new file mode 100644 index 0000000..eac1924 --- /dev/null +++ b/src/tapeport/tape-rs232.h @@ -0,0 +1,38 @@ +/* + * tape-rs232.h: RS-232 interface for the PET, connected to the tape port + * + * Written by + * Gregor Riepl + * + * This file is part of VICE, the Versatile Commodore Emulator. + * See README for copyright notice. + * + * 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. + * + */ + +#ifndef VICE_TAPE_RS232_H +#define VICE_TAPE_RS232_H + +#include "types.h" + +/* Initialize data structures for the emulated serial ports. + * amount=number of tape devices in the system + * (max=2, only two tape interfaces are available in the PET) + */ +extern int tape_rs232_init(int amount); + +#endif