blob: 38276359c7503ffd469016fbf0c4458a9be0b915 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#ifdef __linux__
#include <pty.h>
#elif __APPLE__
#include <util.h>
#endif
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "spidpi.h"
#include "verilator_sim_ctrl.h"
// Enable this define to stop tracing at cycle 4
// and resume at the first SPI packet
// #define CONTROL_TRACE
void *spidpi_create(const char *name, int mode, int loglevel) {
int i;
struct spidpi_ctx *ctx =
(struct spidpi_ctx *)calloc(1, sizeof(struct spidpi_ctx));
assert(ctx);
ctx->loglevel = loglevel;
ctx->mon = monitor_spi_init(mode);
ctx->tick = 0;
ctx->msbfirst = 1;
ctx->nmax = MAX_TRANSACTION;
ctx->nin = 0;
ctx->nout = 0;
ctx->bout = 0;
ctx->state = SP_IDLE;
/* mode is CPOL << 1 | CPHA
* cpol = 0 --> external clock matches internal
* cpha = 0 --> drive on internal falling edge, capture on rising
*/
ctx->cpol = ((mode == 0) || (mode == 2)) ? 0 : 1;
ctx->cpha = ((mode == 1) || (mode == 3)) ? 1 : 0;
/* CPOL = 1 for clock idle high */
ctx->driving = P2D_CSB | ((ctx->cpol) ? P2D_SCK : 0);
char cwd[PATH_MAX];
char *cwd_rv;
cwd_rv = getcwd(cwd, sizeof(cwd));
assert(cwd_rv != NULL);
int rv;
struct termios tty;
cfmakeraw(&tty);
rv = openpty(&ctx->host, &ctx->device, 0, &tty, 0);
assert(rv != -1);
rv = ttyname_r(ctx->device, ctx->ptyname, 64);
assert(rv == 0 && "ttyname_r failed");
int cur_flags = fcntl(ctx->host, F_GETFL, 0);
assert(cur_flags != -1 && "Unable to read current flags.");
int new_flags = fcntl(ctx->host, F_SETFL, cur_flags | O_NONBLOCK);
assert(new_flags != -1 && "Unable to set FD flags");
printf(
"\n"
"SPI: Created %s for %s. Connect to it with any terminal program, e.g.\n"
"$ screen %s\n"
"NOTE: a SPI transaction is run for every 4 characters entered.\n",
ctx->ptyname, name, ctx->ptyname);
rv = snprintf(ctx->mon_pathname, PATH_MAX, "%s/%s.log", cwd, name);
assert(rv <= PATH_MAX && rv > 0);
ctx->mon_file = fopen(ctx->mon_pathname, "w");
if (ctx->mon_file == NULL) {
fprintf(stderr, "SPI: Unable to open file at %s: %s\n", ctx->mon_pathname,
strerror(errno));
return NULL;
}
// more useful for tail -f
setlinebuf(ctx->mon_file);
printf(
"SPI: Monitor output file created at %s. Works well with tail:\n"
"$ tail -f %s\n",
ctx->mon_pathname, ctx->mon_pathname);
return (void *)ctx;
}
char spidpi_tick(void *ctx_void, const svLogicVecVal *d2p_data) {
struct spidpi_ctx *ctx = (struct spidpi_ctx *)ctx_void;
assert(ctx);
int d2p = d2p_data->aval;
// Will tick at the host clock
ctx->tick++;
#ifdef CONTROL_TRACE
if (ctx->tick == 4) {
VerilatorSimCtrl::GetInstance().TraceOff();
}
#endif
monitor_spi(ctx->mon, ctx->mon_file, ctx->loglevel, ctx->tick, ctx->driving,
d2p);
if (ctx->state == SP_IDLE) {
int n = read(ctx->host, &(ctx->buf[ctx->nin]), ctx->nmax - ctx->nin);
if (n == -1) {
if (errno != EAGAIN) {
fprintf(stderr, "Read on SPI FIFO gave %s\n", strerror(errno));
}
} else {
ctx->nin += n;
if (ctx->nin == ctx->nmax) {
ctx->nout = 0;
ctx->nin = 0;
ctx->bout = ctx->msbfirst ? 0x80 : 0x01;
ctx->bin = ctx->msbfirst ? 0x80 : 0x01;
ctx->din = 0;
ctx->state = SP_CSFALL;
#ifdef CONTROL_TRACE
VerilatorSimCtrl::GetInstance().TraceOn();
#endif
}
}
}
// SPI clock toggles every 4th tick (i.e. freq=primary_frequency/8)
if ((ctx->tick & 3) || (ctx->state == SP_IDLE)) {
return ctx->driving;
}
// Only get here on sck edges when active
int internal_sck = (ctx->tick & 4) ? 1 : 0;
int set_sck = (internal_sck ? P2D_SCK : 0);
if (ctx->cpol) {
set_sck ^= P2D_SCK;
}
if (internal_sck == ctx->cpha) {
// host driving edge (falling for mode 0)
switch (ctx->state) {
case SP_DMOVE:
// SCLK low, CSB low
ctx->driving =
set_sck | (ctx->buf[ctx->nout] & ctx->bout) ? P2D_SDI : 0;
ctx->bout = (ctx->msbfirst) ? ctx->bout >> 1 : ctx->bout << 1;
if ((ctx->bout & 0xff) == 0) {
ctx->bout = ctx->msbfirst ? 0x80 : 0x01;
ctx->nout++;
if (ctx->nout == ctx->nmax) {
ctx->state = SP_LASTBIT;
}
}
break;
case SP_LASTBIT:
ctx->state = SP_CSRISE;
// fallthrough
default:
ctx->driving = set_sck | (ctx->driving & ~P2D_SCK);
break;
}
} else {
// host other edge (rising for mode 0)
switch (ctx->state) {
// Read input data (opposite edge to sending)
// both DMOVE and LASTBIT states are data moving ones!
case SP_DMOVE:
case SP_LASTBIT:
ctx->din = ctx->din | ((d2p & D2P_SDO) ? ctx->bin : 0);
ctx->bin = (ctx->msbfirst) ? ctx->bin >> 1 : ctx->bin << 1;
if (ctx->bin == 0) {
int rv = write(ctx->host, &(ctx->din), 1);
assert(rv == 1 && "write() failed.");
ctx->bin = (ctx->msbfirst) ? 0x80 : 0x01;
ctx->din = 0;
}
ctx->driving = set_sck | (ctx->driving & ~P2D_SCK);
break;
case SP_CSFALL:
// CSB low, drive SDI to first bit
ctx->driving =
(set_sck | (ctx->buf[ctx->nout] & ctx->bout) ? P2D_SDI : 0);
ctx->state = SP_DMOVE;
break;
case SP_CSRISE:
// CSB high, clock stopped
ctx->driving = P2D_CSB;
ctx->state = SP_IDLE;
break;
case SP_FINISH:
VerilatorSimCtrl::GetInstance().RequestStop(true);
break;
default:
ctx->driving = set_sck | (ctx->driving & ~P2D_SCK);
break;
}
}
return ctx->driving;
}
void spidpi_close(void *ctx_void) {
struct spidpi_ctx *ctx = (struct spidpi_ctx *)ctx_void;
if (!ctx) {
return;
}
fclose(ctx->mon_file);
free(ctx);
}