2026-04-04 16:22:37 +09:00
|
|
|
#include <cstdio>
|
|
|
|
|
#include <cstring>
|
|
|
|
|
#include <initializer_list>
|
|
|
|
|
#include "pico/stdlib.h"
|
|
|
|
|
#include "pico/error.h"
|
|
|
|
|
#include "hardware/dma.h"
|
|
|
|
|
#include "hardware/clocks.h"
|
2026-04-04 20:36:22 +09:00
|
|
|
#include "pio.h"
|
|
|
|
|
#include "pio.pio.h"
|
2026-04-04 16:22:37 +09:00
|
|
|
|
|
|
|
|
#define PIO_PROGRAM_NAME wizchip_pio_spi_quad_write_read
|
|
|
|
|
#define PIO_PROGRAM_FUNC __CONCAT(PIO_PROGRAM_NAME, _program)
|
|
|
|
|
#define PIO_PROGRAM_GET_DEFAULT_CONFIG_FUNC __CONCAT(PIO_PROGRAM_NAME, _program_get_default_config)
|
|
|
|
|
#define PIO_OFFSET_WRITE_BITS __CONCAT(PIO_PROGRAM_NAME, _offset_write_bits)
|
|
|
|
|
#define PIO_OFFSET_WRITE_BITS_END __CONCAT(PIO_PROGRAM_NAME, _offset_write_bits_end)
|
|
|
|
|
#define PIO_OFFSET_READ_BITS_END __CONCAT(PIO_PROGRAM_NAME, _offset_read_bits_end)
|
|
|
|
|
|
|
|
|
|
static constexpr uint32_t PADS_DRIVE = PADS_BANK0_GPIO0_DRIVE_VALUE_12MA;
|
|
|
|
|
static constexpr uint32_t IRQ_DELAY_NS = 100;
|
|
|
|
|
static constexpr uint32_t QSPI_LOOP_CNT = 2;
|
|
|
|
|
|
|
|
|
|
static struct {
|
|
|
|
|
pio_hw_t *pio;
|
|
|
|
|
uint8_t pio_func_sel;
|
|
|
|
|
int8_t pio_offset;
|
|
|
|
|
int8_t pio_sm;
|
|
|
|
|
int8_t dma_out;
|
|
|
|
|
int8_t dma_in;
|
|
|
|
|
} state;
|
|
|
|
|
|
|
|
|
|
static uint16_t mk_cmd_buf(uint8_t *pdst, uint8_t opcode, uint16_t addr) {
|
|
|
|
|
pdst[0] = ((opcode >> 7 & 0x01) << 4) | ((opcode >> 6 & 0x01) << 0);
|
|
|
|
|
pdst[1] = ((opcode >> 5 & 0x01) << 4) | ((opcode >> 4 & 0x01) << 0);
|
|
|
|
|
pdst[2] = ((opcode >> 3 & 0x01) << 4) | ((opcode >> 2 & 0x01) << 0);
|
|
|
|
|
pdst[3] = ((opcode >> 1 & 0x01) << 4) | ((opcode >> 0 & 0x01) << 0);
|
|
|
|
|
pdst[4] = (uint8_t)(addr >> 8);
|
|
|
|
|
pdst[5] = (uint8_t)(addr);
|
|
|
|
|
pdst[6] = 0;
|
|
|
|
|
return 7;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static uint32_t data_pin_mask() {
|
|
|
|
|
return (1u << PIO_SPI_DATA_IO0_PIN) | (1u << PIO_SPI_DATA_IO1_PIN) |
|
|
|
|
|
(1u << PIO_SPI_DATA_IO2_PIN) | (1u << PIO_SPI_DATA_IO3_PIN);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static __noinline void ns_delay(uint32_t ns) {
|
|
|
|
|
uint32_t cycles = ns * (clock_get_hz(clk_sys) >> 16u) / (1000000000u >> 16u);
|
|
|
|
|
busy_wait_at_least_cycles(cycles);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void wizchip_pio_init() {
|
|
|
|
|
for (auto pin : {PIO_SPI_DATA_IO0_PIN, PIO_SPI_DATA_IO1_PIN, PIO_SPI_DATA_IO2_PIN, PIO_SPI_DATA_IO3_PIN}) {
|
|
|
|
|
gpio_init(pin);
|
|
|
|
|
gpio_set_dir(pin, GPIO_OUT);
|
|
|
|
|
gpio_put(pin, false);
|
|
|
|
|
}
|
|
|
|
|
gpio_init(PIN_CS);
|
|
|
|
|
gpio_set_dir(PIN_CS, GPIO_OUT);
|
|
|
|
|
gpio_put(PIN_CS, true);
|
|
|
|
|
gpio_init(PIN_INT);
|
|
|
|
|
gpio_set_dir(PIN_INT, GPIO_IN);
|
|
|
|
|
gpio_set_pulls(PIN_INT, false, false);
|
|
|
|
|
|
|
|
|
|
pio_hw_t *pios[2] = {pio0, pio1};
|
|
|
|
|
uint pio_index = 1;
|
|
|
|
|
if (!pio_can_add_program(pios[pio_index], &PIO_PROGRAM_FUNC)) {
|
|
|
|
|
pio_index ^= 1;
|
|
|
|
|
assert(pio_can_add_program(pios[pio_index], &PIO_PROGRAM_FUNC));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state.pio = pios[pio_index];
|
|
|
|
|
state.dma_in = -1;
|
|
|
|
|
state.dma_out = -1;
|
|
|
|
|
|
|
|
|
|
static_assert(GPIO_FUNC_PIO1 == GPIO_FUNC_PIO0 + 1);
|
|
|
|
|
state.pio_func_sel = GPIO_FUNC_PIO0 + pio_index;
|
|
|
|
|
state.pio_sm = (int8_t)pio_claim_unused_sm(state.pio, true);
|
|
|
|
|
state.pio_offset = pio_add_program(state.pio, &PIO_PROGRAM_FUNC);
|
|
|
|
|
|
|
|
|
|
pio_sm_config sm_config = PIO_PROGRAM_GET_DEFAULT_CONFIG_FUNC(state.pio_offset);
|
|
|
|
|
sm_config_set_clkdiv_int_frac(&sm_config, WIZNET_SPI_CLKDIV_MAJOR_DEFAULT, WIZNET_SPI_CLKDIV_MINOR_DEFAULT);
|
|
|
|
|
|
|
|
|
|
hw_write_masked(&pads_bank0_hw->io[PIO_SPI_SCK_PIN],
|
|
|
|
|
(uint)PADS_DRIVE << PADS_BANK0_GPIO0_DRIVE_LSB,
|
|
|
|
|
PADS_BANK0_GPIO0_DRIVE_BITS);
|
|
|
|
|
hw_write_masked(&pads_bank0_hw->io[PIO_SPI_SCK_PIN],
|
|
|
|
|
1u << PADS_BANK0_GPIO0_SLEWFAST_LSB,
|
|
|
|
|
PADS_BANK0_GPIO0_SLEWFAST_BITS);
|
|
|
|
|
|
|
|
|
|
sm_config_set_out_pins(&sm_config, PIO_SPI_DATA_IO0_PIN, 4);
|
|
|
|
|
sm_config_set_in_pins(&sm_config, PIO_SPI_DATA_IO0_PIN);
|
|
|
|
|
sm_config_set_set_pins(&sm_config, PIO_SPI_DATA_IO0_PIN, 4);
|
|
|
|
|
sm_config_set_sideset(&sm_config, 1, false, false);
|
|
|
|
|
sm_config_set_sideset_pins(&sm_config, PIO_SPI_SCK_PIN);
|
|
|
|
|
sm_config_set_in_shift(&sm_config, false, true, 8);
|
|
|
|
|
sm_config_set_out_shift(&sm_config, false, true, 8);
|
|
|
|
|
hw_set_bits(&state.pio->input_sync_bypass, data_pin_mask());
|
|
|
|
|
pio_sm_set_config(state.pio, state.pio_sm, &sm_config);
|
|
|
|
|
pio_sm_set_consecutive_pindirs(state.pio, state.pio_sm, PIO_SPI_SCK_PIN, 1, true);
|
|
|
|
|
|
|
|
|
|
for (auto pin : {PIO_SPI_DATA_IO0_PIN, PIO_SPI_DATA_IO1_PIN, PIO_SPI_DATA_IO2_PIN, PIO_SPI_DATA_IO3_PIN}) {
|
|
|
|
|
gpio_set_function(pin, (gpio_function_t)state.pio_func_sel);
|
|
|
|
|
gpio_set_pulls(pin, false, true);
|
|
|
|
|
gpio_set_input_hysteresis_enabled(pin, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pio_sm_exec(state.pio, state.pio_sm, pio_encode_set(pio_pins, 1));
|
|
|
|
|
|
|
|
|
|
state.dma_out = (int8_t)dma_claim_unused_channel(true);
|
|
|
|
|
state.dma_in = (int8_t)dma_claim_unused_channel(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void wizchip_pio_close() {
|
|
|
|
|
if (state.pio_sm >= 0) {
|
|
|
|
|
if (state.pio_offset != -1) {
|
|
|
|
|
pio_remove_program(state.pio, &PIO_PROGRAM_FUNC, state.pio_offset);
|
|
|
|
|
}
|
|
|
|
|
pio_sm_unclaim(state.pio, state.pio_sm);
|
|
|
|
|
}
|
|
|
|
|
if (state.dma_out >= 0) dma_channel_unclaim(state.dma_out);
|
|
|
|
|
if (state.dma_in >= 0) dma_channel_unclaim(state.dma_in);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void wizchip_pio_frame_start() {
|
|
|
|
|
for (auto pin : {PIO_SPI_DATA_IO0_PIN, PIO_SPI_DATA_IO1_PIN, PIO_SPI_DATA_IO2_PIN, PIO_SPI_DATA_IO3_PIN})
|
|
|
|
|
gpio_set_function(pin, (gpio_function_t)state.pio_func_sel);
|
|
|
|
|
gpio_set_function(PIO_SPI_SCK_PIN, (gpio_function_t)state.pio_func_sel);
|
|
|
|
|
gpio_pull_down(PIO_SPI_SCK_PIN);
|
|
|
|
|
gpio_put(PIN_CS, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void wizchip_pio_frame_end() {
|
|
|
|
|
gpio_put(PIN_CS, true);
|
|
|
|
|
ns_delay(IRQ_DELAY_NS);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void wizchip_pio_read(uint8_t opcode, uint16_t addr, uint8_t* buf, uint16_t len) {
|
|
|
|
|
uint8_t cmd[8] = {};
|
|
|
|
|
uint16_t cmd_len = mk_cmd_buf(cmd, opcode, addr);
|
|
|
|
|
|
|
|
|
|
pio_sm_set_enabled(state.pio, state.pio_sm, false);
|
|
|
|
|
pio_sm_set_wrap(state.pio, state.pio_sm, state.pio_offset, state.pio_offset + PIO_OFFSET_READ_BITS_END - 1);
|
|
|
|
|
pio_sm_clear_fifos(state.pio, state.pio_sm);
|
|
|
|
|
pio_sm_set_pindirs_with_mask(state.pio, state.pio_sm, data_pin_mask(), data_pin_mask());
|
|
|
|
|
pio_sm_restart(state.pio, state.pio_sm);
|
|
|
|
|
pio_sm_clkdiv_restart(state.pio, state.pio_sm);
|
|
|
|
|
|
|
|
|
|
pio_sm_put(state.pio, state.pio_sm, cmd_len * QSPI_LOOP_CNT - 1);
|
|
|
|
|
pio_sm_exec(state.pio, state.pio_sm, pio_encode_out(pio_x, 32));
|
|
|
|
|
pio_sm_put(state.pio, state.pio_sm, len - 1);
|
|
|
|
|
pio_sm_exec(state.pio, state.pio_sm, pio_encode_out(pio_y, 32));
|
|
|
|
|
pio_sm_exec(state.pio, state.pio_sm, pio_encode_jmp(state.pio_offset));
|
|
|
|
|
|
|
|
|
|
dma_channel_abort(state.dma_out);
|
|
|
|
|
dma_channel_abort(state.dma_in);
|
|
|
|
|
|
|
|
|
|
dma_channel_config out_cfg = dma_channel_get_default_config(state.dma_out);
|
|
|
|
|
channel_config_set_transfer_data_size(&out_cfg, DMA_SIZE_8);
|
|
|
|
|
channel_config_set_bswap(&out_cfg, true);
|
|
|
|
|
channel_config_set_dreq(&out_cfg, pio_get_dreq(state.pio, state.pio_sm, true));
|
|
|
|
|
dma_channel_configure(state.dma_out, &out_cfg, &state.pio->txf[state.pio_sm], cmd, cmd_len, true);
|
|
|
|
|
|
|
|
|
|
dma_channel_config in_cfg = dma_channel_get_default_config(state.dma_in);
|
|
|
|
|
channel_config_set_transfer_data_size(&in_cfg, DMA_SIZE_8);
|
|
|
|
|
channel_config_set_bswap(&in_cfg, true);
|
|
|
|
|
channel_config_set_dreq(&in_cfg, pio_get_dreq(state.pio, state.pio_sm, false));
|
|
|
|
|
channel_config_set_write_increment(&in_cfg, true);
|
|
|
|
|
channel_config_set_read_increment(&in_cfg, false);
|
|
|
|
|
dma_channel_configure(state.dma_in, &in_cfg, buf, &state.pio->rxf[state.pio_sm], len, true);
|
|
|
|
|
|
|
|
|
|
pio_sm_set_enabled(state.pio, state.pio_sm, true);
|
|
|
|
|
__compiler_memory_barrier();
|
|
|
|
|
dma_channel_wait_for_finish_blocking(state.dma_out);
|
|
|
|
|
dma_channel_wait_for_finish_blocking(state.dma_in);
|
|
|
|
|
__compiler_memory_barrier();
|
|
|
|
|
pio_sm_set_enabled(state.pio, state.pio_sm, false);
|
|
|
|
|
pio_sm_exec(state.pio, state.pio_sm, pio_encode_mov(pio_pins, pio_null));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void wizchip_pio_write(uint8_t opcode, uint16_t addr, uint8_t* buf, uint16_t len) {
|
|
|
|
|
uint8_t cmd[8] = {};
|
|
|
|
|
uint16_t cmd_len = mk_cmd_buf(cmd, opcode, addr);
|
|
|
|
|
uint16_t total = len + cmd_len;
|
|
|
|
|
|
|
|
|
|
pio_sm_set_enabled(state.pio, state.pio_sm, false);
|
|
|
|
|
pio_sm_set_wrap(state.pio, state.pio_sm, state.pio_offset, state.pio_offset + PIO_OFFSET_WRITE_BITS_END - 1);
|
|
|
|
|
pio_sm_clear_fifos(state.pio, state.pio_sm);
|
|
|
|
|
pio_sm_set_pindirs_with_mask(state.pio, state.pio_sm, data_pin_mask(), data_pin_mask());
|
|
|
|
|
pio_sm_restart(state.pio, state.pio_sm);
|
|
|
|
|
pio_sm_clkdiv_restart(state.pio, state.pio_sm);
|
|
|
|
|
|
|
|
|
|
pio_sm_put(state.pio, state.pio_sm, total * QSPI_LOOP_CNT - 1);
|
|
|
|
|
pio_sm_exec(state.pio, state.pio_sm, pio_encode_out(pio_x, 32));
|
|
|
|
|
pio_sm_put(state.pio, state.pio_sm, 0);
|
|
|
|
|
pio_sm_exec(state.pio, state.pio_sm, pio_encode_out(pio_y, 32));
|
|
|
|
|
pio_sm_exec(state.pio, state.pio_sm, pio_encode_jmp(state.pio_offset));
|
|
|
|
|
|
|
|
|
|
dma_channel_abort(state.dma_out);
|
|
|
|
|
|
|
|
|
|
dma_channel_config out_cfg = dma_channel_get_default_config(state.dma_out);
|
|
|
|
|
channel_config_set_transfer_data_size(&out_cfg, DMA_SIZE_8);
|
|
|
|
|
channel_config_set_bswap(&out_cfg, true);
|
|
|
|
|
channel_config_set_dreq(&out_cfg, pio_get_dreq(state.pio, state.pio_sm, true));
|
|
|
|
|
|
|
|
|
|
pio_sm_set_enabled(state.pio, state.pio_sm, true);
|
|
|
|
|
dma_channel_configure(state.dma_out, &out_cfg, &state.pio->txf[state.pio_sm], cmd, cmd_len, true);
|
|
|
|
|
dma_channel_wait_for_finish_blocking(state.dma_out);
|
|
|
|
|
dma_channel_configure(state.dma_out, &out_cfg, &state.pio->txf[state.pio_sm], buf, len, true);
|
|
|
|
|
dma_channel_wait_for_finish_blocking(state.dma_out);
|
|
|
|
|
|
|
|
|
|
const uint32_t stall = 1u << (PIO_FDEBUG_TXSTALL_LSB + state.pio_sm);
|
|
|
|
|
state.pio->fdebug = stall;
|
|
|
|
|
while (!(state.pio->fdebug & stall)) tight_loop_contents();
|
|
|
|
|
|
|
|
|
|
__compiler_memory_barrier();
|
|
|
|
|
pio_sm_set_consecutive_pindirs(state.pio, state.pio_sm, PIO_SPI_DATA_IO0_PIN, 4, false);
|
|
|
|
|
pio_sm_exec(state.pio, state.pio_sm, pio_encode_mov(pio_pins, pio_null));
|
|
|
|
|
pio_sm_set_enabled(state.pio, state.pio_sm, false);
|
|
|
|
|
}
|