|  | // 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); | 
|  | } |