Add support for eject/insert sd card

This commit is contained in:
Niklas Ekström 2021-07-19 18:51:46 +02:00
parent 285569663b
commit e766d41e37
8 changed files with 807 additions and 438 deletions

View File

@ -1,119 +1,147 @@
/*
* Written in the end of April 2020 by Niklas Ekström.
* Updated in July 2021 by Niklas Ekström to handle Card Present signal.
*/
#include <avr/interrupt.h>
#include <avr/io.h>
// Amiga pins:
// D0 = A0 = PC0
// D1 = A1 = PC1
// D2 = A2 = PC2
// D3 = A3 = PC3
// D4 = A4 = PC4
// D5 = A5 = PC5
// D6 = D6 = PD6
// D7 = D7 = PD7
// BUSY = D4 = PD4
// POUT = D5 = PD5
// Par P-name Arduino AVR SD Name Description
// 2 D0 A0 PC0 Parallel port data lines
// 3 D1 A1 PC1
// 4 D2 A2 PC2
// 5 D3 A3 PC3
// 6 D4 A4 PC4
// 7 D5 A5 PC5
// 8 D6 D6 PD6
// 9 D7 D7 PD7
// 10 ACK D9 PB1 IRQ Interrupt request to Amiga
// 11 BUSY D8 PB0 ACT Indicate command running
// 12 POUT D4 PD4 CLK Clock to advance command
// 13 SEL D2 PD2 REQ Amiga wants to execute command
// D3 PD3 CD CP Card Present
// D10 PB2 SS
// D11 PB3 MOSI
// D12 PB4 MISO
// D13 PB5 SCK
// SPI pins in port B.
#define SCK_BIT 5
#define MISO_BIT 4
#define MOSI_BIT 3
#define SS_BIT 2
// Pins in port B.
#define SCK_BIT 5 // Output.
#define MISO_BIT 4 // Input.
#define MOSI_BIT 3 // Output.
#define SS_BIT_n 2 // Output, active low.
#define IRQ_BIT_n 1 // Output, active low, open collector.
#define ACT_BIT_n 0 // Output, active low.
// These bits are in port D.
#define IDLE_BIT 4
#define CLOCK_BIT 5
// Pins in port D.
#define CLK_BIT 4 // Input.
#define CP_BIT_n 3 // Input, active low, internal pull-up enabled.
#define REQ_BIT_n 2 // Input, active low, internal pull-up enabled.
int main()
void start_command()
{
DDRB = (1 << SCK_BIT) | (1 << MOSI_BIT) | (1 << SS_BIT);
PORTB = (1 << SS_BIT);
// SPI enabled, master, fosc/64 = 250 kHz
SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR1) | (1 << SPR0);
SPSR |= (1 << SPI2X);
DDRD = (1 << IDLE_BIT);
DDRC = 0;
PORTD = 0;
PORTC = 0;
uint8_t v;
uint8_t w;
uint8_t dval;
uint8_t cval;
uint8_t next_port_d;
uint8_t next_port_c;
uint16_t byte_count;
main_loop:
if (PIND & (1 << CLOCK_BIT))
dval = PIND;
cval = PINC;
if (!(dval & 0x80)) // READ1 or WRITE1
{
while (PIND & (1 << CLOCK_BIT))
;
}
else
{
while (!(PIND & (1 << CLOCK_BIT)))
;
}
byte_count = cval;
if (!(PIND & 0x80))
{
if (PIND & 0x40)
goto do_read1;
else
goto do_write1;
}
PORTB &= ~(1 << ACT_BIT_n);
v = PIND;
w = PINC;
if (!(v & 0x40)) // READ2 or WRITE2
{
byte_count = (w & 0x1f) << 8;
if (v & (1 << CLOCK_BIT))
{
while (PIND & (1 << CLOCK_BIT))
;
}
else
{
while (!(PIND & (1 << CLOCK_BIT)))
;
}
v = PIND;
byte_count |= (v & 0xc0) | PINC;
if (w & 0x20)
if (dval & 0x40)
goto do_read;
else
goto do_write;
}
else if ((w & 0x3e) == 0) // SPEED
else if (!(dval & 0x40)) // READ2 or WRITE2
{
if (w & 1) // Fast
SPCR = (1 << SPE) | (1 << MSTR);
else // Slow
SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR1) | (1 << SPR0);
byte_count = cval << 7;
PORTB &= ~(1 << ACT_BIT_n);
if (dval & (1 << CLK_BIT))
{
while (PIND & (1 << CLK_BIT))
;
}
else
{
while (!(PIND & (1 << CLK_BIT)))
;
}
dval = PIND;
cval = PINC;
byte_count |= (dval & 0x40) | cval;
if (dval & 0x80)
goto do_read;
else
goto do_write;
}
else
{
uint8_t cmd = (cval & 0x3e) >> 1;
if (cmd == 0) // SPI_SELECT
{
if (cval & 1) // Select
PORTB &= ~(1 << SS_BIT_n);
else // Deselect
PORTB |= (1 << SS_BIT_n);
goto main_loop;
PORTB &= ~(1 << ACT_BIT_n);
}
else if (cmd == 1) // CARD_PRESENT
{
DDRB &= ~(1 << IRQ_BIT_n);
PORTB &= ~(1 << ACT_BIT_n);
do_read1:
byte_count = PINC;
if (dval & (1 << CLK_BIT))
{
while (PIND & (1 << CLK_BIT))
;
}
else
{
while (!(PIND & (1 << CLK_BIT)))
;
}
DDRD = 0xc0;
DDRC = 0x3f;
if (!(PIND & (1 << CP_BIT_n)))
PORTC = 1;
else
PORTC = 0;
}
else if (cmd == 2) // SPEED
{
if (cval & 1) // Fast
SPCR = (1 << SPE) | (1 << MSTR);
else // Slow
SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR1) | (1 << SPR0);
PORTB &= ~(1 << ACT_BIT_n);
}
while (1)
;
}
do_read:
SPDR = 0xff;
v = PIND;
PORTD = (dval & 0xc0) | (1 << CP_BIT_n) | (1 << REQ_BIT_n);
DDRD = 0xc0;
PORTD = (v & 0xc0) | (1 << IDLE_BIT);
DDRD = 0xc0 | (1 << IDLE_BIT);
PORTC = byte_count & 0x3f;
PORTC = cval;
DDRC = 0x3f;
read_loop:
@ -121,23 +149,23 @@ read_loop:
;
next_port_c = SPDR;
next_port_d = (next_port_c & 0xc0) | (1 << IDLE_BIT);
next_port_d = (next_port_c & 0xc0) | (1 << CP_BIT_n) | (1 << REQ_BIT_n);
if (v & (1 << CLOCK_BIT))
if (dval & (1 << CLK_BIT))
{
while (PIND & (1 << CLOCK_BIT))
while (PIND & (1 << CLK_BIT))
;
}
else
{
while (!(PIND & (1 << CLOCK_BIT)))
while (!(PIND & (1 << CLK_BIT)))
;
}
PORTD = next_port_d;
PORTC = next_port_c;
v = PIND;
dval = PIND;
if (byte_count)
{
@ -146,46 +174,24 @@ read_loop:
goto read_loop;
}
if (v & (1 << CLOCK_BIT))
{
while (PIND & (1 << CLOCK_BIT))
;
}
else
{
while (!(PIND & (1 << CLOCK_BIT)))
;
}
DDRD = (1 << IDLE_BIT);
DDRC = 0;
PORTD = 0;
PORTC = 0;
goto main_loop;
do_write1:
byte_count = PINC;
v = PIND;
while (1)
;
do_write:
PORTD = (1 << IDLE_BIT);
write_loop:
if (v & (1 << CLOCK_BIT))
if (dval & (1 << CLK_BIT))
{
while (PIND & (1 << CLOCK_BIT))
while (PIND & (1 << CLK_BIT))
;
}
else
{
while (!(PIND & (1 << CLOCK_BIT)))
while (!(PIND & (1 << CLK_BIT)))
;
}
v = PIND;
SPDR = (v & 0xc0) | PINC;
dval = PIND;
SPDR = (dval & 0xc0) | PINC;
while (!(SPSR & (1 << SPIF)))
;
@ -198,9 +204,81 @@ write_loop:
goto write_loop;
}
PORTD = 0;
goto main_loop;
return 0;
while (1)
;
}
void busy_wait()
{
while (1)
;
}
// Interrupt handler for REQ signal changes (INT0).
ISR(INT0_vect, ISR_NAKED)
{
void (*next_fn)();
if (PIND & (1 << REQ_BIT_n))
{
PORTB |= (1 << ACT_BIT_n);
DDRD = 0;
PORTD = (1 << CP_BIT_n) | (1 << REQ_BIT_n);
DDRC = 0;
PORTC = 0;
EIMSK |= (1 << 1);
next_fn = &busy_wait;
}
else
{
EIMSK &= ~(1 << 1);
next_fn = &start_command;
}
uint16_t fn_int = (uint16_t)next_fn;
uint16_t sp = ((SPH << 8) | SPL) + 1;
uint8_t *p = (uint8_t *)sp;
*p++ = fn_int >> 8;
*p++ = fn_int & 0xff;
reti();
}
// Interrupt handler for CP signal changes (INT1).
ISR(INT1_vect, ISR_NAKED)
{
DDRB |= (1 << IRQ_BIT_n);
reti();
}
void main()
{
DDRB = (1 << SCK_BIT) | (1 << MOSI_BIT) | (1 << SS_BIT_n) | (1 << ACT_BIT_n);
PORTB = (1 << SS_BIT_n) | (1 << ACT_BIT_n);
// SPI enabled, master, fosc/64 = 250 kHz
SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR1) | (1 << SPR0);
SPSR |= (1 << SPI2X);
DDRD = 0;
PORTD = (1 << CP_BIT_n) | (1 << REQ_BIT_n);
DDRC = 0;
PORTC = 0;
// Enable interrupts
EICRA = (1 << 2) | (1 << 0);
EIFR = (1 << 1) | (1 << 0);
EIMSK = (1 << 1) | (1 << 0);
sei();
while (1)
;
}

View File

@ -1 +1 @@
vc romtag.asm device.c sd.c timer.c ../../spi-lib/spi.c ../../spi-lib/spi_low.asm -I../../spi-lib -nostdlib -o spisd.device
vc romtag.asm device.c sd.c timer.c ../../spi-lib/spi.c ../../spi-lib/spi_low.asm -I../../spi-lib -Os -nostdlib -lamiga -o spisd.device

View File

@ -1,253 +1,383 @@
/*
* SPI SD device driver for K1208/Amiga 1200
* Written by Niklas Ekström in July 2021.
*
* Copyright (C) 2018 Mike Stirling
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
/*
* Parts of this file were changed in order for it to compile with VBCC.
* The procedures device_get_geometry() and begin_io() are intact.
* Previous version of this file (device.c) used code written by
* Mike Sterling in 2018 for the SPI SD device driver for K1208/Amiga 1200.
*
* In order to handle sd card removal this file was rewritten almost entirely.
*/
#include <exec/types.h>
#include <exec/execbase.h>
#include <exec/devices.h>
#include <exec/errors.h>
#include <exec/execbase.h>
#include <exec/interrupts.h>
#include <exec/ports.h>
#include <exec/tasks.h>
#include <libraries/dos.h>
#include <devices/timer.h>
#include <devices/trackdisk.h>
#include <proto/exec.h>
#include "sd.h"
#include "spi.h"
#define TASK_STACK_SIZE 2048
#define TASK_PRIORITY 10
#define DEBOUNCE_TIMEOUT_US 100000
#define SIGB_CARD_CHANGE 30
#define SIGB_OP_REQUEST 29
#define SIGB_TIMER 28
#define SIGF_CARD_CHANGE (1 << SIGB_CARD_CHANGE)
#define SIGF_OP_REQUEST (1 << SIGB_OP_REQUEST)
#define SIGF_OP_TIMER (1 << SIGB_TIMER)
#ifndef TD_GETGEOMETRY
// Needed to compile with AmigaOS 1.3 headers.
#define TD_GETGEOMETRY (CMD_NONSTD+13)
struct DriveGeometry
{
ULONG dg_SectorSize;
ULONG dg_TotalSectors;
ULONG dg_Cylinders;
ULONG dg_CylSectors;
ULONG dg_Heads;
ULONG dg_TrackSectors;
ULONG dg_BufMemType;
UBYTE dg_DeviceType;
UBYTE dg_Flags;
UWORD dg_Reserved;
ULONG dg_SectorSize;
ULONG dg_TotalSectors;
ULONG dg_Cylinders;
ULONG dg_CylSectors;
ULONG dg_Heads;
ULONG dg_TrackSectors;
ULONG dg_BufMemType;
UBYTE dg_DeviceType;
UBYTE dg_Flags;
UWORD dg_Reserved;
};
#endif
char device_name[] = "spisd.device";
char id_string[] = "spisd 0.3 (3 May 2020)";
struct ExecBase *SysBase;
BPTR saved_seg_list;
static BPTR saved_seg_list;
static struct timerequest tr;
static struct Task *task;
static struct MsgPort mp;
static struct MsgPort timer_mp;
static volatile BOOL card_present;
static volatile BOOL card_opened;
static volatile ULONG card_change_num;
BOOL is_open;
static struct Interrupt *remove_int;
static struct IOStdReq *change_int;
static struct Library *init_device(__reg("a6") struct ExecBase *sys_base, __reg("a0") BPTR seg_list, __reg("d0") struct Library *dev)
char device_name[] = "spisd.device";
char id_string[] = "spisd 2.0 (19 July 2021)";
static uint32_t device_get_geometry(struct IOStdReq *ior)
{
SysBase = *(struct ExecBase **)4;
saved_seg_list = seg_list;
struct DriveGeometry *geom = (struct DriveGeometry*)ior->io_Data;
const sd_card_info_t *ci = sd_get_card_info();
dev->lib_Node.ln_Type = NT_DEVICE;
dev->lib_Node.ln_Name = device_name;
dev->lib_Flags = LIBF_SUMUSED | LIBF_CHANGED;
dev->lib_Version = 0;
dev->lib_Revision = 2;
dev->lib_IdString = (APTR)id_string;
if (ci->type == sdCardType_None)
return TDERR_DiskChanged;
is_open = FALSE;
return dev;
geom->dg_SectorSize = 1 << ci->block_size;
geom->dg_TotalSectors = ci->total_sectors; //ci->capacity >> ci->block_size;
geom->dg_Cylinders = geom->dg_TotalSectors;
geom->dg_CylSectors = 1;
geom->dg_Heads = 1;
geom->dg_TrackSectors = 1;
geom->dg_BufMemType = MEMF_PUBLIC;
geom->dg_DeviceType = 0; //DG_DIRECT_ACCESS;
geom->dg_Flags = 1; //DGF_REMOVABLE;
return 0;
}
static BPTR expunge(__reg("a6") struct Library *dev)
static void handle_changed()
{
if (dev->lib_OpenCnt != 0)
{
dev->lib_Flags |= LIBF_DELEXP;
return 0;
}
// Wait to debounce the card detect switch.
tr.tr_node.io_Command = TR_ADDREQUEST;
tr.tr_time.tv_secs = 0;
tr.tr_time.tv_micro = DEBOUNCE_TIMEOUT_US;
DoIO((struct IORequest *)&tr);
spi_shutdown();
int res = spi_get_card_present();
BPTR seg_list = saved_seg_list;
Remove(&dev->lib_Node);
FreeMem((char *)dev - dev->lib_NegSize, dev->lib_NegSize + dev->lib_PosSize);
return seg_list;
if (res == 1 && sd_open() == 0)
card_opened = TRUE;
else
card_opened = FALSE;
Forbid();
card_present = res == 1;
card_change_num++;
Permit();
if (remove_int)
Cause(remove_int);
if (change_int)
Cause((struct Interrupt *)change_int->io_Data);
}
static void open(__reg("a6") struct Library *dev, __reg("a1") struct IORequest *ior, __reg("d0") ULONG unitnum, __reg("d1") ULONG flags)
static void process_request(struct IOStdReq *ior)
{
ior->io_Error = IOERR_OPENFAIL;
ior->io_Message.mn_Node.ln_Type = NT_REPLYMSG;
if (!card_present)
ior->io_Error = TDERR_DiskChanged;
else if (!card_opened)
ior->io_Error = TDERR_NotSpecified;
else
{
switch (ior->io_Command)
{
case TD_GETGEOMETRY:
ior->io_Error = device_get_geometry(ior);
break;
if (unitnum != 0)
return;
case TD_FORMAT:
case CMD_WRITE:
if (sd_write((uint8_t *)ior->io_Data, ior->io_Offset >> SD_SECTOR_SHIFT, ior->io_Length >> SD_SECTOR_SHIFT) == 0)
ior->io_Actual = ior->io_Length;
else
ior->io_Error = TDERR_NotSpecified;
break;
if (!is_open)
{
spi_initialize();
if (sd_open() != 0)
return;
is_open = TRUE;
}
case CMD_READ:
if (sd_read((uint8_t *)ior->io_Data, ior->io_Offset >> SD_SECTOR_SHIFT, ior->io_Length >> SD_SECTOR_SHIFT) == 0)
ior->io_Actual = ior->io_Length;
else
ior->io_Error = TDERR_NotSpecified;
break;
}
}
dev->lib_OpenCnt++;
ior->io_Error = 0;
ReplyMsg(&ior->io_Message);
}
static BPTR close(__reg("a6") struct Library *dev, __reg("a1") struct IORequest *ior)
static void task_run()
{
ior->io_Device = NULL;
ior->io_Unit = NULL;
if (card_present && sd_open() == 0)
card_opened = TRUE;
dev->lib_OpenCnt--;
while (1)
{
ULONG sigs = Wait(SIGF_CARD_CHANGE | SIGF_OP_REQUEST);
if (dev->lib_OpenCnt == 0 && (dev->lib_Flags & LIBF_DELEXP))
return expunge(dev);
if (sigs & SIGF_CARD_CHANGE)
handle_changed();
return 0;
if (sigs & SIGF_OP_REQUEST)
{
BOOL first = TRUE;
struct IOStdReq *ior;
while ((ior = (struct IOStdReq *)GetMsg(&mp)))
{
if (!first && (SetSignal(0, SIGF_CARD_CHANGE) & SIGF_CARD_CHANGE))
handle_changed();
process_request(ior);
first = FALSE;
}
}
}
}
static uint32_t device_get_geometry(struct IOStdReq *iostd)
static void change_isr()
{
struct DriveGeometry *geom = (struct DriveGeometry*)iostd->io_Data;
const sd_card_info_t *ci = sd_get_card_info();
if (ci->type != sdCardType_None) {
geom->dg_SectorSize = 1 << ci->block_size;
geom->dg_TotalSectors = ci->total_sectors; //ci->capacity >> ci->block_size;
geom->dg_Cylinders = geom->dg_TotalSectors;
geom->dg_CylSectors = 1;
geom->dg_Heads = 1;
geom->dg_TrackSectors = 1;
geom->dg_BufMemType = MEMF_PUBLIC;
geom->dg_DeviceType = 0; //DG_DIRECT_ACCESS;
geom->dg_Flags = 1; //DGF_REMOVABLE;
return 0;
} else {
return TDERR_DiskChanged;
}
Signal(task, SIGF_CARD_CHANGE);
}
static void begin_io(__reg("a6") struct Library *dev, __reg("a1") struct IORequest *ioreq)
static void begin_io(__reg("a6") struct Library *dev, __reg("a1") struct IOStdReq *ior)
{
struct IOStdReq *iostd = (struct IOStdReq*)ioreq;
if (!ior)
return;
if (ioreq == NULL) {
return;
}
ior->io_Error = 0;
ior->io_Actual = 0;
iostd->io_Error = 0;
switch (ior->io_Command)
{
case CMD_RESET:
case CMD_CLEAR:
case CMD_UPDATE:
case TD_MOTOR:
case TD_PROTSTATUS:
break;
switch (iostd->io_Command) {
case CMD_RESET:
case CMD_CLEAR:
case CMD_UPDATE:
case TD_MOTOR:
case TD_REMOVE:
/* NULL commands */
iostd->io_Actual = 0;
break;
case TD_PROTSTATUS:
/* Should return a non-zero value if the card is write protected */
iostd->io_Actual = 0;
break;
case TD_CHANGESTATE:
/* Should return a non-zero value if the card is invalid or not inserted */
iostd->io_Actual = 0;
break;
case TD_CHANGENUM:
/* This should increment each time a disk is inserted */
iostd->io_Actual = 1;
break;
case TD_GETDRIVETYPE:
iostd->io_Actual = 0; //DG_DIRECT_ACCESS;
break;
case TD_GETGEOMETRY:
iostd->io_Actual = 0;
iostd->io_Error = device_get_geometry(iostd);
break;
#if 0
case HD_SCSI_CMD:
break;
case NSCMD_DEVICEQUERY:
break;
case NSCMD_TD_READ64:
break;
case NSCMD_WRITE64:
break;
#endif
case TD_CHANGESTATE:
ior->io_Actual = card_present ? 0 : 1;
break;
case TD_FORMAT:
case CMD_WRITE:
/* FIXME: Should be deferred to task but this did not work reliably - investigate */
if (sd_write((uint8_t *)iostd->io_Data, iostd->io_Offset >> SD_SECTOR_SHIFT, iostd->io_Length >> SD_SECTOR_SHIFT) == 0) {
iostd->io_Actual = iostd->io_Length;
iostd->io_Error = 0;
} else {
iostd->io_Actual = 0;
iostd->io_Error = TDERR_NotSpecified;
}
break;
case CMD_READ:
if (sd_read((uint8_t *)iostd->io_Data, iostd->io_Offset >> SD_SECTOR_SHIFT, iostd->io_Length >> SD_SECTOR_SHIFT) == 0) {
iostd->io_Actual = iostd->io_Length;
iostd->io_Error = 0;
} else {
iostd->io_Actual = 0;
iostd->io_Error = TDERR_NotSpecified;
}
break;
default:
iostd->io_Error = IOERR_NOCMD;
}
case TD_CHANGENUM:
ior->io_Actual = card_change_num;
break;
if (iostd && !(iostd->io_Flags & IOF_QUICK)) {
/* Reply to message now unless it was deferred to the task or is IOF_QUICK */
ReplyMsg(&iostd->io_Message);
}
case TD_GETDRIVETYPE:
ior->io_Actual = 0; //DG_DIRECT_ACCESS;
break;
case TD_REMOVE:
remove_int = (struct Interrupt *)ior->io_Data;
break;
case TD_ADDCHANGEINT:
if (change_int)
ior->io_Error = IOERR_ABORTED;
else
{
change_int = ior;
ior->io_Flags &= ~IOF_QUICK;
ior = NULL;
}
break;
case TD_REMCHANGEINT:
if (change_int == ior)
change_int = NULL;
break;
case TD_GETGEOMETRY:
case TD_FORMAT:
case CMD_WRITE:
case CMD_READ:
PutMsg(&mp, (struct Message *)&ior->io_Message);
ior->io_Flags &= ~IOF_QUICK;
ior = NULL;
break;
default:
ior->io_Error = IOERR_NOCMD;
}
if (ior && !(ior->io_Flags & IOF_QUICK))
ReplyMsg(&ior->io_Message);
}
static ULONG abort_io(__reg("a6") struct Library *dev, __reg("a1") struct IORequest *ior)
{
// There is no asynchronous processing of requests so this is always a no-op.
return IOERR_NOCMD;
return IOERR_NOCMD;
}
static struct Library *init_device(__reg("a6") struct ExecBase *sys_base, __reg("a0") BPTR seg_list, __reg("d0") struct Library *dev)
{
SysBase = *(struct ExecBase **)4;
saved_seg_list = seg_list;
dev->lib_Node.ln_Type = NT_DEVICE;
dev->lib_Node.ln_Name = device_name;
dev->lib_Flags = LIBF_SUMUSED | LIBF_CHANGED;
dev->lib_Version = 2;
dev->lib_Revision = 0;
dev->lib_IdString = (APTR)id_string;
Forbid();
tr.tr_node.io_Message.mn_Node.ln_Type = NT_REPLYMSG;
tr.tr_node.io_Message.mn_ReplyPort = &timer_mp;
tr.tr_node.io_Message.mn_Length = sizeof(tr);
if (OpenDevice(TIMERNAME, UNIT_VBLANK, (struct IORequest *)&tr, 0))
goto fail1;
task = CreateTask(device_name, TASK_PRIORITY, (char *)&task_run, TASK_STACK_SIZE);
if (!task)
goto fail2;
int res = spi_initialize(&change_isr);
if (res < 0)
goto fail3;
card_present = res == 1;
mp.mp_Node.ln_Type = NT_MSGPORT;
mp.mp_Flags = PA_SIGNAL;
mp.mp_SigBit = SIGB_OP_REQUEST;
mp.mp_SigTask = task;
NewList(&mp.mp_MsgList);
timer_mp.mp_Node.ln_Type = NT_MSGPORT;
timer_mp.mp_Flags = PA_SIGNAL;
timer_mp.mp_SigBit = SIGB_TIMER;
timer_mp.mp_SigTask = task;
NewList(&timer_mp.mp_MsgList);
Permit();
return dev;
fail3:
DeleteTask(task);
fail2:
CloseDevice((struct IORequest *)&tr);
fail1:
Permit();
FreeMem((char *)dev - dev->lib_NegSize, dev->lib_NegSize + dev->lib_PosSize);
return NULL;
}
static BPTR expunge(__reg("a6") struct Library *dev)
{
if (dev->lib_OpenCnt != 0)
{
dev->lib_Flags |= LIBF_DELEXP;
return 0;
}
// This could be improved on.
// There is a risk that the task has an outstanding debounce timer,
// and deleting the task at that point will probably cause a crash.
spi_shutdown();
DeleteTask(task);
CloseDevice((struct IORequest *)&tr);
BPTR seg_list = saved_seg_list;
Remove(&dev->lib_Node);
FreeMem((char *)dev - dev->lib_NegSize, dev->lib_NegSize + dev->lib_PosSize);
return seg_list;
}
static void open(__reg("a6") struct Library *dev, __reg("a1") struct IORequest *ior, __reg("d0") ULONG unitnum, __reg("d1") ULONG flags)
{
ior->io_Error = IOERR_OPENFAIL;
ior->io_Message.mn_Node.ln_Type = NT_REPLYMSG;
if (unitnum != 0)
return;
dev->lib_OpenCnt++;
ior->io_Error = 0;
}
static BPTR close(__reg("a6") struct Library *dev, __reg("a1") struct IORequest *ior)
{
ior->io_Device = NULL;
ior->io_Unit = NULL;
dev->lib_OpenCnt--;
if (dev->lib_OpenCnt == 0 && (dev->lib_Flags & LIBF_DELEXP))
return expunge(dev);
return 0;
}
static ULONG device_vectors[] =
{
(ULONG)open,
(ULONG)close,
(ULONG)expunge,
0,
(ULONG)begin_io,
(ULONG)abort_io,
-1,
(ULONG)open,
(ULONG)close,
(ULONG)expunge,
0,
(ULONG)begin_io,
(ULONG)abort_io,
-1,
};
ULONG auto_init_tables[] =
{
sizeof(struct Library),
(ULONG)device_vectors,
0,
(ULONG)init_device,
sizeof(struct Library),
(ULONG)device_vectors,
0,
(ULONG)init_device,
};

16
spi-lib/cia_protos.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef CIA_PROTOS_H
#define CIA_PROTOS_H
#include <exec/types.h>
#include <exec/interrupts.h>
#include <exec/libraries.h>
struct Interrupt *AddICRVector(__reg("a6") struct Library *resource, __reg("d0") LONG iCRBit, __reg("a1") struct Interrupt *interrupt)="\tjsr\t-6(a6)";
void RemICRVector(__reg("a6") struct Library *resource, __reg("d0") LONG iCRBit, __reg("a1") struct Interrupt *interrupt)="\tjsr\t-12(a6)";
WORD AbleICR(__reg("a6") struct Library *resource, __reg("d0") LONG mask)="\tjsr\t-18(a6)";
WORD SetICR(__reg("a6") struct Library *resource, __reg("d0") LONG mask)="\tjsr\t-24(a6)";
#endif /* CIA_PROTOS_H */

10
spi-lib/misc_protos.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef MISC_PROTOS_H
#define MISC_PROTOS_H
#include <exec/types.h>
UBYTE *AllocMiscResource(__reg("a6") struct Library *resource, __reg("d0") ULONG unitNum, __reg("a1") const char *name)="\tjsr\t-6(a6)";
void FreeMiscResource(__reg("a6") struct Library *resource, __reg("d0") ULONG unitNum)="\tjsr\t-12(a6)";
#endif /* MISC_PROTOS_H */

View File

@ -1,22 +1,30 @@
/*
* Written in the end of April 2020 by Niklas Ekström.
* Updated in July 2021 by Niklas Ekström to handle Card Present signal.
*/
#include <exec/types.h>
#include <exec/interrupts.h>
#include <exec/libraries.h>
#include <hardware/cia.h>
#include <resources/cia.h>
#include <resources/misc.h>
#include <proto/exec.h>
#include "cia_protos.h"
#include "misc_protos.h"
#include "spi.h"
#define CIAB_PRTRSEL 2
#define CIAB_PRTRPOUT 1
#define CIAB_PRTRBUSY 0
#define REQ_BIT CIAB_PRTRSEL
#define CLK_BIT CIAB_PRTRPOUT
#define ACT_BIT CIAB_PRTRBUSY
#define CS_BIT CIAB_PRTRSEL
#define CLOCK_BIT CIAB_PRTRPOUT
#define IDLE_BIT CIAB_PRTRBUSY
#define REQ_MASK (1 << REQ_BIT)
#define CLK_MASK (1 << CLK_BIT)
#define ACT_MASK (1 << ACT_BIT)
#define CS_MASK (1 << CS_BIT)
#define CLOCK_MASK (1 << CLOCK_BIT)
#define IDLE_MASK (1 << IDLE_BIT)
extern void spi_read_fast(__reg("a0") UBYTE *buf, __reg("d0") ULONG size);
extern void spi_write_fast(__reg("a0") const UBYTE *buf, __reg("d0") ULONG size);
static volatile UBYTE *cia_a_prb = (volatile UBYTE *)0xbfe101;
static volatile UBYTE *cia_a_ddrb = (volatile UBYTE *)0xbfe301;
@ -26,59 +34,93 @@ static volatile UBYTE *cia_b_ddra = (volatile UBYTE *)0xbfd200;
static long current_speed = SPI_SPEED_SLOW;
void spi_initialize()
static const char spi_lib_name[] = "spi-lib";
static struct Library *miscbase;
static struct Library *ciaabase;
static struct Interrupt flag_interrupt;
static int wait_until_active()
{
// Should allocate the parallel port in a system friendly way?
*cia_b_pra = (*cia_b_pra & ~IDLE_MASK) | (CS_MASK | CLOCK_MASK);
*cia_b_ddra = (*cia_b_ddra & ~IDLE_MASK) | (CS_MASK | CLOCK_MASK);
*cia_a_prb = 0xff;
*cia_a_ddrb = 0;
}
void spi_shutdown()
{
*cia_b_ddra &= 0xf8;
*cia_a_ddrb = 0;
}
static void wait_until_idle()
{
// This will block forever if the adapter is not present.
// TODO: Should eventually timeout and give up.
int count = 32;
UBYTE ctrl = *cia_b_pra;
while (ctrl & IDLE_MASK)
while (count > 0 && (ctrl & ACT_MASK))
{
ctrl ^= CLOCK_MASK;
*cia_b_pra = ctrl;
count--;
ctrl = *cia_b_pra;
}
return count;
}
void spi_select()
{
*cia_a_prb = 0xc1;
UBYTE prev = *cia_b_pra;
*cia_b_pra = prev & ~REQ_MASK;
wait_until_active();
*cia_b_pra = prev;
}
void spi_deselect()
{
*cia_a_prb = 0xc0;
UBYTE prev = *cia_b_pra;
*cia_b_pra = prev & ~REQ_MASK;
wait_until_active();
*cia_b_pra = prev;
}
int spi_get_card_present()
{
*cia_a_prb = 0xc2;
UBYTE ctrl = *cia_b_pra;
ctrl &= ~REQ_MASK;
*cia_b_pra = ctrl;
if (!wait_until_active())
{
ctrl |= REQ_MASK;
*cia_b_pra = ctrl;
return -1;
}
*cia_a_ddrb = 0x00;
ctrl ^= CLK_MASK;
*cia_b_pra = ctrl;
int present = *cia_a_prb & 1;
ctrl |= REQ_MASK;
*cia_b_pra = ctrl;
*cia_a_ddrb = 0xff;
return present;
}
void spi_set_speed(long speed)
{
wait_until_idle();
*cia_a_prb = speed == SPI_SPEED_FAST ? 0xc5 : 0xc4;
*cia_a_ddrb = 0xff;
*cia_a_prb = speed == SPI_SPEED_FAST ? 0xc1 : 0xc0;
*cia_b_pra ^= CLOCK_MASK;
*cia_a_ddrb = 0;
UBYTE prev = *cia_b_pra;
*cia_b_pra = prev & ~REQ_MASK;
wait_until_active();
*cia_b_pra = prev;
current_speed = speed;
}
void spi_select(void)
{
*cia_b_pra &= ~CS_MASK;
}
void spi_deselect(void)
{
*cia_b_pra |= CS_MASK;
}
// A slow SPI transfer takes 32 us (8 bits times 4us (250kHz)).
// An E-cycle is 1.4 us.
static void wait_40_us()
@ -90,29 +132,29 @@ static void wait_40_us()
static void spi_write_slow(__reg("a0") const UBYTE *buf, __reg("d0") ULONG size)
{
wait_until_idle();
*cia_a_ddrb = 0xff;
UBYTE ctrl = *cia_b_pra;
if (size <= 64) // WRITE1: 00xxxxxx
{
*cia_a_prb = (size - 1) & 0x3f;
ctrl ^= CLOCK_MASK;
ctrl &= ~REQ_MASK;
*cia_b_pra = ctrl;
wait_until_active();
}
else // WRITE2: 100xxxxx xxxxxxxx
else // WRITE2: 10xxxxxx 0xxxxxxx
{
*cia_a_prb = 0x80 | (((size - 1) >> 8) & 0x1f);
*cia_a_prb = 0x80 | (((size - 1) >> 7) & 0x3f);
ctrl ^= CLOCK_MASK;
ctrl &= ~REQ_MASK;
*cia_b_pra = ctrl;
*cia_a_prb = (size - 1) & 0xff;
wait_until_active();
ctrl ^= CLOCK_MASK;
*cia_a_prb = (size - 1) & 0x7f;
ctrl ^= CLK_MASK;
*cia_b_pra = ctrl;
}
@ -120,40 +162,41 @@ static void spi_write_slow(__reg("a0") const UBYTE *buf, __reg("d0") ULONG size)
{
*cia_a_prb = *buf++;
ctrl ^= CLOCK_MASK;
ctrl ^= CLK_MASK;
*cia_b_pra = ctrl;
wait_40_us();
}
*cia_a_ddrb = 0;
ctrl |= REQ_MASK;
*cia_b_pra = ctrl;
}
static void spi_read_slow(__reg("a0") UBYTE *buf, __reg("d0") ULONG size)
{
wait_until_idle();
*cia_a_ddrb = 0xff;
UBYTE ctrl = *cia_b_pra;
if (size <= 64) // READ1: 01xxxxxx
{
*cia_a_prb = 0x40 | ((size - 1) & 0x3f);
ctrl ^= CLOCK_MASK;
ctrl &= ~REQ_MASK;
*cia_b_pra = ctrl;
wait_until_active();
}
else // READ2: 101xxxxx xxxxxxxx
else // READ2: 10xxxxxx 1xxxxxxx
{
*cia_a_prb = 0xa0 | (((size - 1) >> 8) & 0x1f);
*cia_a_prb = 0x80 | (((size - 1) >> 7) & 0x3f);
ctrl ^= CLOCK_MASK;
ctrl &= ~REQ_MASK;
*cia_b_pra = ctrl;
*cia_a_prb = (size - 1) & 0xff;
wait_until_active();
ctrl ^= CLOCK_MASK;
*cia_a_prb = 0x80 | ((size - 1) & 0x7f);
ctrl ^= CLK_MASK;
*cia_b_pra = ctrl;
}
@ -163,18 +206,17 @@ static void spi_read_slow(__reg("a0") UBYTE *buf, __reg("d0") ULONG size)
{
wait_40_us();
ctrl ^= CLOCK_MASK;
ctrl ^= CLK_MASK;
*cia_b_pra = ctrl;
*buf++ = *cia_a_prb;
}
ctrl ^= CLOCK_MASK;
ctrl |= REQ_MASK;
*cia_b_pra = ctrl;
}
extern void spi_read_fast(__reg("a0") UBYTE *buf, __reg("d0") ULONG size);
extern void spi_write_fast(__reg("a0") const UBYTE *buf, __reg("d0") ULONG size);
*cia_a_ddrb = 0xff;
}
void spi_read(__reg("a0") UBYTE *buf, __reg("d0") ULONG size)
{
@ -191,3 +233,95 @@ void spi_write(__reg("a0") const UBYTE *buf, __reg("d0") ULONG size)
else
spi_write_slow(buf, size);
}
int spi_initialize(void (*change_isr)())
{
int success = 0;
miscbase = (struct Library *)OpenResource(MISCNAME);
if (!miscbase)
{
success = -1;
goto fail_out1;
}
ciaabase = (struct Library *)OpenResource(CIAANAME);
if (!ciaabase)
{
success = -2;
goto fail_out1;
}
if (AllocMiscResource(miscbase, MR_PARALLELPORT, spi_lib_name))
{
success = -3;
goto fail_out1;
}
if (AllocMiscResource(miscbase, MR_PARALLELBITS, spi_lib_name))
{
success = -4;
goto fail_out2;
}
flag_interrupt.is_Node.ln_Name = (char *)spi_lib_name;
flag_interrupt.is_Node.ln_Type = NT_INTERRUPT;
flag_interrupt.is_Code = change_isr;
Disable();
if (AddICRVector(ciaabase, CIAICRB_FLG, &flag_interrupt))
{
Enable();
success = -5;
goto fail_out3;
}
AbleICR(ciaabase, CIAICRF_FLG);
SetICR(ciaabase, CIAICRF_FLG);
Enable();
*cia_b_pra = (*cia_b_pra & ~ACT_MASK) | (REQ_MASK | CLK_MASK);
*cia_b_ddra = (*cia_b_ddra & ~ACT_MASK) | (REQ_MASK | CLK_MASK);
*cia_a_prb = 0xff;
*cia_a_ddrb = 0xff;
int card_present = spi_get_card_present();
if (card_present < 0)
{
success = -6;
goto fail_out4;
}
AbleICR(ciaabase, CIAICRF_SETCLR | CIAICRF_FLG);
return card_present;
fail_out4:
*cia_b_ddra &= ~(ACT_MASK | REQ_MASK | CLK_MASK);
*cia_a_ddrb = 0;
RemICRVector(ciaabase, CIAICRB_FLG, &flag_interrupt);
fail_out3:
FreeMiscResource(miscbase, MR_PARALLELBITS);
fail_out2:
FreeMiscResource(miscbase, MR_PARALLELPORT);
fail_out1:
return success;
}
void spi_shutdown()
{
AbleICR(ciaabase, CIAICRF_FLG);
*cia_b_ddra &= ~(ACT_MASK | REQ_MASK | CLK_MASK);
*cia_a_ddrb = 0;
RemICRVector(ciaabase, CIAICRB_FLG, &flag_interrupt);
FreeMiscResource(miscbase, MR_PARALLELBITS);
FreeMiscResource(miscbase, MR_PARALLELPORT);
}

View File

@ -1,5 +1,6 @@
/*
* Written in the end of April 2020 by Niklas Ekström.
* Updated in July 2021 by Niklas Ekström to handle Card Present signal.
*/
#ifndef SPI_H_
#define SPI_H_
@ -7,7 +8,8 @@
#define SPI_SPEED_SLOW 0
#define SPI_SPEED_FAST 1
void spi_initialize();
int spi_initialize(void (*change_isr)());
int spi_get_card_present();
void spi_shutdown();
void spi_set_speed(long speed);
void spi_select();

View File

@ -1,4 +1,5 @@
; Written in the end of April 2020 by Niklas Ekström.
; Updated in July 2021 by Niklas Ekström to handle Card Present signal.
XDEF _spi_read_fast
XDEF _spi_write_fast
@ -16,12 +17,9 @@ CIAPRB equ $0100
CIADDRA equ $0200
CIADDRB equ $0300
Disable equ -120
Enable equ -126
CS_BIT equ CIAB_PRTRSEL
CLOCK_BIT equ CIAB_PRTRPOUT
IDLE_BIT equ CIAB_PRTRBUSY
REQ_BIT equ CIAB_PRTRSEL
CLK_BIT equ CIAB_PRTRPOUT
ACT_BIT equ CIAB_PRTRBUSY
; a0 = unsigned char *buf
; d0 = unsigned int size
@ -32,53 +30,53 @@ _spi_write_fast:
bne.b .not_zero
rts
.not_zero:
movem.l d2/a5-a6,-(a7)
move.l 4.w,a6
jsr Disable(a6)
movem.l d2/a5,-(a7)
lea.l CIAA_BASE+CIAPRB,a1 ; Data
lea.l CIAB_BASE+CIAPRA,a5 ; Control pins
.idle_wait: move.b (a5),d2
btst #IDLE_BIT,d2
beq.b .is_idle
bchg #CLOCK_BIT,d2
move.b d2,(a5)
bra.b .idle_wait
.is_idle: move.b #$ff,$200(a1) ; Start driving data pins
move.b (a5),d2
subq #1,d0 ; d0 = size - 1
cmp #63,d0
ble.b .one_byte_cmd
; WRITE2 = 100xxxxx xxxxxxxx
; WRITE2 = 10xxxxxx 0xxxxxxx
move d0,d1
lsr #8,d1
lsr #7,d1
or.b #$80,d1
move.b d1,(a1)
bchg #CLOCK_BIT,d2
bclr #REQ_BIT,d2
move.b d2,(a5)
move.b d0,(a1)
bchg #CLOCK_BIT,d2
.act_wait2: move.b (a5),d2
btst #ACT_BIT,d2
bne.b .act_wait2
move.b d0,d1
and.b #$7f,d1
move.b d1,(a1)
bchg #CLK_BIT,d2
move.b d2,(a5)
bra.b .cmd_sent
.one_byte_cmd: ; WRITE1 = 00xxxxxx
move.b d0,(a1)
bchg #CLOCK_BIT,d2
bclr #REQ_BIT,d2
move.b d2,(a5)
.act_wait1: move.b (a5),d2
btst #ACT_BIT,d2
bne.b .act_wait1
.cmd_sent: addq #1,d0 ; d0 = size
btst #0,d0
beq.b .even
move.b (a0)+,(a1)
bchg #CLOCK_BIT,d2
bchg #CLK_BIT,d2
move.b d2,(a5)
.even: lsr #1,d0
@ -86,7 +84,7 @@ _spi_write_fast:
subq #1,d0
move.b d2,d1
bchg #CLOCK_BIT,d1
bchg #CLK_BIT,d1
.loop: move.b (a0)+,(a1)
move.b d1,(a5)
@ -94,10 +92,10 @@ _spi_write_fast:
move.b d2,(a5)
dbra d0,.loop
.done: move.b #0,$200(a1) ; Stop driving data pins
.done: bset #REQ_BIT,d2
move.b d2,(a5)
jsr Enable(a6)
movem.l (a7)+,d2/a5-a6
movem.l (a7)+,d2/a5
rts
; a0 = unsigned char *buf
@ -109,38 +107,34 @@ _spi_read_fast:
bne.b .not_zero
rts
.not_zero:
movem.l d2/a5-a6,-(a7)
move.l 4.w,a6
jsr Disable(a6)
movem.l d2/a5,-(a7)
lea.l CIAA_BASE+CIAPRB,a1 ; Data
lea.l CIAB_BASE+CIAPRA,a5 ; Control pins
.idle_wait: move.b (a5),d2
btst #IDLE_BIT,d2
beq.b .is_idle
bchg #CLOCK_BIT,d2
move.b d2,(a5)
bra.b .idle_wait
.is_idle: move.b #$ff,$200(a1) ; Start driving data pins
move.b (a5),d2
subq #1,d0 ; d0 = size - 1
cmp #63,d0
ble.b .one_byte_cmd
; READ2 = 101xxxxx xxxxxxxx
; READ2 = 10xxxxxx 1xxxxxxx
move d0,d1
lsr #8,d1
or.b #$a0,d1
lsr #7,d1
or.b #$80,d1
move.b d1,(a1)
bchg #CLOCK_BIT,d2
bclr #REQ_BIT,d2
move.b d2,(a5)
move.b d0,(a1)
bchg #CLOCK_BIT,d2
.act_wait2: move.b (a5),d2
btst #ACT_BIT,d2
bne.b .act_wait2
move.b d0,d1
or.b #$80,d1
move.b d1,(a1)
bchg #CLK_BIT,d2
move.b d2,(a5)
bra.b .cmd_sent
@ -148,9 +142,13 @@ _spi_read_fast:
move.b d0,d1
or.b #$40,d1
move.b d1,(a1)
bchg #CLOCK_BIT,d2
bclr #REQ_BIT,d2
move.b d2,(a5)
.act_wait1: move.b (a5),d2
btst #ACT_BIT,d2
bne.b .act_wait1
.cmd_sent: move.b #0,$200(a1) ; Stop driving data pins
addq #1,d0 ; d0 = size
@ -158,7 +156,7 @@ _spi_read_fast:
btst #0,d0
beq.b .even
bchg #CLOCK_BIT,d2
bchg #CLK_BIT,d2
move.b d2,(a5)
move.b (a1),(a0)+
@ -167,7 +165,7 @@ _spi_read_fast:
subq #1,d0
move.b d2,d1
bchg #CLOCK_BIT,d1
bchg #CLK_BIT,d1
.loop: move.b d1,(a5)
move.b (a1),(a0)+
@ -175,9 +173,10 @@ _spi_read_fast:
move.b (a1),(a0)+
dbra d0,.loop
.done: bchg #CLOCK_BIT,d2
.done: bset #REQ_BIT,d2
move.b d2,(a5)
jsr Enable(a6)
movem.l (a7)+,d2/a5-a6
move.b #$ff,$200(a1) ; Start driving data pins
movem.l (a7)+,d2/a5
rts