///////////////////////////////////////////////////////////////////////
// sd.c
//
// Written: Jaocb Pease jacob.pease@okstate.edu 7/22/2024
//
// Purpose: SD Card protocol functions
//
// 
//
// A component of the Wally configurable RISC-V project.
// 
// Copyright (C) 2021-23 Harvey Mudd College & Oklahoma State University
//
// SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1
//
// Licensed under the Solderpad Hardware License v 2.1 (the
// “License”); you may not use this file except in compliance with the
// License, or, at your option, the Apache License version 2.0. You
// may obtain a copy of the License at
//
// https://solderpad.org/licenses/SHL-2.1/
//
// Unless required by applicable law or agreed to in writing, any work
// distributed under the License is distributed on an “AS IS” BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
///////////////////////////////////////////////////////////////////////

#include "sd.h"
#include "spi.h"
#include "uart.h"
#include "fail.h"
#include "time.h"

// Parallel byte update CRC7-CCITT algorithm.
// The result is the CRC7 result, left shifted over by 1
// which is perfect, since we append a 1 at the end anyway
uint8_t crc7(uint8_t prev, uint8_t in) {
    // CRC polynomial 0x89
    uint8_t remainder = prev ^ in;
    remainder ^= (remainder >> 4) ^ (remainder >> 7);
    remainder = (remainder << 1) ^ (remainder << 4);
    return remainder & 0xff;
}

// Need to check this. This could be wrong as well.
uint16_t crc16(uint16_t crc, uint8_t data) {
    // CRC polynomial 0x11021
    crc = (uint8_t)(crc >> 8) | (crc << 8);
    crc ^= data;
    crc ^= (uint8_t)(crc >> 4) & 0xf;
    crc ^= crc << 12;
    crc ^= (crc & 0xff) << 5;
    return crc;
}

// sd_cmd ------------------------------------------------------------
// Sends SD card command using SPI mode.
// This function:
// * Chooses the response length based on the input command
// * Makes use of SPI's full duplex. For every byte sent,
//   a byte is received. Thus for every byte sent as part of
//   a command, a useless byte must be read from the receive
//   FIFO.
// * Takes advantage of the Sifive SPI peripheral spec's
//   watermark and interrupt features to determine when a
//   transfer is complete. This should save on cycles since
//   no arbitrary delays need to be added.

uint64_t sd_cmd(uint8_t cmd, uint32_t arg, uint8_t crc) {
  uint8_t response_len;
  uint8_t i;
  uint8_t shiftAmnt;
  uint64_t r;
  uint8_t rbyte;

  // Initialize the response with 0's.
  r = 0;

  // Choose response length based on cmd input.
  // Most commands return an R1 format response.
  switch (cmd) {
    case 8:
      response_len = R7_RESPONSE;
      break;
    case 12:
      response_len = R1B_RESPONSE;
      break;
    default:
      response_len = R1_RESPONSE;
      break;
  }

  // Make interrupt pending after response fifo receives the correct
  // response length.  Probably unecessary so let's wait and see what
  // happens.
  // write_reg(SPI_RXMARK, response_len);

  // Chip select must remain asserted during transaction
  if (cmd != SD_CMD_STOP_TRANSMISSION) {
    write_reg(SPI_CSMODE, SIFIVE_SPI_CSMODE_MODE_HOLD);
  }
  
  // Write all 7 bytes into transfer fifo
  // spi_sendbyte(0xff);
  spi_dummy();
  spi_sendbyte(0x40 | cmd);
  spi_sendbyte(arg >> 24);
  spi_sendbyte(arg >> 16);
  spi_sendbyte(arg >> 8);
  spi_sendbyte(arg);
  spi_sendbyte(crc);

  // Wait for command to send
  // The Transfer IP bit should go high when the txFIFO is empty
  // while(!(read_reg(SPI_IP) & 1)) {}
  waittx();

  // Read the dummy rxFIFO entries to move the head back to the tail
  for (i = 0; i < 7; i++) {
    spi_readbyte();
  }

  // Send "dummy signals". Since SPI is duplex,
  // useless bytes must be transferred
  /* for (i = 0; i < response_len; i++) { */
  /*   spi_sendbyte(0xFF); */
  /* } */

  /* // Wait for transfer fifo again */
  /* waittx(); */

  // Wait for actual response from SD card
  // All responses start with a 0. Output of SDCIn is high, unless
  // a message is being transferred.
  do {
    rbyte = spi_dummy();
  } while ( (rbyte & 0x80) != 0 );

  // Note about the compiler. In order to compile as sll instead of
  // sllw, the number to shift has to be a 64 bit number.
  r = ((uint64_t)rbyte) << ((response_len - 1)*8);
  
  // Read rxfifo response
  for (i = 1; i < response_len; i++) {
    rbyte = spi_dummy();
    r = r | (((uint64_t)rbyte) << ((response_len - 1 - i)*8));
  }

  if (cmd != 18) {
    write_reg(SPI_CSMODE, SIFIVE_SPI_CSMODE_MODE_AUTO);
  } else {
    spi_dummy();
  }
  return r;
} // sd_cmd

uint64_t sd_read64(uint16_t * crc) {
  uint64_t r;
  uint8_t rbyte;
  int i;

  /* for (i = 0; i < 8; i++) { */
  /*   spi_sendbyte(0xFF); */
  /* } */

  /* waittx(); */

  for (i = 0; i < 8; i++) {
    rbyte = spi_dummy();
    *crc = crc16(*crc, rbyte);
    r = r | ((uint64_t)(rbyte) << ((8 - 1 - i)*8));
  }

  return r;
}

// Utility defines for CMD0, CMD8, CMD55, and ACMD41
#define CMD0()   sd_cmd( 0, 0x00000000, 0x95) // Reset SD card into IDLE state
#define CMD8()   sd_cmd( 8, 0x000001aa, 0x87) // 
#define CMD55()  sd_cmd(55, 0x00000000, 0x65) //
#define ACMD41() sd_cmd(41, 0x40000000, 0x77) //

// init_sd: ----------------------------------------------------------
// This first initializes the SPI peripheral then initializes the SD
// card itself. We use the uart to display anything that goes wrong.
int init_sd(uint32_t freq, uint32_t sdclk){
  print_time();
  println("Initializing SPI Controller.");
  spi_init();

  uint64_t r;
  uint32_t newClockDiv;
  int n;

  print_time();
  println("Initializing SD Card in SPI mode.");
  // This is necessary. This is the card's pre-init state initialization.
  write_reg(SPI_CSMODE, SIFIVE_SPI_CSMODE_MODE_OFF);
  for (int i = 0; i < 10; i++) {
    spi_txrx(0xff);
  }
  write_reg(SPI_CSMODE, SIFIVE_SPI_CSMODE_MODE_AUTO);
  
  // CMD0 --------------------------------------------------------------
  // Reset SD Card command
  // Initializes SD card into SPI mode if CS is asserted '0'
  // We expect to get the R1 response 0x01 which means that the
  // card has been put into the idle state.
  print_time();
  print_uart("CMD0: ");
  n = 0;
  do {
    r = CMD0();
    n++;
    if (n == 1000) {
      fail();
    }
  } while ( r != 0x01 );
  println_with_r1("Success, r = 0x", r & 0xff);

  // CMD8 -------------------------------------------------------------
  //
  print_time();
  print_uart("CMD8: ");
  r = CMD8();
  if ((r & 0x000000ff0000ffff) != 0x01000001aa) {
    println_with_r7("Failed, 0x", r);
    fail();
  }
  println_with_r7("Success, 0x", r);

  // ACMD41 -----------------------------------------------------------
  print_time();
  print_uart("ACMD41: ");
  n = 0;
  do {
    CMD55();
    r = ACMD41();
    n++;
    if (n == 1000) {
      fail();
    }
  } while (r == 0x1);
  println_with_r1("Success, r = 0x", r & 0xff);

  print_time();
  println_with_dec("New clock frequency: ", (uint64_t)sdclk);
  spi_set_clock(freq, sdclk);
  
  print_time();
  println("SD card is initialized.");
}