; rp2040-psram
; 
; Copyright © 2023 Ian Scott
; 
; Permission is hereby granted, free of charge, to any person obtaining a copy of
; this software and associated documentation files (the “Software”), to deal in
; the Software without restriction, including without limitation the rights to
; use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
; of the Software, and to permit persons to whom the Software is furnished to do
; so, subject to the following conditions:
; 
; The above copyright notice and this permission notice shall be included in all
; copies or substantial portions of the Software.
; 
; THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
; SOFTWARE.

; SPI, customized as the PSRAM chips like to implement it:
; - Data is always written first, then optionally read
; Depending on PCB layout, introduce fudge factor:
; - Reads in high speed mode need an extra clock cycle to synchronize
; - Reads are done on the falling edge of SCK when > 83MHz

.program spi_psram_fudge
.side_set 2                        ; sideset bit 1 is SCK, bit 0 is CS
begin:
    out x, 8            side 0b01  ; x = number of bits to output. CS deasserted
    out y, 8            side 0b01  ; y = number of bits to input
    jmp x--, writeloop  side 0b01  ; Pre-decement x by 1 so loop has correct number of iterations
writeloop:
    out pins, 1         side 0b00  ; Write value on pin, lower clock. CS asserted
    jmp x--, writeloop  side 0b10  ; Raise clock: this is when PSRAM reads the value. Loop if we have more to write
    jmp !y, begin       side 0b00  ; If this is a write-only operation, jump back to beginning
    nop                 side 0b10  ; Fudge factor of extra clock cycle; the PSRAM needs 1 extra for output to start appearing
    jmp readloop_mid    side 0b00  ; Jump to middle of readloop to decrement y and get right clock phase
readloop:
    in pins, 1          side 0b00  ; Read value on pin, lower clock. Datasheet says to read on falling edge > 83MHz
readloop_mid:
    jmp y--, readloop   side 0b10  ; Raise clock. Loop if we have more to read

.program spi_psram
.side_set 2                        ; sideset bit 1 is SCK, bit 0 is CS
begin:
    out x, 8            side 0b01  ; x = number of bits to output. CS deasserted
    out y, 8            side 0b01  ; y = number of bits to input
    jmp x--, writeloop  side 0b01  ; Pre-decement x by 1 so loop has correct number of iterations
writeloop:
    out pins, 1         side 0b00  ; Write value on pin, lower clock. CS asserted
    jmp x--, writeloop  side 0b10  ; Raise clock: this is when PSRAM reads the value. Loop if we have more to write
    jmp !y, begin       side 0b00  ; If this is a write-only operation, jump back to beginning
    jmp readloop_mid    side 0b10  ; Jump to middle of readloop to decrement y and get right clock phase
readloop:
    in pins, 1          side 0b10  ; Read value on pin, raise clock.
readloop_mid:
    jmp y--, readloop   side 0b00  ; Lower clock. Loop if we have more to read

% c-sdk {
#include "hardware/gpio.h"
static inline void pio_spi_psram_cs_init(PIO pio, uint sm, uint prog_offs, uint n_bits, float clkdiv, bool fudge, uint pin_cs, uint pin_mosi, uint pin_miso) {
    pio_sm_config c;
    if (fudge) {
        c = spi_psram_fudge_program_get_default_config(prog_offs);
    } else {
        c = spi_psram_program_get_default_config(prog_offs);
    }
    sm_config_set_out_pins(&c, pin_mosi, 1);
    sm_config_set_in_pins(&c, pin_miso);
    sm_config_set_sideset_pins(&c, pin_cs);
    sm_config_set_out_shift(&c, false, true, n_bits);
    sm_config_set_in_shift(&c, false, true, n_bits);
    sm_config_set_clkdiv(&c, clkdiv);

    pio_sm_set_consecutive_pindirs(pio, sm, pin_cs, 2, true);
    pio_sm_set_consecutive_pindirs(pio, sm, pin_mosi, 1, true);
    pio_sm_set_consecutive_pindirs(pio, sm, pin_miso, 1, false);
    pio_gpio_init(pio, pin_mosi);
    pio_gpio_init(pio, pin_cs);
    pio_gpio_init(pio, pin_cs + 1);

    hw_set_bits(&pio->input_sync_bypass, 1u << pin_miso);

    pio_sm_init(pio, sm, prog_offs, &c);
    pio_sm_set_enabled(pio, sm, true);
}
%}

.program qspi_psram
.side_set 2
begin:
    out x, 8            side 0b01  ; x = number of nibbles to output. CS deasserted
    out y, 8            side 0b01  ; y = number of nibbles to input
    jmp x--, writeloop  side 0b01  ; Pre-decement x by 1 so loop has correct number of iterations
writeloop:
    out pins, 4         side 0b00  ; Write value on pins, lower clock. CS asserted
    jmp x--, writeloop  side 0b10  ; Raise clock: this is when PSRAM reads the value. Loop if we have more to write
    jmp !y, begin       side 0b00  ; If this is a write-only operation, jump back to beginning
    set pindirs 0       side 0b10  ; Fudge factor of extra clock cycle; the PSRAM needs 1 extra for output to start appearing
    nop    side 0b00  ; Jump to middle of readloop to decrement y and get right clock phase
readloop:
    in pins, 4          side 0b10  ; Read value on s, lower clock. Datasheet says to read on falling edge > 83MHz
readloop_mid:
    jmp y--, readloop   side 0b00  ; Raise clock. Loop if we have more to read
    set pindirs 0xF     side 0b01

% c-sdk {
#include "hardware/gpio.h"
static inline void pio_qspi_psram_cs_init(PIO pio, uint sm, uint prog_offs, uint n_bits, float clkdiv, uint pin_cs, uint pin_sio0) {
    pio_sm_config c = qspi_psram_program_get_default_config(prog_offs);
    sm_config_set_out_pins(&c, pin_sio0, 4);
    sm_config_set_in_pins(&c, pin_sio0);
    sm_config_set_set_pins(&c, pin_sio0, 4);
    sm_config_set_sideset_pins(&c, pin_cs);
    sm_config_set_out_shift(&c, false, true, n_bits);
    sm_config_set_in_shift(&c, false, true, n_bits);
    sm_config_set_clkdiv(&c, clkdiv);

    pio_sm_set_consecutive_pindirs(pio, sm, pin_cs, 2, true);
    pio_sm_set_consecutive_pindirs(pio, sm, pin_sio0, 4, true);
    pio_gpio_init(pio, pin_sio0);
    pio_gpio_init(pio, pin_sio0 + 1);
    pio_gpio_init(pio, pin_sio0 + 2);
    pio_gpio_init(pio, pin_sio0 + 3);
    pio_gpio_init(pio, pin_cs);
    pio_gpio_init(pio, pin_cs + 1);

    hw_set_bits(&pio->input_sync_bypass, 0xfu << pin_sio0);

    pio_sm_init(pio, sm, prog_offs, &c);
    pio_sm_set_enabled(pio, sm, true);
}
%}
