432 lines
13 KiB
JavaScript
432 lines
13 KiB
JavaScript
|
|
const ACTION_WAIT = 0;
|
|
const ACTION_RECV = 1;
|
|
const ACTION_SEND = 2;
|
|
const ACTION_BUSY = 3;
|
|
|
|
const CTX_INIT = 0;
|
|
const CTX_CMD = 1;
|
|
const CTX_APPCMD = 2;
|
|
const CTX_REG = 3;
|
|
const CTX_READ = 4;
|
|
const CTX_READM = 5;
|
|
const CTX_WRITE = 6;
|
|
const CTX_WRITE1 = 7;
|
|
const CTX_WRITEM = 8;
|
|
const CTX_WRITEM1 = 9;
|
|
|
|
function crc7(b, j, c)
|
|
{
|
|
let crc = 0;
|
|
while(c--) {
|
|
crc = crc ^ b[j++];
|
|
for (let i = 0; i < 8; i++) {
|
|
crc = (crc & 0x80) ? ((crc << 1) ^ 0x89) : (crc << 1);
|
|
}
|
|
}
|
|
return crc & 0xff | 0x01;
|
|
}
|
|
|
|
function crc16(b, j, c)
|
|
{
|
|
let crc = 0;
|
|
while (c--) {
|
|
crc = crc ^ (b[j++] << 8);
|
|
for (let i = 0; i < 8; i++) {
|
|
crc = (crc & 0x8000) ? ((crc << 1) ^ 0x1021) : (crc << 1);
|
|
}
|
|
}
|
|
return crc & 0xffff;
|
|
}
|
|
|
|
|
|
export class Spi {
|
|
/** Create a new Spi
|
|
* @param {Gigatron} cpu
|
|
* @param {Object} options
|
|
*/
|
|
constructor(cpu, id) {
|
|
this.cpu = cpu
|
|
/* spi stuff */
|
|
this.cs = (id >=0 && id < 4) ? (4 << id) : 0;
|
|
this.mask = 0;
|
|
this.miso = 0;
|
|
this.mosi = 0;
|
|
/* sd stuff */
|
|
this.vhd = 0;
|
|
this.vhdlen = 0;
|
|
this.buffer = new Uint8Array(520);
|
|
this.idle = 1;
|
|
this.action = 0;
|
|
this.context = 0;
|
|
this.count = 0;
|
|
this.len = 0;
|
|
this.offset = 0;
|
|
this.serial = 0;
|
|
}
|
|
|
|
/** Advance spi simulation by one tick */
|
|
tick() {
|
|
let b = this.cpu.prevctrl;
|
|
if (b >= 0) {
|
|
let a = this.cpu.ctrl;
|
|
let selected = this.cs && !(a & this.cs);
|
|
if (selected && (b & this.cs)) {
|
|
this.mask = 0x80;
|
|
this.miso = this.spiselect();
|
|
this.cpu.miso = (this.miso & this.mask) ? 0xf : 0;
|
|
}
|
|
if (selected && ((a ^ b) & 1)) {
|
|
if (a & 1) {
|
|
/* clock rising (latch) */
|
|
let mask = this.mask;
|
|
let mosi = this.mosi;
|
|
this.mosi = (a & 0x8000) ? (mosi | mask) : (mosi & ~mask)
|
|
} else {
|
|
/* clock falling (shift) */
|
|
this.mask >>= 1;
|
|
if (! this.mask) {
|
|
this.mask = 0x80;
|
|
this.miso = this.spibyte(this.mosi)
|
|
}
|
|
this.cpu.miso = (this.miso & this.mask) ? 0xf : 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
loadvhdurl(url) {
|
|
let req = new XMLHttpRequest();
|
|
req.open('GET', url);
|
|
req.responseType = 'arraybuffer';
|
|
/* req.setRequestHeader('accept-encoding','gzip'); */
|
|
req.onload = (event) => {
|
|
if (req.status == 200) {
|
|
let vhd = new Uint8Array(req.response);
|
|
if (vhd.length > 0) {
|
|
this.vhd = vhd;
|
|
this.vhdlen = vhd.length;
|
|
this.serial += 1;
|
|
}
|
|
}
|
|
};
|
|
req.onerror = (event) => {
|
|
console.log("error while reading vhd from", url);
|
|
};
|
|
req.send();
|
|
}
|
|
|
|
loadvhdfile(file) {
|
|
let reader = new FileReader();
|
|
reader.onload = (event) => {
|
|
let vhd = new Uint8Array(reader.result);
|
|
if (vhd.length > 0) {
|
|
this.vhd = vhd;
|
|
this.vhdlen = vhd.length;
|
|
this.serial += 1;
|
|
}
|
|
};
|
|
reader.onerror = (event) => {
|
|
console.log("error while reading vhd from", file);
|
|
};
|
|
reader.readAsArrayBuffer(file);
|
|
}
|
|
|
|
/** enable/disable SD card emulation */
|
|
stop() {
|
|
this.vhdlen = 0;
|
|
}
|
|
start() {
|
|
if (this.vhd)
|
|
this.vhdlen = this.vhd.length;
|
|
}
|
|
|
|
/** Set state (action,context) */
|
|
set_wait_state(ctx) {
|
|
this.context = ctx;
|
|
this.action = ACTION_WAIT;
|
|
}
|
|
set_recv_state(ctx, n) {
|
|
this.context = ctx;
|
|
this.action = ACTION_RECV;
|
|
this.len = n;
|
|
this.count = 0;
|
|
}
|
|
set_recv_state1(ctx, n, b) {
|
|
this.context = ctx;
|
|
this.action = ACTION_RECV;
|
|
this.len = n;
|
|
this.buffer[0] = b;
|
|
this.count = 1;
|
|
}
|
|
set_send_state(ctx, n) {
|
|
this.context = ctx;
|
|
this.action = ACTION_SEND;
|
|
this.len = n;
|
|
this.count = 0;
|
|
}
|
|
set_send_r1_state(ctx, r1) {
|
|
this.buffer[0] = r1;
|
|
this.set_send_state(ctx, 1);
|
|
}
|
|
set_busy_state(ctx, n) {
|
|
this.context = ctx;
|
|
this.action = ACTION_BUSY;
|
|
this.len = n;
|
|
this.count = 0;
|
|
}
|
|
|
|
/** Which byte is returned by the device when selected */
|
|
spiselect() {
|
|
if (this.action == ACTION_BUSY) {
|
|
return 0x00;
|
|
}
|
|
if (this.context != CTX_INIT && this.context != CTX_APPCMD) {
|
|
this.context = CTX_CMD;
|
|
}
|
|
this.set_wait_state(this.context);
|
|
return 0xff;
|
|
}
|
|
|
|
/** Which byte is returned to the master when receiving mosi */
|
|
spibyte(mosi) {
|
|
let c = this.context;
|
|
let a = this.action;
|
|
switch(a) {
|
|
case ACTION_WAIT:
|
|
if (mosi == 0xff || this.vhdlen <= 0) { return 0xff; }
|
|
break;
|
|
case ACTION_RECV:
|
|
this.buffer[this.count] = mosi;
|
|
if (++this.count < this.len) { return 0xff; }
|
|
break;
|
|
case ACTION_SEND:
|
|
if (c == CTX_READM && mosi == 64 + 12) { break; } // CMD12
|
|
if (this.count < this.len) { return this.buffer[this.count++]; }
|
|
break;
|
|
case ACTION_BUSY:
|
|
if (this.count++ < this.len) { return 0; }
|
|
break;
|
|
}
|
|
switch(c) {
|
|
case CTX_INIT:
|
|
case CTX_CMD:
|
|
case CTX_APPCMD:
|
|
if (a == ACTION_WAIT) {
|
|
this.set_recv_state1(c, 6, mosi);
|
|
} else if (a == ACTION_SEND || a == ACTION_BUSY) {
|
|
this.set_wait_state(c);
|
|
} else {
|
|
this.sdcommand(c);
|
|
}
|
|
return 0xff;
|
|
case CTX_REG:
|
|
this.buffer[0] = 0xfe;
|
|
this.buffer[16] = crc7(this.buffer, 1, 15);
|
|
let crc = crc16(this.buffer, 1, 16);
|
|
this.buffer[17] = crc >> 8;
|
|
this.buffer[18] = crc;
|
|
this.set_send_state(CTX_CMD, 16 + 3);
|
|
return 0xff;
|
|
case CTX_READ:
|
|
if (! this.read_data()) {
|
|
this.set_send_r1_state(CTX_CMD, 9);
|
|
} else {
|
|
this.set_send_state(CTX_CMD, 512+3);
|
|
}
|
|
return 0xff;
|
|
case CTX_READM:
|
|
if (mosi == 64 + 12) {
|
|
this.set_recv_state1(CTX_CMD, 6, mosi);
|
|
} else if (! this.read_data()) {
|
|
this.set_send_r1_state(CTX_CMD, 9);
|
|
} else {
|
|
this.set_send_state(CTX_READM, 512+3);
|
|
}
|
|
this.offset += 512;
|
|
return 0xff;
|
|
case CTX_WRITE:
|
|
this.set_wait_state(CTX_WRITE1);
|
|
return 0xff;
|
|
case CTX_WRITE1:
|
|
if (a == ACTION_WAIT) {
|
|
this.set_recv_state1(CTX_WRITE1, 512+3, mosi);
|
|
} else {
|
|
this.set_busy_state(CTX_CMD, 4);
|
|
return (this.buffer[0] == 0xfe && this.write_data()) ? 0x5 : 0xd;
|
|
}
|
|
return 0xff;
|
|
case CTX_WRITEM:
|
|
this.set_wait_state(CTX_WRITEM1);
|
|
return 0xff;
|
|
case CTX_WRITEM1:
|
|
if (a == ACTION_WAIT && mosi == 0xfd) {
|
|
this.set_busy_state(CTX_CMD, 4); // stop tran
|
|
} else if (a == ACTION_WAIT) {
|
|
this.set_recv_state1(CTX_WRITEM1, 512+3, mosi);
|
|
} else if (this.buffer[0] == 0xfc && this.write_data()) {
|
|
this.offset += 512;
|
|
this.set_busy_state(ACTION_WRITEM, 4);
|
|
return 0x5;
|
|
} else {
|
|
this.set_busy_state(CTX_CMD, 4);
|
|
return 0xd;
|
|
}
|
|
return 0xff;
|
|
}
|
|
this.set_send_r1_state(CTX_CMD, 4);
|
|
return 0xff;
|
|
}
|
|
|
|
sdcommand(ctx) {
|
|
let buffer = this.buffer;
|
|
let cmd = buffer[0] & 0x3f;
|
|
if (ctx == CTX_INIT
|
|
&& !(buffer[0]==0x40 && buffer[1]==0 && buffer[2]==0
|
|
&& buffer[3]==0 && buffer[4]==0 && buffer[5]==0x95) ) {
|
|
this.set_send_r1_state(CTX_INIT, 5);
|
|
return;
|
|
}
|
|
if (buffer[0] != cmd + 64) {
|
|
this.set_send_r1_state(CTX_CMD, 5);
|
|
return;
|
|
}
|
|
if (ctx == CTX_APPCMD) {
|
|
cmd += 128;
|
|
}
|
|
if (this.idle && cmd != 0 && cmd != 1 && cmd != 8
|
|
&& cmd != 128+41 && cmd != 55 && cmd != 58) {
|
|
cmd = 0xff;
|
|
}
|
|
switch(cmd) {
|
|
case 128+41: // ACMD41: APP_SEND_OP_COND
|
|
if (this.vhdlen == 0) {
|
|
this.set_send_r1_state(CTX_CMD, 4 + this.idle)
|
|
} else {
|
|
this.idle = 0;
|
|
this.set_send_r1_state(CTX_CMD, 0)
|
|
}
|
|
break;
|
|
case 128+23: // ACMD22: SET_WR_BLOCK_ERASE_COUNT
|
|
// ignored but not illegal
|
|
this.set_send_r1_state(CTX_CMD, 0);
|
|
break;
|
|
case 0: // CMD0: GO_IDLE_STATE
|
|
this.idle = 1;
|
|
this.set_send_r1_state(CTX_CMD, this.idle);
|
|
break;
|
|
case 1: // CMD1: SEND_OP_COND
|
|
this.idle = 0;
|
|
this.set_send_r1_state(CTX_CMD, 0);
|
|
break;
|
|
case 8: // CMD8: SEND_IF_COND
|
|
buffer[0] = this.idle;
|
|
if (this.vhdlen == 0) {
|
|
this.set_send_r1_state(CTX_CMD, 4 + idle);
|
|
} else {
|
|
this.set_send_state(CTX_CMD, 5);
|
|
}
|
|
break;
|
|
case 9: { // CMD9: SEND_CSD
|
|
let size = this.vhdlen / (512 * 1024);
|
|
buffer.set([0, 0x40, 0xe, 0, 0x32, 0x5b, 0x59, 0, 0,
|
|
0, 0, 0x7f, 0x80, 0xa, 0x40, 0x40, 0xf1], 0);
|
|
buffer[10] = size & 0xff;
|
|
buffer[9] = (size & 0xff00) >> 8;
|
|
buffer[8] = (size & 0xcf0000) >> 16;
|
|
this.set_send_state(CTX_REG, 1); }
|
|
break;
|
|
case 10: // CMD10: SEND_CID
|
|
buffer.set([0x00, 0xbb, 83, 68, 48, 48, 48, 48, 48,
|
|
0x11, 0, 0, 0, 0, 0, 1, 0xf1, 88, 88], 0);
|
|
buffer[11] = this.serial;
|
|
buffer[12] = this.serial >> 8;
|
|
this.set_send_state(CTX_REG, 1);
|
|
break;
|
|
case 12: // CMD12: STOP_TRANSMISSION
|
|
this.set_busy_state(CTX_CMD, 3);
|
|
break;
|
|
case 16: { // CMD16: SET_BLOCK_LENGTH
|
|
let bl = (buffer[1]<<24)|(buffer[2]<<16)|(buffer[3]<<8)|(buffer[4]);
|
|
if (bl != 512) {
|
|
this.set_send_r1_state(CTX_CMD, 64);
|
|
} else {
|
|
set_send_r1_state(CTX_CMD, 0);
|
|
} }
|
|
break;
|
|
case 17: // CMD17: READ_SINGLE_BLOCK
|
|
|
|
this.offset = (buffer[1]<<24)|(buffer[2]<<16)|(buffer[3]<<8)|(buffer[4]);
|
|
this.offset *= 512;
|
|
if (this.offset > this.vhdlen - 512) {
|
|
this.set_send_r1_state(CTX_CMD, 64);
|
|
} else {
|
|
this.set_send_r1_state(CTX_READ, 0);
|
|
}
|
|
break;
|
|
case 18: // CMD18: READ_MULTIPLE_BLOCK
|
|
this.offset = (buffer[1]<<24)|(buffer[2]<<16)|(buffer[3]<<8)|(buffer[4]);
|
|
this.offset *= 512;
|
|
if (this.offset > this.vhdlen - 512) {
|
|
this.set_send_r1_state(CTX_CMD, 64);
|
|
} else {
|
|
this.set_send_r1_state(CTX_READM, 0);
|
|
}
|
|
break;
|
|
case 24: // CMD24: WRITE_SINGLE_BLOCK
|
|
this.offset = (buffer[1]<<24)|(buffer[2]<<16)|(buffer[3]<<8)|(buffer[4]);
|
|
this.offset *= 512;
|
|
if (this.offset > this.vhdlen - 512) {
|
|
this.set_send_r1_state(CTX_CMD, 64);
|
|
} else {
|
|
this.set_send_r1_state(CTX_WRITE, 0);
|
|
}
|
|
break;
|
|
case 25: // CMD18: WRITE_MULTIPLE_BLOCK
|
|
this.offset = (buffer[1]<<24)|(buffer[2]<<16)|(buffer[3]<<8)|(buffer[4]);
|
|
this.offset *= 512;
|
|
if (this.offset > this.vhdlen - 512) {
|
|
this.set_send_r1_state(CTX_CMD, 64);
|
|
} else {
|
|
this.set_send_r1_state(CTX_WRITEM, 0);
|
|
}
|
|
break;
|
|
case 55: // CMD55: APP_CMD
|
|
this.set_send_r1_state(CTX_APPCMD, this.idle);
|
|
break;
|
|
case 58: // CMD58: READ_OCR
|
|
buffer.set([0, 0x40, 0xff, 0x80, 0], 0);
|
|
this.set_send_state(CTX_CMD, 5);
|
|
break;
|
|
default:
|
|
this.set_send_r1_state(CTX_CMD, 4 + this.idle);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
read_data() {
|
|
let offset = this.offset;
|
|
let buffer = this.buffer;
|
|
let vhd = this.vhd;
|
|
buffer[0] = 0xfe;
|
|
for (let i=0; i<512; i++)
|
|
buffer[1 + i] = vhd[offset + i];
|
|
let crc = 0; /* crc16(buffer, 1, 512) */
|
|
buffer[513] = crc >> 8;
|
|
buffer[514] = crc;
|
|
return 1;
|
|
}
|
|
|
|
write_data() {
|
|
let offset = this.offset;
|
|
let buffer = this.buffer;
|
|
let vhd = this.vhd;
|
|
buffer[0] = 0xfe;
|
|
for (let i=0; i<512; i++)
|
|
vhd[offset + i] = buffer[1 + i];
|
|
return 1;
|
|
}
|
|
|
|
}
|