diff --git a/drivers/fatfs/fatfs.cmake b/drivers/fatfs/fatfs.cmake index 4cf0d510..1f5a4bb0 100644 --- a/drivers/fatfs/fatfs.cmake +++ b/drivers/fatfs/fatfs.cmake @@ -9,4 +9,5 @@ if (NOT TARGET fatfs) target_link_libraries(fatfs INTERFACE pico_stdlib hardware_clocks hardware_spi) target_include_directories(fatfs INTERFACE ${CMAKE_CURRENT_LIST_DIR}) + endif() diff --git a/drivers/sdcard/pio_spi.c b/drivers/sdcard/pio_spi.c new file mode 100644 index 00000000..c4db7711 --- /dev/null +++ b/drivers/sdcard/pio_spi.c @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "pio_spi.h" + +// Just 8 bit functions provided here. The PIO program supports any frame size +// 1...32, but the software to do the necessary FIFO shuffling is left as an +// exercise for the reader :) +// +// Likewise we only provide MSB-first here. To do LSB-first, you need to +// - Do shifts when reading from the FIFO, for general case n != 8, 16, 32 +// - Do a narrow read at a one halfword or 3 byte offset for n == 16, 8 +// in order to get the read data correctly justified. + +void __time_critical_func(pio_spi_write8_blocking)(const pio_spi_inst_t *spi, const uint8_t *src, size_t len) { + size_t tx_remain = len, rx_remain = len; + // Do 8 bit accesses on FIFO, so that write data is byte-replicated. This + // gets us the left-justification for free (for MSB-first shift-out) + io_rw_8 *txfifo = (io_rw_8 *) &spi->pio->txf[spi->sm]; + io_rw_8 *rxfifo = (io_rw_8 *) &spi->pio->rxf[spi->sm]; + while (tx_remain || rx_remain) { + if (tx_remain && !pio_sm_is_tx_fifo_full(spi->pio, spi->sm)) { + *txfifo = *src++; + --tx_remain; + } + if (rx_remain && !pio_sm_is_rx_fifo_empty(spi->pio, spi->sm)) { + (void) *rxfifo; + --rx_remain; + } + } +} + +void __time_critical_func(pio_spi_read8_blocking)(const pio_spi_inst_t *spi, uint8_t *dst, size_t len) { + size_t tx_remain = len, rx_remain = len; + io_rw_8 *txfifo = (io_rw_8 *) &spi->pio->txf[spi->sm]; + io_rw_8 *rxfifo = (io_rw_8 *) &spi->pio->rxf[spi->sm]; + while (tx_remain || rx_remain) { + if (tx_remain && !pio_sm_is_tx_fifo_full(spi->pio, spi->sm)) { + *txfifo = 0; + --tx_remain; + } + if (rx_remain && !pio_sm_is_rx_fifo_empty(spi->pio, spi->sm)) { + *dst++ = *rxfifo; + --rx_remain; + } + } +} + +void __time_critical_func(pio_spi_write8_read8_blocking)(const pio_spi_inst_t *spi, uint8_t *src, uint8_t *dst, + size_t len) { + size_t tx_remain = len, rx_remain = len; + io_rw_8 *txfifo = (io_rw_8 *) &spi->pio->txf[spi->sm]; + io_rw_8 *rxfifo = (io_rw_8 *) &spi->pio->rxf[spi->sm]; + while (tx_remain || rx_remain) { + if (tx_remain && !pio_sm_is_tx_fifo_full(spi->pio, spi->sm)) { + *txfifo = *src++; + --tx_remain; + } + if (rx_remain && !pio_sm_is_rx_fifo_empty(spi->pio, spi->sm)) { + *dst++ = *rxfifo; + --rx_remain; + } + } +} + +void __time_critical_func(pio_spi_repeat8_read8_blocking)(const pio_spi_inst_t *spi, uint8_t src, uint8_t *dst, + size_t len) { + size_t tx_remain = len, rx_remain = len; + io_rw_8 *txfifo = (io_rw_8 *) &spi->pio->txf[spi->sm]; + io_rw_8 *rxfifo = (io_rw_8 *) &spi->pio->rxf[spi->sm]; + while (tx_remain || rx_remain) { + if (tx_remain && !pio_sm_is_tx_fifo_full(spi->pio, spi->sm)) { + *txfifo = src; + --tx_remain; + } + if (rx_remain && !pio_sm_is_rx_fifo_empty(spi->pio, spi->sm)) { + *dst++ = *rxfifo; + --rx_remain; + } + } +} \ No newline at end of file diff --git a/drivers/sdcard/pio_spi.h b/drivers/sdcard/pio_spi.h new file mode 100644 index 00000000..f6c4f6ff --- /dev/null +++ b/drivers/sdcard/pio_spi.h @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +#ifndef _PIO_SPI_H +#define _PIO_SPI_H + +#include "hardware/pio.h" +#include "spi.pio.h" + +typedef struct pio_spi_inst { + PIO pio; + uint sm; + uint cs_pin; +} pio_spi_inst_t; + +void pio_spi_write8_blocking(const pio_spi_inst_t *spi, const uint8_t *src, size_t len); + +void pio_spi_read8_blocking(const pio_spi_inst_t *spi, uint8_t *dst, size_t len); + +void pio_spi_write8_read8_blocking(const pio_spi_inst_t *spi, uint8_t *src, uint8_t *dst, size_t len); + +void pio_spi_repeat8_read8_blocking(const pio_spi_inst_t *spi, uint8_t src, uint8_t *dst, size_t len); + +#endif \ No newline at end of file diff --git a/drivers/sdcard/sdcard.c b/drivers/sdcard/sdcard.c index e0ef58a0..3da9f9e4 100644 --- a/drivers/sdcard/sdcard.c +++ b/drivers/sdcard/sdcard.c @@ -3,7 +3,11 @@ #include "pico.h" #include "pico/stdlib.h" #include "hardware/clocks.h" +#ifndef SDCARD_PIO #include "hardware/spi.h" +#else +#include "pio_spi.h" +#endif #include "hardware/gpio.h" //#include "hardware/gpio_ex.h" @@ -55,6 +59,13 @@ DSTATUS Stat = STA_NOINIT; /* Physical drive status */ static BYTE CardType; /* Card type flags */ +#ifdef SDCARD_PIO +pio_spi_inst_t pio_spi = { + .pio = SDCARD_PIO, + .sm = SDCARD_PIO_SM +}; +#endif + static inline uint32_t _millis(void) { return to_ms_since_boot(get_absolute_time()); @@ -78,12 +89,16 @@ static inline void cs_deselect(uint cs_pin) { static void FCLK_SLOW(void) { - spi_set_baudrate(spi0, CLK_SLOW); +#ifndef SDCARD_PIO + spi_set_baudrate(SDCARD_SPI_BUS, CLK_SLOW); +#endif } static void FCLK_FAST(void) { - spi_set_baudrate(spi0, CLK_FAST); +#ifndef SDCARD_PIO + spi_set_baudrate(SDCARD_SPI_BUS, CLK_FAST); +#endif } static void CS_HIGH(void) @@ -107,18 +122,15 @@ void init_spi(void) //gpio_pull_up(SDCARD_PIN_SPI0_SCK); //gpio_set_drive_strength(SDCARD_PIN_SPI0_SCK, PADS_BANK0_GPIO0_DRIVE_VALUE_4MA); // 2mA, 4mA (default), 8mA, 12mA //gpio_set_slew_rate(SDCARD_PIN_SPI0_SCK, 0); // 0: SLOW (default), 1: FAST - gpio_set_function(SDCARD_PIN_SPI0_SCK, GPIO_FUNC_SPI); gpio_init(SDCARD_PIN_SPI0_MISO); gpio_pull_up(SDCARD_PIN_SPI0_MISO); //gpio_set_schmitt(SDCARD_PIN_SPI0_MISO, 1); // 0: Off, 1: On (default) - gpio_set_function(SDCARD_PIN_SPI0_MISO, GPIO_FUNC_SPI); gpio_init(SDCARD_PIN_SPI0_MOSI); gpio_pull_up(SDCARD_PIN_SPI0_MOSI); //gpio_set_drive_strength(SDCARD_PIN_SPI0_MOSI, PADS_BANK0_GPIO0_DRIVE_VALUE_4MA); // 2mA, 4mA (default), 8mA, 12mA //gpio_set_slew_rate(SDCARD_PIN_SPI0_MOSI, 0); // 0: SLOW (default), 1: FAST - gpio_set_function(SDCARD_PIN_SPI0_MOSI, GPIO_FUNC_SPI); gpio_init(SDCARD_PIN_SPI0_CS); //gpio_pull_up(SDCARD_PIN_SPI0_CS); @@ -129,15 +141,40 @@ void init_spi(void) /* chip _select invalid*/ CS_HIGH(); - spi_init(spi0, CLK_SLOW); +#ifndef SDCARD_PIO + gpio_set_function(SDCARD_PIN_SPI0_SCK, GPIO_FUNC_SPI); + gpio_set_function(SDCARD_PIN_SPI0_MISO, GPIO_FUNC_SPI); + gpio_set_function(SDCARD_PIN_SPI0_MOSI, GPIO_FUNC_SPI); + + spi_init(SDCARD_SPI_BUS, CLK_SLOW); /* SPI0 parameter config */ - spi_set_format(spi0, + spi_set_format(SDCARD_SPI_BUS, 8, /* data_bits */ SPI_CPOL_0, /* cpol */ SPI_CPHA_0, /* cpha */ SPI_MSB_FIRST /* order */ ); +#else + gpio_set_dir(SDCARD_PIN_SPI0_SCK, GPIO_OUT); + gpio_set_dir(SDCARD_PIN_SPI0_MISO, GPIO_OUT); + gpio_set_dir(SDCARD_PIN_SPI0_MOSI, GPIO_OUT); + + float clkdiv = 3.0f; + int cpol = 0; + int cpha = 0; + uint cpha0_prog_offs = pio_add_program(pio_spi.pio, &spi_cpha0_program); + pio_spi_init(pio_spi.pio, pio_spi.sm, + cpha0_prog_offs, + 8, // 8 bits per SPI frame + clkdiv, + cpha, + cpol, + SDCARD_PIN_SPI0_SCK, + SDCARD_PIN_SPI0_MOSI, + SDCARD_PIN_SPI0_MISO + ); +#endif } /* Exchange a byte */ @@ -147,7 +184,11 @@ BYTE xchg_spi ( ) { uint8_t *buff = (uint8_t *) &dat; - spi_write_read_blocking(spi0, buff, buff, 1); +#ifndef SDCARD_PIO + spi_write_read_blocking(SDCARD_SPI_BUS, buff, buff, 1); +#else + pio_spi_write8_read8_blocking(&pio_spi, buff, buff, 1); +#endif return (BYTE) *buff; } @@ -160,7 +201,11 @@ void rcvr_spi_multi ( ) { uint8_t *b = (uint8_t *) buff; - spi_read_blocking(spi0, 0xff, b, btr); +#ifndef SDCARD_PIO + spi_read_blocking(SDCARD_SPI_BUS, 0xff, b, btr); +#else + pio_spi_repeat8_read8_blocking(&pio_spi, 0xff, b, btr); +#endif } @@ -427,7 +472,11 @@ void xmit_spi_multi ( ) { const uint8_t *b = (const uint8_t *) buff; - spi_write_blocking(spi0, b, btx); +#ifndef SDCARD_PIO + spi_write_blocking(SDCARD_SPI_BUS, b, btx); +#else + pio_spi_write8_blocking(&pio_spi, b, btx); +#endif } /*-----------------------------------------------------------------------*/ diff --git a/drivers/sdcard/sdcard.cmake b/drivers/sdcard/sdcard.cmake index c7e539df..7242dbf8 100644 --- a/drivers/sdcard/sdcard.cmake +++ b/drivers/sdcard/sdcard.cmake @@ -1,10 +1,13 @@ if (NOT TARGET sdcard) add_library(sdcard INTERFACE) + pico_generate_pio_header(sdcard ${CMAKE_CURRENT_LIST_DIR}/spi.pio) + target_sources(sdcard INTERFACE ${CMAKE_CURRENT_LIST_DIR}/sdcard.c + ${CMAKE_CURRENT_LIST_DIR}/pio_spi.c ) - target_link_libraries(sdcard INTERFACE fatfs pico_stdlib hardware_clocks hardware_spi) + target_link_libraries(sdcard INTERFACE fatfs pico_stdlib hardware_clocks hardware_spi hardware_pio) target_include_directories(sdcard INTERFACE ${CMAKE_CURRENT_LIST_DIR}) endif() diff --git a/drivers/sdcard/sdcard.h b/drivers/sdcard/sdcard.h index fb3f7172..3bdb7ebc 100644 --- a/drivers/sdcard/sdcard.h +++ b/drivers/sdcard/sdcard.h @@ -4,6 +4,10 @@ /* SPI pin assignment */ /* Pico Wireless */ +#ifndef SDCARD_SPI_BUS +#define SDCARD_SPI_BUS spi0 +#endif + #ifndef SDCARD_PIN_SPI0_CS #define SDCARD_PIN_SPI0_CS 22 #endif diff --git a/drivers/sdcard/spi.pio b/drivers/sdcard/spi.pio new file mode 100644 index 00000000..4438e3d6 --- /dev/null +++ b/drivers/sdcard/spi.pio @@ -0,0 +1,168 @@ +; +; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. +; +; SPDX-License-Identifier: BSD-3-Clause +; + +; These programs implement full-duplex SPI, with a SCK period of 4 clock +; cycles. A different program is provided for each value of CPHA, and CPOL is +; achieved using the hardware GPIO inversion available in the IO controls. +; +; Transmit-only SPI can go twice as fast -- see the ST7789 example! + + +.program spi_cpha0 +.side_set 1 + +; Pin assignments: +; - SCK is side-set pin 0 +; - MOSI is OUT pin 0 +; - MISO is IN pin 0 +; +; Autopush and autopull must be enabled, and the serial frame size is set by +; configuring the push/pull threshold. Shift left/right is fine, but you must +; justify the data yourself. This is done most conveniently for frame sizes of +; 8 or 16 bits by using the narrow store replication and narrow load byte +; picking behaviour of RP2040's IO fabric. + +; Clock phase = 0: data is captured on the leading edge of each SCK pulse, and +; transitions on the trailing edge, or some time before the first leading edge. + + out pins, 1 side 0 [1] ; Stall here on empty (sideset proceeds even if + in pins, 1 side 1 [1] ; instruction stalls, so we stall with SCK low) + +.program spi_cpha1 +.side_set 1 + +; Clock phase = 1: data transitions on the leading edge of each SCK pulse, and +; is captured on the trailing edge. + + out x, 1 side 0 ; Stall here on empty (keep SCK deasserted) + mov pins, x side 1 [1] ; Output data, assert SCK (mov pins uses OUT mapping) + in pins, 1 side 0 ; Input data, deassert SCK + +% c-sdk { +#include "hardware/gpio.h" +static inline void pio_spi_init(PIO pio, uint sm, uint prog_offs, uint n_bits, + float clkdiv, bool cpha, bool cpol, uint pin_sck, uint pin_mosi, uint pin_miso) { + pio_sm_config c = cpha ? spi_cpha1_program_get_default_config(prog_offs) : spi_cpha0_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_sck); + // Only support MSB-first in this example code (shift to left, auto push/pull, threshold=nbits) + 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); + + // MOSI, SCK output are low, MISO is input + pio_sm_set_pins_with_mask(pio, sm, 0, (1u << pin_sck) | (1u << pin_mosi)); + pio_sm_set_pindirs_with_mask(pio, sm, (1u << pin_sck) | (1u << pin_mosi), (1u << pin_sck) | (1u << pin_mosi) | (1u << pin_miso)); + pio_gpio_init(pio, pin_mosi); + pio_gpio_init(pio, pin_miso); + pio_gpio_init(pio, pin_sck); + + // The pin muxes can be configured to invert the output (among other things + // and this is a cheesy way to get CPOL=1 + gpio_set_outover(pin_sck, cpol ? GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL); + // SPI is synchronous, so bypass input synchroniser to reduce input delay. + 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); +} +%} + +; SPI with Chip Select +; ----------------------------------------------------------------------------- +; +; For your amusement, here are some SPI programs with an automatic chip select +; (asserted once data appears in TX FIFO, deasserts when FIFO bottoms out, has +; a nice front/back porch). +; +; The number of bits per FIFO entry is configured via the Y register +; and the autopush/pull threshold. From 2 to 32 bits. +; +; Pin assignments: +; - SCK is side-set bit 0 +; - CSn is side-set bit 1 +; - MOSI is OUT bit 0 (host-to-device) +; - MISO is IN bit 0 (device-to-host) +; +; This program only supports one chip select -- use GPIO if more are needed +; +; Provide a variation for each possibility of CPHA; for CPOL we can just +; invert SCK in the IO muxing controls (downstream from PIO) + + +; CPHA=0: data is captured on the leading edge of each SCK pulse (including +; the first pulse), and transitions on the trailing edge + +.program spi_cpha0_cs +.side_set 2 + +.wrap_target +bitloop: + out pins, 1 side 0x0 [1] + in pins, 1 side 0x1 + jmp x-- bitloop side 0x1 + + out pins, 1 side 0x0 + mov x, y side 0x0 ; Reload bit counter from Y + in pins, 1 side 0x1 + jmp !osre bitloop side 0x1 ; Fall-through if TXF empties + + nop side 0x0 [1] ; CSn back porch +public entry_point: ; Must set X,Y to n-2 before starting! + pull ifempty side 0x2 [1] ; Block with CSn high (minimum 2 cycles) +.wrap ; Note ifempty to avoid time-of-check race + +; CPHA=1: data transitions on the leading edge of each SCK pulse, and is +; captured on the trailing edge + +.program spi_cpha1_cs +.side_set 2 + +.wrap_target +bitloop: + out pins, 1 side 0x1 [1] + in pins, 1 side 0x0 + jmp x-- bitloop side 0x0 + + out pins, 1 side 0x1 + mov x, y side 0x1 + in pins, 1 side 0x0 + jmp !osre bitloop side 0x0 + +public entry_point: ; Must set X,Y to n-2 before starting! + pull ifempty side 0x2 [1] ; Block with CSn high (minimum 2 cycles) + nop side 0x0 [1]; CSn front porch +.wrap + +% c-sdk { +#include "hardware/gpio.h" +static inline void pio_spi_cs_init(PIO pio, uint sm, uint prog_offs, uint n_bits, float clkdiv, bool cpha, bool cpol, + uint pin_sck, uint pin_mosi, uint pin_miso) { + pio_sm_config c = cpha ? spi_cpha1_cs_program_get_default_config(prog_offs) : spi_cpha0_cs_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_sck); + 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_pins_with_mask(pio, sm, (2u << pin_sck), (3u << pin_sck) | (1u << pin_mosi)); + pio_sm_set_pindirs_with_mask(pio, sm, (3u << pin_sck) | (1u << pin_mosi), (3u << pin_sck) | (1u << pin_mosi) | (1u << pin_miso)); + pio_gpio_init(pio, pin_mosi); + pio_gpio_init(pio, pin_miso); + pio_gpio_init(pio, pin_sck); + pio_gpio_init(pio, pin_sck + 1); + gpio_set_outover(pin_sck, cpol ? GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL); + hw_set_bits(&pio->input_sync_bypass, 1u << pin_miso); + + uint entry_point = prog_offs + (cpha ? spi_cpha1_cs_offset_entry_point : spi_cpha0_cs_offset_entry_point); + pio_sm_init(pio, sm, entry_point, &c); + pio_sm_exec(pio, sm, pio_encode_set(pio_x, n_bits - 2)); + pio_sm_exec(pio, sm, pio_encode_set(pio_y, n_bits - 2)); + pio_sm_set_enabled(pio, sm, true); +} +%} \ No newline at end of file