CM4_PSPI使用参考


Revision History

Revision No.
Description
Date
1.0 Initial release 06/04/2024

1. 概述

1.1. PSPI

PSPI支持半双工spi通讯协议,包含CS、CK、MOSI、MISO引脚。spi通讯,片选信号CS、时钟CK、MOSI都由master控制,MISO由slave device控制。

1.2. 特性支持

BUS PM-PSPI
BANK ADDR 0x19
FIFO Y
DMA Y
Full-Duplex N
Half-Duplex Y
Interrupt Y
Max Length 4096(byte)
CS Change Y
CPOL Y
CPHA Y
bits_per_word Y(dma only 8bits)
CS NUM 2
CS Polarity Y
Speed 23437Hz - 50MHz
3-wire Y
4-wire Y

1.3. speed的计算方法:

speed = (source clock rate) / (2 * (divisor + 1))

1.4. source clock list

source clock rate divisor range
12MHz 0 - 0xff
24MHz 0 - 0xff
50MHz 0 - 0xff
100MHz 0 - 0xff

1.5. 引脚说明

PSPI包含CS、CK、DI、DO四根引脚,当作为4线使用时,DI表示data input,是接收数据的引脚,DO表示data output,是发送数据的引脚。当作为3线使用时,使用CS、CK、DI三根引脚,数据的收发都在DI完成。


2. 使用介绍

2.1. RTOS config配置

RTOS下使用PSPI,需要开启CONFIG_PSPI_SUPPORT驱动编译选项

# Feature_Name = [DRV] PSPI driver support
# Description = PSPI driver support
# Option_Selection = TRUE, FALSE
CONFIG_PSPI_SUPPORT = TRUE

2.2. 驱动路径

sc/driver/sysdriver/pspi/drv/src/drv_pspi.c
sc/driver/sysdriver/pspi/drv/pub/drv_pspi.h
sc/driver/sysdriver/pspi/hal/iford/hal_pspi.c
sc/driver/sysdriver/pspi/hal/iford/hal_pspi_cfg.h
sc/driver/sysdriver/pspi/hal/iford/hal_pspi.h

2.3 SYSDESC定义

<pspi0>
    [reg_u32] 0x40003200;
    [interrupts_u32] INT_IRQ_PSPI02HOST;
    [dma_u8] 1;
    [status_u8] 1;
属性 说明 备注
<pspi0> 用于和驱动匹配 禁止修改
reg_u32 IO_address Address_size 禁止修改
interrupts_u32 中断号 禁止修改
dma_u8 urdma mode使能 1 :使能   
0 :禁用
status_u8 节点使能,用于是否使能该pspi0 1: 使能
0: 不使能

2.4 引脚复用

即PADMUX,用于将PSPI信号连接到某组具体的引脚上。设定修改在proj/sc/driver/sysdriver/padmux/hal/iford/src/*-padmux.c当中,以便统一管理。第一列为引脚号定义,第二列为引脚复用的padmod编号,第三列puse为功能定义。需要注意的是,第一列的引脚号不能重复;对应的第二列的padmod编号与引脚号要匹配的上。

proj/sc/driver/sysdriver/padmux/hal/iford/src/iford-ssc029a-s01a-padmux.c

pad_info_t schematic[] =
{
    // for example
    // {PAD_I2C1_SCL,    PINMUX_FOR_I2C1_MODE_1,    MDRV_PUSE_I2C1_SCL},
    // {PAD_I2C1_SDA,    PINMUX_FOR_I2C1_MODE_1,    MDRV_PUSE_I2C1_SDA},
    // for pspi0
    {PAD_PM_PSPI0_DI,   PINMUX_FOR_PSPI0_MODE_1,    MDRV_PUSE_PSPI0_DI},
    {PAD_PM_PSPI0_DO,   PINMUX_FOR_PSPI0_MODE_1,    MDRV_PUSE_PSPI0_DO},
    {PAD_PM_PSPI0_CK,   PINMUX_FOR_PSPI0_MODE_1,    MDRV_PUSE_PSPI0_CK},
    {PAD_PM_PSPI0_CZ,   PINMUX_FOR_PSPI0_MODE_1,    MDRV_PUSE_PSPI0_CZ},
};

int size_of_schematic = sizeof(schematic);

3. API 说明

3.1. 设备获取与关闭

3.1.1. 设备获取

  • 函数声明

    pspi_handle *drv_pspi_open(u8 bus);

  • 功能说明

    指定pspi设备默认初始化并获得handle pointer。

  • 参数

    参数 说明
    bus pspi bus number
  • 返回值

    返回值 说明
    pspi_handle * handle 指针

3.1.2. 设备关闭

  • 函数声明

    void drv_pspi_close(pspi_handle *handle);

  • 功能说明

    释放被open获取的pspi。

  • 参数

    参数 说明
    handle 被open获取的pspi handle指针
  • 返回值

    void

3.2. 设备设定

3.2.1. 设备设定

  • 函数声明

    s32 drv_pspi_ioctrl(pspi_handle *handle, u32 cmd, void *arg);

  • 功能说明

    对设备通讯mode、cs select、speed、bits_per_word等做设定;或者注册async回调函数。

  • 参数

    参数 说明
    handle pspi handle指针
    cmd 选择操作类别:
    PSPI_CMD_SETUP:通讯设定
    PSPI_CMD_ASYNC_SET:注册异步回调
    arg 通讯设定结构体指针,或者回调函数指针

通讯设定结构体声明:

struct pspi_setup
{
    u32 speed;
    u32 mode;
#define PSPI_CPHA       0x01    /* clock phase */
#define PSPI_CPOL       0x02    /* clock polarity */
#define PSPI_CS_HIGH    0x04    /* chipselect active high? */
#define PSPI_LSB_FIRST  0x08    /* per-word bits-on-wire */
#define PSPI_3WIRE      0x10    /* SI/SO signals shared */
#define PSPI_SLAVE      0x20
    u8  bus_select;
    u8  chip_select;
    u8  bits_per_word;                               /* The number of bits in an SPI transmission*/
    u8  data_lane;
    u16 delay_cycle;                             /* cs is inactive*/
    u16 wait_cycle;                              /* cs is active  */
};

回调函数声明:

void (*pspi_async_callback)(void *);
  • 返回值

    返回值 说明
    s32 设定结果:
    -1:handle指针为空
    0:设定成功

3.3. 通讯

3.3.1. 通讯调用

  • 函数声明

    s32 drv_pspi_transfer(pspi_handle *handle, struct pspi_transfer *transfer, u8 msg_num, s32 timeout_ms);

  • 功能说明

    发起通讯。

    由于pspi只支持半双工通讯,所以在每个struct pspi_transfer结构体当中,tx_buf和rx_buf只能有一个为有效值,另一个必须为0,否则将错误返回;cs_change表示当前结构体发起的读或写通讯后,cs是否回到idle电平,或者继续保持active电平。

  • 参数

    参数 说明
    handle pspi handle 指针
    transfer 通讯内容结构体指针,当一次进行多次传输时,为结构体数组首地址
    msg_num 通讯结构体个数,表示进行多少次读写操作
    timeout_ms 超时等待,暂未支持

通讯内容结构体声明:

struct pspi_transfer
{
    u8 *tx_buf; // 发送数据buffer地址
    u8 *rx_buf; // 接收数据buffer地址
    u32 length; // 通讯字节数
    u8  cs_change; // 0: 完成后cs保持active电平; 1:完成后cs回复idle电平
};
  • 返回值

    返回值 说明
    s32 通讯结果:
    0:通讯成功
    非0:通讯失败

3.4. 参考用例

static int pspi_flash_demo(CLI_t * cli, char * p)
{
    pspi_handle *handle;
    struct pspi_setup setup = {0};
    struct pspi_transfer tfr[2] = {0};
    int ret = 0;
    int i;

    unsigned char pspi_wr_buf[MAX_BUF_SIZE] = {0};
    unsigned char pspi_rd_buf[MAX_BUF_SIZE] = {0};

    // pspi_controller setup
    setup.chip_select = 0;
    setup.bits_per_word = 8;
    setup.delay_cycle = 0;
    setup.wait_cycle = 0;
    setup.speed = 6000000;
    setup.mode = 0;
    // setup.mode |= PSPI_3WIRE;

    handle = drv_pspi_open(0);
    if (!handle)
    {
        CamOsPrintf("drv_pspi_open fail\n");
        return eCLI_PARSE_ERROR;
    }
    handle->slave = 0;

    drv_pspi_ioctrl(handle, PSPI_CMD_SETUP, (void *)&setup);

    // write enable
    pspi_wr_buf[0] = 0x06;
    tfr[0].cs_change = 1;
    tfr[0].rx_buf    = NULL;
    tfr[0].tx_buf    = pspi_wr_buf;
    tfr[0].length    = 0x1;
    ret = drv_pspi_transfer(handle, &tfr[0], 1, 0);

    // unlock
    pspi_wr_buf[0] = 0x1F;
    pspi_wr_buf[1] = 0xA0;
    pspi_wr_buf[2] = 0x00;
    tfr[0].cs_change = 1;
    tfr[0].rx_buf    = NULL;
    tfr[0].tx_buf    = pspi_wr_buf;
    tfr[0].length    = 0x3;
    ret = drv_pspi_transfer(handle, &tfr[0], 1, 0);

    pspi_wr_buf[0] = 0x0F;
    pspi_wr_buf[1] = 0xA0;
    tfr[0].cs_change = 0;
    tfr[0].rx_buf    = NULL;
    tfr[0].tx_buf    = pspi_wr_buf;
    tfr[0].length    = 0x2;
    tfr[1].cs_change = 1;
    tfr[1].rx_buf    = pspi_rd_buf;
    tfr[1].tx_buf    = NULL;
    tfr[1].length    = 0x1;
    //ret = drv_pspi_transfer(handle, &tfr[0], 2, 0);

    // write enable
    pspi_wr_buf[0] = 0x06;
    tfr[0].cs_change = 1;
    tfr[0].rx_buf    = NULL;
    tfr[0].tx_buf    = pspi_wr_buf;
    tfr[0].length    = 0x1;
    ret = drv_pspi_transfer(handle, &tfr[0], 1, 0);

    // erase block 0
    pspi_wr_buf[0] = 0xd8;
    pspi_wr_buf[1] = 0x00;
    pspi_wr_buf[2] = 0x00;
    pspi_wr_buf[3] = 0x00;
    tfr[0].cs_change = 1;
    tfr[0].rx_buf    = NULL;
    tfr[0].tx_buf    = pspi_wr_buf;
    tfr[0].length    = 0x4;
    ret = drv_pspi_transfer(handle, &tfr[0], 1, 0);

    // wait oip
    CamOsUsDelay(20000);

    // read page to cache
    pspi_wr_buf[0] = 0x13;
    pspi_wr_buf[1] = 0x00;
    pspi_wr_buf[2] = 0x00;
    pspi_wr_buf[3] = 0x00;
    tfr[0].cs_change = 1;
    tfr[0].rx_buf    = NULL;
    tfr[0].tx_buf    = pspi_wr_buf;
    tfr[0].length    = 0x4;
    ret = drv_pspi_transfer(handle, &tfr[0], 1, 0);

    // read data from cache
    pspi_wr_buf[0] = 0x03;
    pspi_wr_buf[1] = 0x00;
    pspi_wr_buf[2] = 0x00;
    pspi_wr_buf[3] = 0x00;
    tfr[0].cs_change = 0;
    tfr[0].rx_buf    = NULL;
    tfr[0].tx_buf    = pspi_wr_buf;
    tfr[0].length    = 0x4;
    tfr[1].cs_change = 1;
    tfr[1].rx_buf    = pspi_rd_buf;
    tfr[1].tx_buf    = NULL;
    tfr[1].length    = MAX_BUF_SIZE;
    ret = drv_pspi_transfer(handle, &tfr[0], 2, 0);

    if (ret)
    {
        CamOsPrintf("pspi_flash fail, %d .\n", ret);
    }

    for (i = 0; i < MAX_BUF_SIZE; i++)
    {
        if (pspi_rd_buf[i] != 0xFF)
        {
            CamOsPrintf("erase fail\n");
            CamOsHexdump((char *)pspi_rd_buf, MAX_BUF_SIZE);
            break;
        }
    }

    // write enable
    pspi_wr_buf[0] = 0x06;
    tfr[0].cs_change = 1;
    tfr[0].rx_buf    = NULL;
    tfr[0].tx_buf    = pspi_wr_buf;
    tfr[0].length    = 0x1;
    ret = drv_pspi_transfer(handle, &tfr[0], 1, 0);

    // write data to cache
    pspi_wr_buf[0] = 0x02;
    pspi_wr_buf[1] = 0x00;
    pspi_wr_buf[2] = 0x00;
    tfr[0].cs_change = 0;
    tfr[0].rx_buf    = NULL;
    tfr[0].tx_buf    = pspi_wr_buf;
    tfr[0].length    = 0x3;
    ret = drv_pspi_transfer(handle, &tfr[0], 1, 0);

    for (i = 0; i < MAX_BUF_SIZE; i++)
    {
        pspi_wr_buf[i] = i;
    }
    tfr[0].cs_change = 1;
    tfr[0].rx_buf    = NULL;
    tfr[0].tx_buf    = pspi_wr_buf;
    tfr[0].length    = MAX_BUF_SIZE;
    ret = drv_pspi_transfer(handle, &tfr[0], 1, 0);

    // program execute
    pspi_wr_buf[0] = 0x10;
    pspi_wr_buf[1] = 0x00;
    pspi_wr_buf[2] = 0x00;
    pspi_wr_buf[3] = 0x00;
    tfr[0].cs_change = 1;
    tfr[0].rx_buf    = NULL;
    tfr[0].tx_buf    = pspi_wr_buf;
    tfr[0].length    = 0x4;
    ret = drv_pspi_transfer(handle, &tfr[0], 1, 0);

    // wait oip
    CamOsUsDelay(10000);

    // read page to cache
    pspi_wr_buf[0] = 0x13;
    pspi_wr_buf[1] = 0x00;
    pspi_wr_buf[2] = 0x00;
    pspi_wr_buf[3] = 0x00;
    tfr[0].cs_change = 1;
    tfr[0].rx_buf    = NULL;
    tfr[0].tx_buf    = pspi_wr_buf;
    tfr[0].length    = 0x4;
    ret = drv_pspi_transfer(handle, &tfr[0], 1, 0);

    // wait oip
    CamOsUsDelay(1000);

    // read data from cache
    pspi_wr_buf[0] = 0x03;
    pspi_wr_buf[1] = 0x00;
    pspi_wr_buf[2] = 0x00;
    pspi_wr_buf[3] = 0x00;
    tfr[0].cs_change = 0;
    tfr[0].rx_buf    = NULL;
    tfr[0].tx_buf    = pspi_wr_buf;
    tfr[0].length    = 0x4;
    tfr[1].cs_change = 1;
    tfr[1].rx_buf    = pspi_rd_buf;
    tfr[1].tx_buf    = NULL;
    tfr[1].length    = MAX_BUF_SIZE;
    ret = drv_pspi_transfer(handle, &tfr[0], 2, 0);

    if (ret)
    {
        CamOsPrintf("pspi_flash fail, %d .\n", ret);
    }

    for (i = 0; i < MAX_BUF_SIZE; i++)
    {
        if (pspi_rd_buf[i] != i)
        {
            CamOsPrintf("data check fail\n");
            CamOsHexdump((char *)pspi_rd_buf, MAX_BUF_SIZE);
            break;
        }
    }

    return eCLI_PARSE_OK;
}

SS_RTOS_CLI_CMD(pspi_flash, "pspi flash demo", " pspi_flash\n", pspi_flash_demo);