//Serial host bridge example sketch
//modified to work with USB Adaptor hardware (PCB 07111231):
//PIO-USB host on GP15/GP16=D+/D-
//LED on GP14
//Pico board only (or W)
//Processor must be 120MHz or 240MHz, TinyUSB Stack
//usbh_helper.h file merged into this one
//will pass through data on UART (eg keyboard-serial adaptor)

/*********************************************************************
 Adafruit invests time and resources providing this open source code,
 please support Adafruit and open-source hardware by purchasing
 products from Adafruit!

 MIT license, check LICENSE for more information
 Copyright (c) 2019 Ha Thach for Adafruit Industries
 All text above, and the splash screen below must be included in
 any redistribution
*********************************************************************/

#include "pio_usb.h"
#define PIN_USB_HOST_DP 15
#define STATUS_LED 14
#define PICO_ONBOARD_LED (25)

#include "Adafruit_TinyUSB.h"
Adafruit_USBH_Host USBHost;
// CDC Host object
Adafruit_USBH_CDC SerialHost;

#define CDC_BAUD (115200)
uint32_t currentBaudrate=CDC_BAUD;  //needed if attached to downstream hardware UART, can be set

//hardware for comms with display terminal
//serial port depends on proper UART pins
#define UART Serial1
#define UART_TX (0)
#define UART_RX (1)
#define UART_FIFO (128)
#define UART_BAUD (115200)

//hardware UART for comms with keyboard host
#define UART_KBD Serial2
#define UART_TX_KBD (4)
#define UART_RX_KBD (5)
#define UART_FIFO_KBD (128)
#define UART_BAUD_KBD (115200)

//keyboard interfaces
#include "keys.h"

//misc housekeeping
bool cdcMounted=0;
unsigned long tk=0;
#define FLICKER_TIME (100)

//for NV storage
#include <EEPROM.h>
//use flash block size
#define EEPROM_SIZE_FROM_FLASH 4096
//avoid location 0
#define EEPROM_DATA_START 1


static void rp2040_configure_pio_usb(void) {
  //while ( !Serial ) delay(10);   // wait for native usb
  //Serial.println("Core1 setup to run TinyUSB host with pio-usb");
  // Check for CPU frequency, must be multiple of 120Mhz for bit-banging USB
  uint32_t cpu_hz = clock_get_hz(clk_sys);
  if (cpu_hz != 120000000UL && cpu_hz != 240000000UL) {
    while (!Serial) {
      delay(10);   // wait for native usb
    }
    //Serial.printf("Error: CPU Clock = %lu, PIO USB require CPU clock must be multiple of 120 Mhz\r\n", cpu_hz);
    //Serial.printf("Change your CPU Clock to either 120 or 240 Mhz in Menu->CPU Speed \r\n");
    while (1) {
      delay(1);
    }
  }
  pio_usb_configuration_t pio_cfg = PIO_USB_DEFAULT_CONFIG;
  pio_cfg.pin_dp = PIN_USB_HOST_DP;
#if defined(ARDUINO_RASPBERRY_PI_PICO_W)
  // For pico-w, PIO is also used to communicate with cyw43
  // Therefore we need to alternate the pio-usb configuration
  // details https://github.com/sekigon-gonnoc/Pico-PIO-USB/issues/46
  pio_cfg.sm_tx      = 3;
  pio_cfg.sm_rx      = 2;
  pio_cfg.sm_eop     = 3;
  pio_cfg.pio_rx_num = 0;
  pio_cfg.pio_tx_num = 1;
  pio_cfg.tx_ch      = 9;
#endif
  USBHost.configure_pio_usb(1, &pio_cfg);
}


// forward Serial <-> SerialHost
void forward_serial(void) {
  uint8_t buf[64];  //64= size of USB?

  //KBD to serialHost
  if(UART_KBD.available()){
    size_t count = UART_KBD.readBytes(buf, sizeof(buf));
    tk=millis()+FLICKER_TIME;
    //if (SerialHost && SerialHost.connected()) {
    if (SerialHost) {     //ignore connectivity
      SerialHost.write(buf, count);
      SerialHost.flush();
    }
  }

  // Serial -> SerialHost
  if (Serial.available()) {
    size_t count = Serial.read(buf, sizeof(buf));
    tk=millis()+FLICKER_TIME;
    //if (SerialHost && SerialHost.connected()) {
    if (SerialHost) {     //ignore connectivity
      SerialHost.write(buf, count);
      SerialHost.flush();
    }
  }

  // SerialHost -> Serial and hardware UART
//  if (SerialHost.connected() && SerialHost.available()) {
  if (SerialHost.available()) {   //ignore connectivity
    size_t count = SerialHost.read(buf, sizeof(buf));
    Serial.write(buf, count);
    Serial.flush();
    UART.write(buf,count);    
  }
}

//--------------------------------------------------------------------+
// For RP2040 use both core0 for device stack, core1 for host stack
//--------------------------------------------------------------------+

//------------- Core0 -------------//
void setup() {
  Serial.begin(currentBaudrate);   //virtual, so doesn't actually matter, but match to downstream CDC
  // while ( !Serial ) delay(10);   // wait for native usb
  //Serial.println("TinyUSB Host Serial Echo Example"); //don't want extraneous serial data
  pinMode(STATUS_LED,OUTPUT);
  digitalWrite(STATUS_LED,LOW);
  pinMode(PICO_ONBOARD_LED,OUTPUT);
  digitalWrite(PICO_ONBOARD_LED,HIGH);  //show power on

  EEPROM.begin(EEPROM_SIZE_FROM_FLASH);
  EEPROM.get(EEPROM_DATA_START,currentBaudrate);
  if(currentBaudrate<=0){     //EEPROM blank/error
    //Serial.println("EEPROM error, loading defaults.");
    currentBaudrate=CDC_BAUD;
    saveToFlash();
  }

  UART.setRX(UART_RX);
  UART.setTX(UART_TX);
  UART.setFIFOSize(UART_FIFO);
  UART.begin(UART_BAUD);  

    //UART.printf("Baud is %d\r\n",currentBaudrate);  //debugging if USB-serial not present

  UART_KBD.setRX(UART_RX_KBD);
  UART_KBD.setTX(UART_TX_KBD);
  UART_KBD.setFIFOSize(UART_FIFO_KBD);
  UART_KBD.begin(UART_BAUD_KBD);  
  UART_KBD.setTimeout(0);
}

void loop() {
  //CDC
  uint32_t newBaudrate;
  forward_serial();
  if(Serial){
    newBaudrate=Serial.baud();        //match downstream to upstream
    if(newBaudrate!=currentBaudrate){
      //Serial.printf("Baudrate change: %d -> %d\r\n",currentBaudrate,newBaudrate);
      currentBaudrate=newBaudrate;
      SerialHost.setBaudrate(currentBaudrate);  
      //UART.printf("Baud is %d\r\n",currentBaudrate);  //debugging if USB-serial not present
      saveToFlash();  
    }  
  }
  //determine LED state
  if(cdcMounted){
  //if(SerialHost.connected()){   //ignore DTR connectivity
  //if(SerialHost.mounted()){
    if(millis()<tk){
      digitalWrite(STATUS_LED,LOW); //flicker off when keys pressed
    }else{
      digitalWrite(STATUS_LED,HIGH);    
    }
  }else{
    digitalWrite(STATUS_LED,LOW);
  }
}

//------------- Core1 -------------//
void setup1() {
  delay(20);    //allow other core to start and load EEPROM

  // configure pio-usb: defined in usbh_helper.h
  rp2040_configure_pio_usb();

  // Initialize SerialHost
  SerialHost.begin(currentBaudrate);

  // run host stack on controller (rhport) 1
  // Note: For rp2040 pico-pio-usb, calling USBHost.begin() on core1 will have most of the
  // host bit-banging processing works done in core1 to free up core0 for other works
  USBHost.begin(1);

}

void loop1() {
  USBHost.task();
}

//--------------------------------------------------------------------+
// TinyUSB Host callbacks
//--------------------------------------------------------------------+
extern "C" {

// Invoked when a device with CDC interface is mounted
// idx is index of cdc interface in the internal pool.
void tuh_cdc_mount_cb(uint8_t idx) {
  // bind SerialHost object to this interface index
  SerialHost.mount(idx);  
  cdcMounted=1;
}

// Invoked when a device with CDC interface is unmounted
void tuh_cdc_umount_cb(uint8_t idx) {
  SerialHost.umount(idx);
  cdcMounted=0;
}

}

void saveToFlash(void){
  EEPROM.put(EEPROM_DATA_START,currentBaudrate);
  if(EEPROM.commit()){
    //Serial.println("Saved to flash");
  }else{
    //Serial.println("ERROR! Save to flash failed");
  }
}
