# SPI Usage Instructions

## Overview

The K230 platform contains three SPI controllers:

| SPI Controller | Base Address | Type   | Interrupt Number |
| ---------- | ---------- | ------ | ------- |
| spi0       | 0x91584000 | OSPI   | 146-154 |
| spi1       | 0x91582000 | QSPI 0 | 155-163 |
| spi2       | 0x91583000 | QSPI 1 | 164-172 |

The SPI controller is based on the Synopsys DesignWare APB SSI core

> **Note**: All SPI controllers are `disabled` by default and must be enabled in the device tree before use.
---

## Device Tree Configuration

Enable SPI in the device tree (using spi0 as an example):

```dts
//arch/riscv/boot/dts/canaan/k230-canmv-01studio-lcd.dts
aliases {
    spi0 = &spi0;
};

&iomux {
    ospi_pins: ospi_pins {
        pin_clk_mo_mi{
            pins = K230_IO15,K230_IO16,K230_IO17;
            function = "alt1";
        };
        pin_cs{
            pins = K230_IO14;
            function = "alt1";
            bias-pull-up;
        };
    };
};

&spi0
{
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&ospi_pins>;

    spidev0: spidev@0 {
        compatible = "rohm,dh2228fv";
        reg = <0>;
        spi-max-frequency = <4000000>;
        status = "okay";
    };
};


```

---

## User-space Tool Usage

K230 SDK buildroot provides user-space SPI tools:

- **spi-config**: Query and set SPI configuration
- **spi-pipe**: Data transmission via SPI

```makefile
BR2_PACKAGE_SPI_TOOLS:

  This package contains some simple command line tools to help
  using Linux spidev devices.

  https://github.com/cpb-/spi-tools

  Symbol: BR2_PACKAGE_SPI_TOOLS [=y]
  Type  : bool
  Prompt: spi-tools
    Location:
      -> Target packages
        -> Hardware handling
    Defined at package/spi-tools/Config.in:1
```

Usage Examples

```bash
# Query current configuration
spi-config -d /dev/spidev0.0 -q
# Output: /dev/spidev0.0: mode=0, lsb=0, bits=8, speed=500000

# Set SPI mode to mode 3
spi-config -d /dev/spidev0.0 -m 3


# Read spi nor chip id, specify blocksize as 4 bytes
echo -n -e '\x9F\x00\x00\x00' | spi-pipe -d /dev/spidev0.0 -s 1000000 -b 4 | hexdump -C
```

Test Example

```bash
# Loopback send/receive to test read/write
spi-config -d /dev/spidev0.0 -m 0 -s 10000000
loop=0
while true
do
    dd if=/dev/random of=test_pattern bs=4096 count=1 2>/dev/null
    spi-pipe -d /dev/spidev0.0 -b 8192 < test_pattern > readback
    cmp test_pattern readback
    if [ $? -ne 0 ]
    then
        echo "loop:$loop read error"
        exit -1
    fi
    let loop++
    echo $loop
    # sleep 0.1~
done
```

---

## C Language Example

SPI NOR Flash read/write example: first read the ID, then perform read/write testing;

```c
// 先读取id，后进行读写测试；
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
#include <stdint.h>

#define SPI_DEVICE "/dev/spidev0.0"
#define SPI_FREQ 1000000

// SPI NOR Flash commands
#define CMD_JEDEC_ID      0x9F
#define CMD_READ_STATUS   0x05
#define CMD_WRITE_ENABLE  0x06
#define CMD_PAGE_PROGRAM  0x02
#define CMD_READ_DATA     0x03

// Wait for Flash ready
int spi_nor_wait_ready(int fd, int timeout)
{
    uint8_t tx[] = {CMD_READ_STATUS};
    uint8_t rx[2] = {0};
    int i;

    for (i = 0; i < timeout; i++) {
        struct spi_ioc_transfer tr = {
            .tx_buf = (unsigned long)tx,
            .rx_buf = (unsigned long)rx,
            .len = 2,
            .speed_hz = SPI_FREQ,
            .bits_per_word = 8,
        };

        if (ioctl(fd, SPI_IOC_MESSAGE(1), &tr) < 0) {
            return -1;
        }

        if (!(rx[1] & 0x01)) {  // WIP bit cleared
            return 0;
        }

        usleep(1000);  // 1ms delay
    }

    return -1;  // Timeout
}

int spi_nor_read_jedec_id(int fd, uint8_t *id)
{
    uint8_t tx[] = {CMD_JEDEC_ID, 0x00, 0x00, 0x00};
    uint8_t rx[4] = {0};

    struct spi_ioc_transfer tr = {
        .tx_buf = (unsigned long)tx,
        .rx_buf = (unsigned long)rx,
        .len = 4,
        .speed_hz = SPI_FREQ,
        .bits_per_word = 8,
    };

    if (ioctl(fd, SPI_IOC_MESSAGE(1), &tr) < 0) {
        return -1;
    }

    // ID starts from rx[1] (3 bytes)
    id[0] = rx[1];
    id[1] = rx[2];
    id[2] = rx[3];

    return 0;
}

int spi_nor_read_status(int fd, uint8_t *status)
{
    uint8_t tx[] = {CMD_READ_STATUS};
    uint8_t rx[2] = {0};

    struct spi_ioc_transfer tr = {
        .tx_buf = (unsigned long)tx,
        .rx_buf = (unsigned long)rx,
        .len = 2,
        .speed_hz = SPI_FREQ,
        .bits_per_word = 8,
    };

    if (ioctl(fd, SPI_IOC_MESSAGE(1), &tr) < 0) {
        return -1;
    }

    *status = rx[1];
    return 0;
}

int spi_nor_write_enable(int fd)
{
    uint8_t tx[] = {CMD_WRITE_ENABLE};

    struct spi_ioc_transfer tr = {
        .tx_buf = (unsigned long)tx,
        .rx_buf = 0,
        .len = 1,
        .speed_hz = SPI_FREQ,
        .bits_per_word = 8,
    };

    return ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
}

int spi_nor_page_program(int fd, uint32_t addr, uint8_t *data, size_t len)
{
    uint8_t tx[4 + len];

    tx[0] = CMD_PAGE_PROGRAM;
    tx[1] = (addr >> 16) & 0xFF;
    tx[2] = (addr >> 8) & 0xFF;
    tx[3] = addr & 0xFF;
    memcpy(&tx[4], data, len);

    struct spi_ioc_transfer tr = {
        .tx_buf = (unsigned long)tx,
        .rx_buf = 0,
        .len = 4 + len,
        .speed_hz = SPI_FREQ,
        .bits_per_word = 8,
    };

    return ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
}

int spi_nor_read_data(int fd, uint32_t addr, uint8_t *data, size_t len)
{
    uint8_t tx[4 + len];
    uint8_t rx[4 + len];

    tx[0] = CMD_READ_DATA;
    tx[1] = (addr >> 16) & 0xFF;
    tx[2] = (addr >> 8) & 0xFF;
    tx[3] = addr & 0xFF;

    struct spi_ioc_transfer tr = {
        .tx_buf = (unsigned long)tx,
        .rx_buf = (unsigned long)rx,
        .len = 4 + len,
        .speed_hz = SPI_FREQ,
        .bits_per_word = 8,
    };

    if (ioctl(fd, SPI_IOC_MESSAGE(1), &tr) < 0) {
        return -1;
    }

    memcpy(data, &rx[4], len);
    return 0;
}

int main(int argc, char *argv[])
{
    int fd;
    uint8_t id[3];
    uint8_t status;
    uint8_t test_data[256];
    uint8_t read_data[256];
    int i;

    fd = open(SPI_DEVICE, O_RDWR);
    if (fd < 0) {
        perror("Failed to open SPI device");
        return -1;
    }

    // Configure SPI
    uint8_t mode = SPI_MODE_0;
    uint32_t speed = SPI_FREQ;
    ioctl(fd, SPI_IOC_WR_MODE, &mode);
    ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);

    // Read JEDEC ID
    printf("Reading JEDEC ID...\n");
    if (spi_nor_read_jedec_id(fd, id) == 0) {
        printf("Manufacturer ID: 0x%02X\n", id[0]);
        printf("Memory Type: 0x%02X\n", id[1]);
        printf("Capacity: 0x%02X\n", id[2]);
    }

    // Read status register
    printf("\nReading Status Register...\n");
    if (spi_nor_read_status(fd, &status) == 0) {
        printf("Status: 0x%02X\n", status);
    }

    // Erase and write test
    printf("\nTesting write operation...\n");

    // Generate test data
    for (i = 0; i < 256; i++) {
        test_data[i] = i & 0xFF;
    }

    // Write enable
    spi_nor_wait_ready(fd, 100);
    spi_nor_write_enable(fd);

    // Page program (256 bytes)
    spi_nor_page_program(fd, 0x000000, test_data, 256);
    spi_nor_wait_ready(fd, 1000);

    // Read back to verify
    spi_nor_read_data(fd, 0x000000, read_data, 256);

    printf("\nVerify: %s\n",
           memcmp(test_data, read_data, 256) == 0 ? "PASS" : "FAIL");

    close(fd);
    return 0;
}

```

---

## Python Example

### Using busio Library

```python
import time
import board
import busio

# Initialize hardware SPI bus (auto CS is configured at bottom level,
#    or using default hardware CS pin)
_,sck,mosi,miso = board.pin.spiPorts[0]
spi = busio.SPI(sck,mosi,miso)

print("--- Reading SPI NOR Flash ID (hardware auto CS control) ---")

# Acquire SPI bus lock
spi.try_lock()

# Configure SPI settings (Mode 0, 1MHz clock)
spi.configure(baudrate=1000000, polarity=0, phase=0)

# Prepare buffers
# bytes total: 1 command byte + 3 bytes for receiving ID
tx_buf = bytes([0x9F, 0x00, 0x00, 0x00])
rx_buf = bytearray(4) # length must match tx_buf

# Simultaneous full-duplex read/write (within single CS active cycle)
# Hardware automatically pulls CS low -> sends 4 bytes while receiving 4 bytes -> CS high
spi.write_readinto(tx_buf, rx_buf)

# Release bus lock
spi.unlock()

# Parse and print result
# Byte 0 in rx_buf is dummy data (sent 0x9F), JEDEC ID follows in bytes 1-3
manufacturer_id = rx_buf[1]
memory_type     = rx_buf[2]
capacity_id     = rx_buf[3]

print("-" * 40)
print(f"Raw response data (RAW): {[hex(x) for x in rx_buf]}")
print(f"Manufacturer ID: 0x{manufacturer_id:02X}")
print(f"Memory Type:     0x{memory_type:02X}")
print(f"Capacity ID:     0x{capacity_id:02X}")
print("-" * 40)

```

### Pure Python

```python
import os
import fcntl
import struct
import ctypes

# --- Linux SPI ioctl command constants ---
# If 0x40016B01 reports an error, some kernels require using 0x40046B01 (write mode as 32-bit)
SPI_IOC_WR_MODE          = 0x40016B01
SPI_IOC_WR_MAX_SPEED_HZ  = 0x40046B04
SPI_IOC_MESSAGE_1        = 0x40206B00  # Transfer 1 spi_ioc_transfer structure

def read_flash_id(bus=0, device=0):
    spi_device = f"/dev/spidev{bus}.{device}"

    if not os.path.exists(spi_device):
        print(f"Error: Device file not found {spi_device}")
        return

    fd = os.open(spi_device, os.O_RDWR)

    try:
        # Set SPI mode (Mode 0)
        try:
            fcntl.ioctl(fd, SPI_IOC_WR_MODE, struct.pack('B', 0))
        except OSError:
            # Try writing Mode in 4-byte unsigned integer format (required by some newer kernels)
            fcntl.ioctl(fd, 0x40046B01, struct.pack('I', 0))

        # Set SPI frequency (100 kHz)
        speed_hz = 100000
        fcntl.ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, struct.pack('I', speed_hz))

        print(f"SPI initialization successful: Mode=0, Speed={speed_hz/1000}kHz")

        # Prepare send and receive buffers
        tx_data = bytes([0x9F, 0x00, 0x00, 0x00])
        # Create a writable receive buffer
        rx_buf = ctypes.create_string_buffer(4)

        # Get the 64-bit memory address of the buffer
        tx_addr = ctypes.addressof(ctypes.create_string_buffer(tx_data))
        rx_addr = ctypes.addressof(rx_buf)

        # Pack strictly according to the spi_ioc_transfer structure of Linux x86_64 / RISCV64:
        # __u64 tx_buf        (Q)
        # __u64 rx_buf        (Q)
        # __u32 len           (I)
        # __u32 speed_hz      (I)
        # __u16 delay_usecs   (H)
        # __u8  bits_per_word (B)
        # __u8  cs_change     (B)
        # __u8  tx_nbits      (B)
        # __u8  rx_nbits      (B)
        # __u16 pad           (H)
        spi_ioc_transfer = struct.pack(
            "=QQIIHBBBBH",
            tx_addr,    # tx_buf
            rx_addr,    # rx_buf
            4,          # len
            speed_hz,   # speed_hz
            0,          # delay_usecs
            8,          # bits_per_word (usually a multiple of 8 bits)
            0,          # cs_change
            0,          # tx_nbits
            0,          # rx_nbits
            0           # pad
        )

        # Execute transfer
        fcntl.ioctl(fd, SPI_IOC_MESSAGE_1, spi_ioc_transfer)

        # Parse return result
        result = [b for b in rx_buf.raw]
        print("-" * 40)
        print(f"Raw response data: {[hex(x) for x in result]}")
        print(f"Manufacturer ID: 0x{result[1]:02X}")
        print(f"Memory Type:    0x{result[2]:02X}")
        print(f"Capacity ID:    0x{result[3]:02X}")
        print("-" * 40)

    except OSError as e:
        print(f"Operation failed: {e}")
    finally:
        os.close(fd)

if __name__ == "__main__":
    # If the device node is /dev/spidev0.0, pass in (0, 0)
    read_flash_id(0, 0)

```
