mirror of
https://github.com/LIV2/amiga-par-to-spi-adapter.git
synced 2025-12-06 06:32:46 +00:00
432 lines
10 KiB
C
432 lines
10 KiB
C
/*
|
|
* Written by Niklas Ekström in July 2021.
|
|
*
|
|
* 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/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 <devices/newstyle.h>
|
|
#include <proto/exec.h>
|
|
#include <proto/alib.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)
|
|
|
|
// How much of struct NSDeviceQueryResult we use/need. It could be extended
|
|
// and we don't want that to change the behaviour of the code.
|
|
#define NSD_QUERY_RESULT_LENGTH_REQUIRED 16
|
|
|
|
struct ExecBase *SysBase;
|
|
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;
|
|
|
|
static struct Interrupt *remove_int;
|
|
static struct IOStdReq *change_int;
|
|
|
|
char device_name[] = "spisd.device";
|
|
char id_string[] = "spisd.device 2.0 (19-Jul-2021)";
|
|
|
|
static uint32_t device_get_geometry(struct IOStdReq *ior)
|
|
{
|
|
struct DriveGeometry *geom = (struct DriveGeometry*)ior->io_Data;
|
|
const sd_card_info_t *ci = sd_get_card_info();
|
|
|
|
if (ci->type == sdCardType_None)
|
|
return TDERR_DiskChanged;
|
|
|
|
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 = DG_DIRECT_ACCESS;
|
|
geom->dg_Flags = DGF_REMOVABLE;
|
|
return 0;
|
|
}
|
|
|
|
static void handle_changed()
|
|
{
|
|
// 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);
|
|
|
|
int res = spi_get_card_present();
|
|
|
|
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 uint32_t offset_to_sd_sectors(uint32_t high_offset, uint32_t low_offset)
|
|
{
|
|
return (high_offset << (32 - SD_SECTOR_SHIFT)) | (low_offset >> SD_SECTOR_SHIFT);
|
|
}
|
|
|
|
static void process_request(struct IOStdReq *ior)
|
|
{
|
|
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;
|
|
|
|
case TD_FORMAT:
|
|
case CMD_WRITE:
|
|
ior->io_Actual = 0;
|
|
case TD_FORMAT64:
|
|
case TD_WRITE64:
|
|
case NSCMD_TD_FORMAT64:
|
|
case NSCMD_TD_WRITE64:
|
|
if (sd_write((uint8_t *)ior->io_Data, offset_to_sd_sectors(ior->io_Actual, ior->io_Offset), ior->io_Length >> SD_SECTOR_SHIFT) == 0)
|
|
ior->io_Actual = ior->io_Length;
|
|
else
|
|
ior->io_Error = TDERR_NotSpecified;
|
|
break;
|
|
|
|
case CMD_READ:
|
|
ior->io_Actual = 0;
|
|
case TD_READ64:
|
|
case NSCMD_TD_READ64:
|
|
if (sd_read((uint8_t *)ior->io_Data, offset_to_sd_sectors(ior->io_Actual, ior->io_Offset), ior->io_Length >> SD_SECTOR_SHIFT) == 0)
|
|
ior->io_Actual = ior->io_Length;
|
|
else
|
|
ior->io_Error = TDERR_NotSpecified;
|
|
break;
|
|
}
|
|
}
|
|
|
|
ReplyMsg(&ior->io_Message);
|
|
}
|
|
|
|
static void task_run()
|
|
{
|
|
if (card_present && sd_open() == 0)
|
|
card_opened = TRUE;
|
|
|
|
while (1)
|
|
{
|
|
ULONG sigs = Wait(SIGF_CARD_CHANGE | SIGF_OP_REQUEST);
|
|
|
|
if (sigs & SIGF_CARD_CHANGE)
|
|
handle_changed();
|
|
|
|
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 void change_isr()
|
|
{
|
|
Signal(task, SIGF_CARD_CHANGE);
|
|
}
|
|
|
|
static const UWORD supported_commands[] =
|
|
{
|
|
CMD_RESET,
|
|
CMD_READ,
|
|
CMD_WRITE,
|
|
CMD_UPDATE,
|
|
CMD_CLEAR,
|
|
TD_MOTOR,
|
|
TD_FORMAT,
|
|
TD_REMOVE,
|
|
TD_CHANGENUM,
|
|
TD_CHANGESTATE,
|
|
TD_PROTSTATUS,
|
|
TD_GETDRIVETYPE,
|
|
TD_ADDCHANGEINT,
|
|
TD_REMCHANGEINT,
|
|
TD_GETGEOMETRY,
|
|
TD_READ64,
|
|
TD_WRITE64,
|
|
TD_FORMAT64,
|
|
NSCMD_DEVICEQUERY,
|
|
NSCMD_TD_READ64,
|
|
NSCMD_TD_WRITE64,
|
|
NSCMD_TD_FORMAT64,
|
|
0
|
|
};
|
|
|
|
static void begin_io(__reg("a6") struct Library *dev, __reg("a1") struct IOStdReq *ior)
|
|
{
|
|
if (!ior)
|
|
return;
|
|
|
|
ior->io_Error = 0;
|
|
|
|
switch (ior->io_Command)
|
|
{
|
|
case CMD_RESET:
|
|
case CMD_CLEAR:
|
|
case CMD_UPDATE:
|
|
case TD_MOTOR:
|
|
case TD_PROTSTATUS:
|
|
ior->io_Actual = 0;
|
|
break;
|
|
|
|
case TD_CHANGESTATE:
|
|
ior->io_Actual = card_present ? 0 : 1;
|
|
break;
|
|
|
|
case TD_CHANGENUM:
|
|
ior->io_Actual = card_change_num;
|
|
break;
|
|
|
|
case TD_GETDRIVETYPE:
|
|
ior->io_Actual = 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 NSCMD_DEVICEQUERY:
|
|
if (ior->io_Length >= NSD_QUERY_RESULT_LENGTH_REQUIRED)
|
|
{
|
|
struct NSDeviceQueryResult *result = ior->io_Data;
|
|
result->nsdqr_DevQueryFormat = 0;
|
|
result->nsdqr_SizeAvailable = NSD_QUERY_RESULT_LENGTH_REQUIRED;
|
|
result->nsdqr_DeviceType = NSDEVTYPE_TRACKDISK;
|
|
result->nsdqr_DeviceSubType = 0;
|
|
result->nsdqr_SupportedCommands = supported_commands;
|
|
ior->io_Actual = NSD_QUERY_RESULT_LENGTH_REQUIRED;
|
|
}
|
|
else {
|
|
ior->io_Error = IOERR_BADLENGTH;
|
|
}
|
|
break;
|
|
|
|
case TD_GETGEOMETRY:
|
|
case TD_FORMAT:
|
|
case CMD_WRITE:
|
|
case CMD_READ:
|
|
case TD_READ64:
|
|
case TD_WRITE64:
|
|
case NSCMD_TD_READ64:
|
|
case NSCMD_TD_WRITE64:
|
|
case NSCMD_TD_FORMAT64:
|
|
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)
|
|
{
|
|
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 auto_init_tables[] =
|
|
{
|
|
sizeof(struct Library),
|
|
(ULONG)device_vectors,
|
|
0,
|
|
(ULONG)init_device,
|
|
};
|