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