SPI 总线驱动

更新时间:
2024-01-15
下载文档

SPI 总线驱动

本章将介绍 MS-RTOS SPI 总线驱动开发及测试。

SPI 是串行外设接口(Serial Peripheral Interface)的缩写,是 Motorola 公司推出的一种同步串行接口技术,是一种高速全双工的通信总线。它被广泛地使用在 EEPROM、FLASH、实时时钟、AD 转换器、数字信号处理器、数字信号解码器和 LCD 等设备与 MCU 间。它以主从方式工作,通常有一个主设备和一个或多个从设备。SPI 在芯片的管脚上只占用四根线,分别是 MOSI(数据输出),MISO(数据输入),SCLK(时钟),CS(片选)。

1. SPI 基础知识

1.1 物理层

SPI 通讯设备之间的常用连接方式:

spi_interface

SPI 通讯使用 3 条总线及片选线,3 条总线分别为 SCK、MOSI、MISO,片选线为 SSn,它们的作用介绍如下:

  • SSn(Slave Select):从设备选择信号线,常称为片选信号线,也称为 NSS、CS,以下用 NSS 表示。SPI 通讯以 NSS 线置低电平为开始信号,以 NSS 线被拉高作为结束信号。
  • SCK(Serial Clock):时钟信号线,用于通讯数据同步。它由通讯主机产生,决定了通讯的速率,不同的设备支持的最高时钟频率不一样,如 STM32 的 SPI 时钟频率最大为 fpclk/2,两个设备之间通讯时,通讯速率受限于低速设备。
  • MOSI(Master Output,Slave Input):主设备输出/从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入主机发送的数据,即这条线上数据的方向为主机到从机。
  • MISO(Master Input,Slave Output):主设备输入/从设备输出引脚。主机从这条信号线读入数据,从机的数据由这条信号线输出到主机,即在这条线上数据的方向为从机到主机。

1.2 协议层

SPI 协议定义了通讯的起始和停止信号、数据有效性、时钟同步等环节。

(1)SPI 基本通讯过程

NSS、SCK、MOSI 信号都由主机控制产生,而 MISO 的信号由从机产生,主机通过该信号线读取从机的数据。MOSI 与 MISO 的信号只在 NSS 为低电平的时候才有效,在 SCK 的每个时钟周期 MOSI 和 MISO 传输一位数据。MOSI 及 MISO 的数据在 SCK 的上升沿期间变化输出,在 SCK 的下降沿时被采样。即在 SCK 的下降沿时刻,MOSI 及 MISO 的数据有效,高电平时表示数据 1,为低电平时表示数据 0。

spi_timing

(2)SPI 信号和响应

SPI 一共有四种通讯模式,它们的主要区别是总线空闲时 SCK 的时钟状态以及数据采样时刻。为方便说明,在此引入时钟极性 CPOL 和时钟相位 CPHA 的概念。

SPI 模式CPOL(时钟极性)CPHA(时钟相位)空闲时 SCK 时钟数据采样时刻
000低电平奇数边沿
101低电平偶数边沿
210高电平奇数边沿
311高电平偶数边沿

时钟极性 CPOL 是指 SPI 通讯设备处于空闲状态时,SCK 信号线的电平信号(即 SPI 通讯开始前、 NSS 线为高电平时 SCK 的状态)。CPOL = 0 时, SCK 在空闲状态时为低电平,CPOL = 1 时,则相反。

时钟相位 CPHA 是指数据的采样的时刻,当 CPHA = 0 时,MOSI 或 MISO 数据线上的信号将会在 SCK 时钟线的奇数边沿被采样。当 CPHA = 1 时,数据线在 SCK 的偶数边沿采样。

spi_communication

1.3 STM32 的 SPI 架构

关于 STM32 SPI 的使用,可以自行在网上搜索。

spi_arch

2. SPI 驱动框架

2.1 模拟 SPI

MS-RTOS 已经实现了通用的 GPIO 模拟 SPI 的库。在构建 MS-RTOS 的 SDK 时,勾选 libmsdriver;或者自己手动从 GitHub 上下载最新的 libmsdriver,并添加到 SDK 中。

(1)相关数据结构

在基于 libmsdriver 来开发 GPIO 模拟的 SPI 驱动时,只需要实现(填充)好 ms_spi_bitbang_io_t ,然后调用 ms_spi_bitbang_bus_dev_create 即可。在实际开发时,需要包含头文件 "spi/ms_drv_spi_bitdang.h" 并添加链接选项 -lmsdriver

/**
 * @brief Functions for setting and getting the state of the SPI lines.
 *
 * These need to be implemented by the user of this library.
 */
typedef struct {
    /*
     * Delay ns
     */
    void(*delay)(ms_ptr_t io_ctx, ms_uint32_t ns);

    /*
     * Return the state of the MISO line (zero/non-zero value)
     */
    ms_uint8_t (*get_miso)(ms_ptr_t io_ctx);

    /*
     * Set the state of the MOSI line (zero/non-zero value)
     */
    void (*set_mosi)(ms_ptr_t io_ctx, ms_uint8_t state);

    /*
     * Set the state of the SCK line (zero/non-zero value)
     */
    void (*set_sck)(ms_ptr_t io_ctx, ms_uint8_t state);

} const ms_spi_bitbang_io_t;

ms_err_t ms_spi_bitbang_bus_dev_create(const char *bus_name, const char *path, 
                                       ms_spi_bitbang_io_t *io, ms_ptr_t io_ctx);

(2)驱动示例

#include "ms_config.h"
#include "ms_rtos.h"
#include "includes.h"
#include "spi/ms_drv_spi_bitdang.h"

/*
 * Set the state of the SCK line (zero/non-zero value)
 */
static void __spi_io_set_sck(ms_ptr_t io_ctx, ms_uint8_t state)
{
    if (state != 0) {
        gpio_bit_set(SPI_SCK_GPIO_PORT, SPI_SCK_GPIO_PIN);
    } else {
        gpio_bit_reset(SPI_SCK_GPIO_PORT, SPI_SCK_GPIO_PIN);
    }
}

/*
 * Return the state of the MISO line (zero/non-zero value)
 */
static ms_uint8_t __spi_io_get_miso(ms_ptr_t io_ctx)
{
    if (gpio_input_bit_get(SPI_MISO_GPIO_PORT, SPI_MISO_GPIO_PIN) == RESET) {
        return 0;
    } else {
        return 1;
    }
}

/*
 * Set the state of the MOSI line (zero/non-zero value)
 */
static void __spi_io_set_mosi(ms_ptr_t io_ctx, ms_uint8_t state)
{
    if (state != 0) {
        gpio_bit_set(SPI_MOSI_GPIO_PORT, SPI_MOSI_GPIO_PIN);
    } else {
        gpio_bit_reset(SPI_MOSI_GPIO_PORT, SPI_MOSI_GPIO_PIN);
    }
}

/*
 * Delay ns
 */
static void __spi_io_delay(ms_ptr_t io_ctx, ms_uint32_t ns)
{
    bsp_delay_us((ns + (1000 - 1)) / 1000);
}

/*
 * ms_spi_bitbang_io_t
 */
static ms_spi_bitbang_io_t __spi_io = {
    .set_sck  = __spi_io_set_sck,
    .set_mosi = __spi_io_set_mosi,
    .get_miso = __spi_io_get_miso,
    .delay    = __spi_io_delay,
};

/*
 * SPI GPIO initialization function
 */
void bsp_spi_emulator_init(const char *bus_name, const char *path)
{
    /* Enable all GPIO clock */
    SPI_ALL_GPIO_EN();

    /* Configure SCK (PB3) pin as push-pull output with pull-up */
    gpio_mode_set(SPI_SCK_GPIO_PORT, GPIO_MODE_OUTPUT, 
                  GPIO_PUPD_NONE, SPI_SCK_GPIO_PIN);
    gpio_output_options_set(SPI_SCK_GPIO_PORT, GPIO_OTYPE_PP, 
                            GPIO_OSPEED_50MHZ, SPI_SCK_GPIO_PIN);
    gpio_bit_set(SPI_SCK_GPIO_PORT, SPI_SCK_GPIO_PIN);

    /* Configure MISO (PB4) pin as push-pull output with pull-up */
    gpio_mode_set(SPI_MISO_GPIO_PORT, GPIO_MODE_OUTPUT, 
                  GPIO_PUPD_NONE, SPI_MISO_GPIO_PIN);
    gpio_output_options_set(SPI_MISO_GPIO_PORT, GPIO_OTYPE_PP, 
                            GPIO_OSPEED_50MHZ, SPI_MISO_GPIO_PIN);
    gpio_bit_set(SPI_MISO_GPIO_PORT, SPI_MISO_GPIO_PIN);

    /* Configure MOSI (PB5) pin as push-pull output with pull-up */
    gpio_mode_set(SPI_MOSI_GPIO_PORT, GPIO_MODE_OUTPUT, 
                  GPIO_PUPD_NONE, SPI_MOSI_GPIO_PIN);
    gpio_output_options_set(SPI_MOSI_GPIO_PORT, GPIO_OTYPE_PP, 
                            GPIO_OSPEED_50MHZ, SPI_MOSI_GPIO_PIN);
    gpio_bit_set(SPI_MOSI_GPIO_PORT, SPI_MOSI_GPIO_PIN);
	
    ms_spi_bitbang_bus_dev_create(bus_name, path, &__spi_io, MS_NULL);
}

2.2 硬件 SPI

MS-RTOS 为 SPI 总线驱动封装了一层简单易用的驱动框架。同时,MS-RTOS 也已经支持了部分系列的 NorFlash 驱动,这部分驱动放到了 libmsdriver 中。

(1)相关数据结构

MS-RTOS SPI 驱动框架相关的数据结构和接口可以在头文件 sdk/src/driver/ms_drv_spi.h 中找到。

  • ms_spi_bus_t

    该结构体用于描述一条 SPI 总线,并包含操作总线控制器的接口。

    /*
     * Chip select function
     */
    typedef ms_err_t (*ms_spi_cs_func_t)(ms_bool_t select);
    
    /*
     * ms_spi_bus_ops_t
     */
    typedef struct {
        ms_ssize_t  (*trans)(ms_ptr_t bus_ctx, ms_spi_cs_func_t cs, const ms_spi_msg_t *msg, ms_size_t n_msg);
        int         (*ioctl)(ms_ptr_t bus_ctx, int cmd, ms_ptr_t arg);
    } const ms_spi_bus_ops_t;
    
    /*
     * ms_spi_bus_t
     */
    typedef struct {
        ms_io_name_node_t   nnode;
        ms_spi_bus_ops_t   *ops;
        ms_handle_t         lockid;
        ms_list_head_t      dev_list;
        ms_ptr_t            ctx;
    } ms_spi_bus_t;
    
  • ms_spi_device_t

    该结构体用于描述一个 SPI 总线上的设备。通常实际 SPI 设备需要包含一个该类型的成员,在进行 SPI 通信时,需要将 ms_spi_device_t attach 到一条 ms_spi_bus_t ,然后调用相应的接口进行通信,如:ms_spi_device_trans

    /*
     * ms_spi_device_t
     */
    typedef struct {
        ms_io_name_node_t   nnode;
        ms_spi_bus_t       *bus;
        ms_ptr_t            ctx;
        ms_spi_cs_func_t    cs;
    } ms_spi_device_t;
    

    SPI 设备可以使用 API 接口包括:

    接口说明
    ms_spi_device_attach将 ms_spi_device_t 附加/绑定到一条 SPI Bus 上(bus_name)
    ms_spi_device_detach取消 ms_spi_device_t 和 SPI Bus 的绑定
    ms_spi_device_trans传输消息
    ms_spi_device_ioctl通过 ioctl 发送总线控制命令
    ms_spi_device_lock_bus锁住 SPI Bus(非硬件上的锁定)
    ms_spi_device_unlock_bus解锁 SPI Bus(非硬件上的解锁)

(2)SPI 框架

下图主要描绘了 SPI 总线驱动、SPI 设备驱动和应用层操作 SPI 总线的过程。

spi_bus_arch

上图由上到下分别为:应用层、IO 驱动层、子系统框架层、驱动适配层。不一定所有类型的驱动都有驱动框架。在注册 SPI 总线驱动时,ms_spi_bus_t 将作为 SPI 总线控制器设备的 privinfo_t 的一员;在应用程序操作 SPI 总线时,将调用到 ms_spi_bus_drv_ops ,实际上将调用 privinfo_tms_spi_bus_t 的操作函数;在注册 SPI 设备驱动时,需要指定 ms_nor_port_t (包含总线信息和挂载点信息),在创建 nor_dev 时将绑定 spi_dev 到对应的 spi_bus , 文件系统的读写操作将通过 SPI 来完成,而使用 spi_dev 就可以调用到 SPI 相关的 API。

(3)驱动示例

SPI 驱动示例,仅作为参考。

#define __MS_IO
#include "ms_config.h"
#include "ms_rtos.h"
#include "ms_io_core.h"
#include <string.h>
#include "includes.h"

/*
 * Private info
 */
typedef struct {
    ms_uint8_t      channel;
    ms_spi_param_t  param;

    ms_handle_t     rx_complete_sem;
    char            rx_sem_name[__SPI_MAX_NAME_LEN];
    ms_handle_t     tx_complete_sem;
    char            tx_sem_name[__SPI_MAX_NAME_LEN];
} privinfo_t;

/*
 * @brief  transmit and receive
 */
static ms_ssize_t __sccb9001cxx_spi_bus_trans(ms_ptr_t bus_ctx, ms_spi_cs_func_t cs,
                                              const ms_spi_msg_t *msg, ms_size_t n_msg)
{
    int         i;
    privinfo_t *priv = bus_ctx;
    ms_err_t    err;

    for (i = 0; i < n_msg; i++, msg++) {

        /*
         * Fix 1 line TRANSMODE to receive data or transmit data
         */
        if (priv->param.direction == MS_SPI_DIRECTION_1LINE) {
            if (!!(msg->flags & MS_SPI_M_WRITE)) {
                priv->native_param.trans_mode = SPI_TRANSMODE_BDTRANSMIT;
            } else {
                priv->native_param.trans_mode = SPI_TRANSMODE_BDRECEIVE;
            }
            __sccb9001cxx_spi_bus_configure(priv, &priv->native_param);
        }

        /*
         * Assert CS before transfer
         */
        if ((msg->flags & MS_SPI_M_BEGIN) && (priv->param.nss == MS_SPI_NSS_SOFT)) {
            if (cs) {
                cs(MS_TRUE);
            }
        }

        /*
         * SPI transmit data with dma or polling
         */
        if (priv->hw_dma_en) {
            err = __sccb9001cxx_spi_transmit_receive_dma(priv, msg, MS_TIMEOUT_FOREVER);
        } else {
            err = __sccb9001cxx_spi_transmit_receive(priv, msg, MS_TIMEOUT_FOREVER);
        }

        /*
         * Deassert CS after transfer
         */
        if ((msg->flags & MS_SPI_M_END) && (priv->param.nss == MS_SPI_NSS_SOFT)) {
            if (cs) {
                cs(MS_FALSE);
            }
        }

        if (err != MS_ERR_NONE) {
            break;
        }
    }

    return  i;
}

/*
 * __sccb9001cxx_spi_bus_ioctl
 */
static int __sccb9001cxx_spi_bus_ioctl(ms_ptr_t bus_ctx, int cmd, ms_ptr_t arg)
{
    privinfo_t *priv = bus_ctx;
    ms_err_t    err;
    int         ret;

    switch (cmd) {
    case MS_SPI_CMD_GET_PARAM:
        if (ms_access_ok(arg, sizeof(ms_spi_param_t), MS_ACCESS_W)) {
            *(ms_spi_param_t *)arg = priv->param;
            ret = 0;
        } else {
            ms_thread_set_errno(EFAULT);
            ret = -1;
        }
        break;

    case MS_SPI_CMD_SET_PARAM:
        if (ms_access_ok(arg, sizeof(ms_spi_param_t), MS_ACCESS_R)) {
            priv->param = *(ms_spi_param_t *)arg;

            err = __sccb9001cxx_spi_bus_param_convert(&priv->param, &priv->native_param);
            if (err == MS_ERR_NONE) {
                __sccb9001cxx_spi_bus_configure(priv, &priv->native_param);
                ret = 0;

            } else {
                ms_thread_set_errno(EINVAL);
                ret = -1;
            }

        } else {
            ms_thread_set_errno(EFAULT);
            ret = -1;
        }
        break;

    default:
        ms_thread_set_errno(EINVAL);
        ret = -1;
        break;
    }

    return ret;
}

static ms_spi_bus_ops_t sccb9001cxx_spi_bus_ops = {
        .trans = __sccb9001cxx_spi_bus_trans,
        .ioctl = __sccb9001cxx_spi_bus_ioctl,
};

static privinfo_t spi_bus_privinfo[__SPI_MAX_CHANNEL_NUM] = {
    {
            .channel = 0,
            .base    = SPI0,
            .irq     = SPI0_IRQn,
    },
};

static ms_spi_bus_t spi_bus_info[__SPI_MAX_CHANNEL_NUM] = {
    {
        .nnode = {
            .name = "spi0",
        },
        .ops = &sccb9001cxx_spi_bus_ops,
        .ctx = &spi_bus_privinfo[0],
    },
};

/*
 * Create SPI device file
 */
ms_err_t sccb9001cxx_spi_bus_dev_create(const char *path, ms_uint32_t channel)
{
    bsp_dma_logic_id_t      tx_dma_id;
    bsp_dma_logic_id_t      rx_dma_id;
    sccb9001_spi_hw_info_t  hw_info;
    ms_spi_bus_t           *spi_bus;
    privinfo_t             *priv;
    ms_err_t                err;

    if (path == MS_NULL || channel >= __SPI_MAX_CHANNEL_NUM) {
        return MS_ERR;
    }

    spi_bus       = &spi_bus_info[channel];
    priv          = spi_bus->ctx;
    priv->channel = channel;

    sprintf(priv->rx_sem_name, "spi%d-rxsem", channel);
    if (ms_semb_create(priv->rx_sem_name, MS_FALSE, 
                       MS_WAIT_TYPE_PRIO, &priv->rx_complete_sem) != MS_ERR_NONE) {
        goto abort_exit;
    }

    sprintf(priv->tx_sem_name, "spi%d-txsem", channel);
    if (ms_semb_create(priv->tx_sem_name, MS_FALSE, 
                       MS_WAIT_TYPE_PRIO, &priv->tx_complete_sem) != MS_ERR_NONE) {
        goto abort_exit;
    }

    err = bsp_spi_low_level_init(channel, &hw_info);
    if (err == MS_ERR_NONE) {
        priv->base           = hw_info.spi_base;
        priv->irq            = hw_info.spi_irq;
        priv->hw_cs_en       = hw_info.hw_cs_en;
        priv->hw_dma_en      = hw_info.hw_dma_en;
        priv->tx_dma_base    = hw_info.tx_dma_base;
        priv->tx_dma_channel = hw_info.tx_dma_channel;
        priv->tx_dma_subperi = hw_info.tx_dma_subperi;
        priv->rx_dma_base    = hw_info.rx_dma_base;
        priv->rx_dma_channel = hw_info.rx_dma_channel;
        priv->rx_dma_subperi = hw_info.rx_dma_subperi;

        if (hw_info.hw_dma_en) {
            err = __SPI_TX_DMA_ID(&hw_info, &tx_dma_id);
            if (err == MS_ERR_NONE) {
                err = __SPI_RX_DMA_ID(&hw_info, &rx_dma_id);
            }

            if (err == MS_ERR_NONE) {
                priv->tx_dma_id         = tx_dma_id.dma_id;
                priv->tx_dma_channel_id = tx_dma_id.channel_id;
                priv->rx_dma_id         = rx_dma_id.dma_id;
                priv->rx_dma_channel_id = rx_dma_id.channel_id;
            } else {
                goto abort_exit;
            }
        }

        priv->param.baud_rate      = 400000;
        priv->param.mode           = MS_SPI_MODE_MASTER;
        priv->param.direction      = MS_SPI_DIRECTION_2LINES;
        priv->param.data_size      = MS_SPI_DATA_SIZE_8BIT;
        priv->param.frame_mode     = MS_SPI_CLK_POLARITY_HIGH | 
                                     MS_SPI_CLK_PHASE_2EDGE | MS_SPI_FIRST_BIT_MSB;
        priv->param.nss            = MS_SPI_NSS_SOFT;
        priv->param.crc_polynomial = 7;

        err = __sccb9001cxx_spi_bus_param_convert(&priv->param, &priv->native_param);
        if (err == MS_ERR_NONE) {
            __sccb9001cxx_spi_bus_configure(priv, &priv->native_param);

            err =  ms_spi_bus_register(spi_bus);
            if (err == MS_ERR_NONE) {
                err = ms_spi_bus_dev_create(path, spi_bus);
            }
        }
    }

    return err;

abort_exit:
    return MS_ERR;
}

2.3 ioctl 命令

以下仅列出几个最基本的命令,可以在 sdk/src/driver/ms_drv_spi.h 文件中找到所有命令的定义。

命令描述参数
MS_SPI_CMD_SET_PARAM设置 SPI 控制器的工作模式ms_spi_param_t 指针
MS_SPI_CMD_GET_PARAM获取 SPI 控制器的工作模式ms_spi_param_t 指针

(1)ms_spi_param_t

typedef struct {
    ms_uint32_t     baud_rate;  	// Specifies the clock frequency.    
    ms_uint8_t      mode;       	// Specifies the SPI operating mode.        
    ms_uint8_t      direction;   	// Specifies the SPI Directional mode.      
    ms_uint8_t      data_size;    	// Specifies the SPI data size.            
    ms_uint16_t     frame_mode; 	// Specifies the frame mode.
    ms_uint8_t      nss;        	// whether the NSS signal is managed by hardware.
    ms_uint32_t     crc_polynomial; // the polynomial used for the CRC calculation. 
} ms_spi_param_t;
  • SPI 工作模式(mode)
可选配置描述
MS_SPI_MODE_SLAVESPI 从机模式
MS_SPI_MODE_MASTERSPI 主机模式
  • SPI 数据线数(direction)
可选配置描述
MS_SPI_DIR_MODE_2LINESSPI 使用两线模式
MS_SPI_DIR_MODE_2LINES_RXONLYSPI 使用两线模式,只使能接收
MS_SPI_DIR_MODE_1LINESPI 使用一线模式
  • SPI 数据帧大小(data_size)
可选配置描述
MS_SPI_DATA_SIZE_8BITSPI 数据帧大小为 8bit
MS_SPI_DATA_SIZE_16BITSPI 数据帧大小为 16bit
  • SPI 时钟/相位/大小端(frame_mode)
可选配置描述
MS_SPI_CLK_POLARITY_LOWSPI 时钟极性,总线空闲时 SCLK 引脚为低电平
MS_SPI_CLK_POLARITY_HIGHSPI 时钟极性,总线空闲时 SCLK 引脚为高电平
MS_SPI_CLK_PHASE_1EDGESPI 时钟相位,在 SCLK 第一个变化沿捕获数据
MS_SPI_CLK_PHASE_2EDGESPI 时钟相位,在 SCLK 第二个变化沿捕获数据
MS_SPI_FIRST_BIT_MSBSPI 数据端序,总线上先发送 MSB
MS_SPI_FIRST_BIT_LSBSPI 数据端序,总线上先发送 LSB
MS_SPI_TI_MODE_DISABLE禁能 TI 兼容模式
MS_SPI_TI_MODE_ENABLE使能 TI 兼容模式
MS_SPI_CRC_CALC_DISABLE禁能 CRC 计算和校验
MS_SPI_CRC_CALC_ENABLE使能 CRC 计算和校验
  • SPI 硬件片选模式(nss)
可选配置描述
MS_SPI_NSS_SOFT使用软件片选,关闭控制器的硬件片选
MS_SPI_NSS_HARD_INPUT配置控制器接收片选信号(用于从机模式下)
MS_SPI_NSS_HARD_OUTPUT配置控制器输出片选信号(用于主机模式下)

(2)ms_spi_msg_t

typedef struct {
    ms_uint16_t     flags;		// 传输控制参数
    ms_ptr_t        tx_buf;		// 发送缓冲区
    ms_ptr_t        rx_buf;		// 接收缓冲区
    ms_size_t       len;		// 缓冲区大小
} ms_spi_msg_t;
  • SPI 传输控制标记(flags)
可选配置描述
MS_SPI_M_BEGINAssert CS before transfer
MS_SPI_M_ENDDeassert CS after transfer
MS_SPI_M_ONCEAssert CS before transfer and deassert CS after transfer
MS_SPI_M_TX_FIXOnly transmit tx_buf[0] in this transfer
MS_SPI_M_RX_FIXOnly store recieve data to rx_buf[0] in this transfer
MS_SPI_M_READMSG use read operation
MS_SPI_M_WRITEMSG use write operation

3. SPI 的应用

3.1 使用 SPI 读写 NorFlash

#define __MS_IO
#include "ms_kern.h"
#include "ms_io_core.h"
#include "ms_driver.h"
#include "ms_littlefs.h"
#include "ms_drv_nor_spi.h"

#include <string.h>
#include <ctype.h>

/**
 * @brief nor flash device driver.
 */

#define MS_RAWFLASH_DRV_NAME       "rawflash"

/*
 * NOR FLASH Chip Command
 */
typedef struct {
    ms_uint8_t  en_4byte_addr;                                          /* enable 4 bytes address mode  */
    ms_uint8_t  en_wr;                                                  /* enable write                 */
    ms_uint8_t  rd_sta_reg1;                                            /* read status register1        */
    ms_uint8_t  rd_sta_reg2;                                            /* read status register2        */
    ms_uint8_t  rd_sta_reg3;                                            /* read status register3        */
    ms_uint8_t  rd_id;                                                  /* read chip id                 */
    ms_uint8_t  rd_data;                                                /* read data                    */
    ms_uint8_t  wr_data;                                                /* write data                   */
    ms_uint8_t  wr_data2;                                               /* write data 2                 */
    ms_uint8_t  sector_erase;                                           /* erase sector                 */
} ms_chip_cmd;

/*
 * Private information of chip
 */
typedef struct {
    char              *chip_name;
    ms_uint32_t        id;
    ms_uint8_t         en_4byte_addr;

    ms_size_t          size;
    ms_size_t          sector_size;
    ms_size_t          page_size;

    const ms_chip_cmd *cmds;
} ms_chip_info;

/*
 * Provide to litterfs for data transmission
 */
typedef struct {
    const ms_chip_info *cur_chip_info;
    ms_spi_device_t     spi_dev;
} privinfo_t;

/*
 * nor flash infomation
 */
typedef struct {
    ms_io_device_t     dev;
    privinfo_t         priv;
} ms_nor_dev_t;

/*
 * Provide to litterfs for data transmission
 */
typedef struct {
    privinfo_t          *priv;
    ms_uint32_t          part_base;
    ms_size_t            part_size;
} partinfo_t;

/*
 * One to one correspondence with the command of operation chip
 */

#define CHIP_CMD_ID_W25Q  0
#define CHIP_CMD_ID_M25P  1
#define CHIP_CMD_ID_AT45D 2

const static ms_chip_cmd chip_cmd[] = {

    /*
     *  w25qxx, mx25xxx
     */
    {
        .en_4byte_addr = 0xB7,
        .en_wr         = 0x06,
        .rd_sta_reg1   = 0x05,
        .rd_sta_reg2   = 0x35,
        .rd_sta_reg3   = 0x15,
        .rd_id         = 0x9F,
        .rd_data       = 0x03,
        .wr_data       = 0x02,
        .sector_erase  = 0x20,
    },

    /*
     *  w25pxx
     */
    {
        .en_wr         = 0x06,
        .rd_sta_reg1   = 0x05,
        .rd_id         = 0x9F,
        .rd_data       = 0x03,
        .wr_data       = 0x02,
        .sector_erase  = 0xD8,
    },
    /*
     * at45dxxx
     */
    {
        .rd_sta_reg1   = 0xD7,
        .rd_id         = 0x9F,
        .rd_data       = 0x0B,
        .wr_data       = 0x82,
        .wr_data2      = 0x85,
        .sector_erase  = 0x50,
    },
};

/*
 * all chips information
 */
const static ms_chip_info all_chip_info[] = {
    /*
     * chip_name, id, en_4byte_addr, size. sector_size, page_size, cmds
     */
    {
        "gd25q32", 0xC84016, 0, 4 * 1024 * 1024,  4 * 1024, 256, &chip_cmd[CHIP_CMD_ID_W25Q]
    },
    {
        "gd25q64", 0xC84017, 0, 8 * 1024 * 1024,  4 * 1024, 256, &chip_cmd[CHIP_CMD_ID_W25Q]
    },
    {
        "gd25q127c", 0xC84018, 0, 16 * 1024 * 1024,  4 * 1024, 256, &chip_cmd[CHIP_CMD_ID_W25Q]
    },
    {
        "gd25q257d", 0xC84019, 1, 32 * 1024 * 1024,  4 * 1024, 256, &chip_cmd[CHIP_CMD_ID_W25Q]
    },
    {
        "gd25q256e", 0xC84019, 1, 32 * 1024 * 1024,  4 * 1024, 256, &chip_cmd[CHIP_CMD_ID_W25Q]
    },
    {
        "w25q16",  0xef4015, 0, 2 * 1024 * 1024,  4 * 1024, 256, &chip_cmd[CHIP_CMD_ID_W25Q]
    },
    {
        "w25q32",  0xef4016, 0, 4 * 1024 * 1024,  4 * 1024, 256, &chip_cmd[CHIP_CMD_ID_W25Q]
    },
    {
        "w25q64",  0xef4017, 0, 8 * 1024 * 1024,  4 * 1024, 256, &chip_cmd[CHIP_CMD_ID_W25Q]
    },
    {
        "w25q128", 0xef4018, 0, 16 * 1024 * 1024, 4 * 1024, 256, &chip_cmd[CHIP_CMD_ID_W25Q]
    },
    {
        "w25q256", 0xef4019, 1, 32 * 1024 * 1024, 4 * 1024, 256, &chip_cmd[CHIP_CMD_ID_W25Q]
    },
    {
        "m25p05",  0x202010, 0, 64 * 1024,        32 * 1024, 128, &chip_cmd[CHIP_CMD_ID_M25P]
    },
    {
        "m25p10",  0x202011, 0, 128 * 1024,       32 * 1024, 128, &chip_cmd[CHIP_CMD_ID_M25P]
    },
    {
        "m25p20",  0x202012, 0, 256 * 1024,       64 * 1024, 256, &chip_cmd[CHIP_CMD_ID_M25P]
    },
    {
        "m25p40",  0x202013, 0, 512 * 1024,       64 * 1024, 256, &chip_cmd[CHIP_CMD_ID_M25P]
    },
    {
        "m25p80",  0x202014, 0, 1* 1024 * 1024,   64 * 1024, 256, &chip_cmd[CHIP_CMD_ID_M25P]
    },
    {
        "m25p16",  0x202015, 0, 2 * 1024 * 1024,  64 * 1024, 256, &chip_cmd[CHIP_CMD_ID_M25P]
    },
    {
        "m25p32",  0x202016, 0, 4 * 1024 * 1024,  64 * 1024, 256, &chip_cmd[CHIP_CMD_ID_M25P]
    },
    {
        "m25p64",  0x202017, 0, 8 * 1024 * 1024,  64 * 1024, 256, &chip_cmd[CHIP_CMD_ID_M25P]
    },
    {
        "m25p128", 0x202018, 0, 16 * 1024 * 1024, 256 * 1024, 256, &chip_cmd[CHIP_CMD_ID_M25P]
    },
    {
        "m25l51245g", 0xc2201a, 1, 64 * 1024 * 1024, 4 * 1024, 256, &chip_cmd[CHIP_CMD_ID_W25Q]
    },
    {
        "AT45DB021E", 0x1f2300, 0 , 2 * 1024 * 1024, 2 * 1024, 256, &chip_cmd[CHIP_CMD_ID_AT45D]
    },
    {
        "AT45DB041E", 0x1f2400, 0 , 4 * 1024 * 1024, 2 * 1024, 256, &chip_cmd[CHIP_CMD_ID_AT45D]
    },
    {
        "AT45DB081E", 0x1f2500, 0 , 8 * 1024 * 1024, 2 * 1024, 256, &chip_cmd[CHIP_CMD_ID_AT45D]
    },
    {
        "AT45DB161E", 0x1f2600, 0,  16 * 1024 * 1024, 4 * 1024, 512, &chip_cmd[CHIP_CMD_ID_AT45D]
    },
    {
        "AT45DQ161", 0x1f2600, 0,  16 * 1024 * 1024, 4 * 1024, 512, &chip_cmd[CHIP_CMD_ID_AT45D]
    },
    {
        "AT45DB321E", 0x1f2700, 0,  32 * 1024 * 1024, 4 * 1024, 512, &chip_cmd[CHIP_CMD_ID_AT45D]
    },
    {
        "AT45DQ321", 0x1f2701, 0,  32 * 1024 * 1024, 4 * 1024, 512, &chip_cmd[CHIP_CMD_ID_AT45D]
    },
    {
        "AT45DB641E", 0x1f2800, 0, 64 * 1024 * 1024, 2 * 1024, 256, &chip_cmd[CHIP_CMD_ID_AT45D]
    },
    {
        "MX25L25645G", 0XC22019, 0, 32 * 1024 * 1024, 4 * 1024, 256, &chip_cmd[CHIP_CMD_ID_W25Q]
    },
};

/**
 * @brief raw flash lock.
 */
static ms_handle_t  rawflash_lock;
#define __RAWFLASH_LOCK()     ms_mutex_lock(rawflash_lock, MS_TIMEOUT_FOREVER)
#define __RAWFLASH_UNLOCK()   ms_mutex_unlock(rawflash_lock);

/*
 * brief:   read norflash Status register
 * regno:   Register number
 * ret:     The value of the register
 */
static ms_uint8_t __norflash_read_sr(privinfo_t *priv, ms_uint8_t regno)
{
    ms_uint8_t   cmd;
    ms_uint8_t   dump;
    ms_uint8_t   rx;
    ms_spi_msg_t msgs[2];

    switch (regno)
    {
        case 1:
            cmd = priv->cur_chip_info->cmds->rd_sta_reg1;
            break;
        case 2:
            cmd = priv->cur_chip_info->cmds->rd_sta_reg2;
            break;
        case 3:
            cmd = priv->cur_chip_info->cmds->rd_sta_reg3;
            break;
        default:
            cmd = priv->cur_chip_info->cmds->rd_sta_reg1;
            break;
    }

    msgs[0].len    =  1;
    msgs[0].tx_buf = &cmd;
    msgs[0].rx_buf = &dump;
    msgs[0].flags  =  MS_SPI_M_RX_FIX | MS_SPI_M_WRITE | MS_SPI_M_BEGIN;

    dump = 0;
    msgs[1].len    =  1;
    msgs[1].tx_buf = &dump;
    msgs[1].rx_buf = &rx;
    msgs[1].flags  =  MS_SPI_M_TX_FIX | MS_SPI_M_READ | MS_SPI_M_END;

    if (ms_spi_device_trans(&priv->spi_dev, msgs, 2) != 2) {
        rx = 0;
    }

    return rx;
}

/*
 * wait free
 */
static void __norflash_wait_busy(privinfo_t *priv, ms_uint32_t delay_ms)
{
    while ((__norflash_read_sr(priv, 1) & 0x01) == 0x01) {
        if (delay_ms) {
            ms_thread_sleep_ms(delay_ms);
        }
    }
}

/*
 * enable write
 */
static void __norflash_write_enable(privinfo_t *priv)
{
    ms_uint8_t   dump;
    ms_spi_msg_t msg;

    msg.len    =  1;
    msg.rx_buf = &dump;
    msg.tx_buf = &priv->cur_chip_info->cmds->en_wr;
    msg.flags  =  MS_SPI_M_RX_FIX | MS_SPI_M_WRITE | MS_SPI_M_ONCE;

    ms_spi_device_trans(&priv->spi_dev, &msg, 1);
}

/*
 * read chip id
 */
static ms_uint32_t __norflash_read_id(privinfo_t *priv)
{
    ms_uint32_t  chip_id = 0;
    ms_uint8_t   dump;
    ms_uint8_t   rx[3];
    ms_spi_msg_t msgs[2];

    msgs[0].len    =  1;
    msgs[0].tx_buf = &priv->cur_chip_info->cmds->rd_id;
    msgs[0].rx_buf = &dump;
    msgs[0].flags  =  MS_SPI_M_RX_FIX | MS_SPI_M_WRITE | MS_SPI_M_BEGIN;

    dump = 0;
    msgs[1].len    =  3;
    msgs[1].tx_buf = &dump;
    msgs[1].rx_buf =  rx;
    msgs[1].flags  =  MS_SPI_M_TX_FIX | MS_SPI_M_READ | MS_SPI_M_END;

    if (ms_spi_device_trans(&priv->spi_dev, msgs, 2) != 2) {
        chip_id = 0;
    } else {
        chip_id  = (ms_uint32_t)(rx[0] << 16);
        chip_id += (ms_uint32_t)(rx[1] << 8);
        chip_id += (ms_uint32_t)(rx[2] << 0);
    }

    return chip_id;
}


/*
 * brief:   read SPI FLASH, Start reading data of specified length at the specified address
 * buf:     Data storage area
 * addr:    Address to start reading
 * len:     Number of bytes to read
 */
static void __norflash_read(privinfo_t *priv, ms_ptr_t buf, ms_uint32_t addr, ms_size_t len)
{
    ms_uint8_t   dump;
    ms_uint8_t   tx[5];
    ms_spi_msg_t msgs[2];

    __RAWFLASH_LOCK();

    if (buf) {
        tx[0] = priv->cur_chip_info->cmds->rd_data;

        if (priv->cur_chip_info->en_4byte_addr == 0) {
            msgs[0].len = 4;
            tx[1]       = (ms_uint8_t)(addr >> 16);
            tx[2]       = (ms_uint8_t)(addr >> 8);
            tx[3]       = (ms_uint8_t)(addr);
        } else {
            msgs[0].len = 5;
            tx[1]       = (ms_uint8_t)(addr >> 24);
            tx[2]       = (ms_uint8_t)(addr >> 16);
            tx[3]       = (ms_uint8_t)(addr >> 8);
            tx[4]       = (ms_uint8_t)(addr);
        }

        msgs[0].tx_buf  =  tx;
        msgs[0].rx_buf  = &dump;
        msgs[0].flags   =  MS_SPI_M_RX_FIX | MS_SPI_M_WRITE | MS_SPI_M_BEGIN;

        dump = 0;
        msgs[1].len     =  len;
        msgs[1].tx_buf  = &dump;
        msgs[1].rx_buf  =  buf;
        msgs[1].flags   =  MS_SPI_M_TX_FIX | MS_SPI_M_READ | MS_SPI_M_END;

        ms_spi_device_trans(&priv->spi_dev, msgs, 2);
    }

    __RAWFLASH_UNLOCK();
}

/*
 * brief:  SPI writes data in a page, Write up to 256 bytes of data at the specified address
 * buf:    Data storage area
 * addr:   Address to start writing Address to start writing
 * len:    The number of bytes to write, which should not exceed the remaining
 *         bytes of the page!!!
 */
static void __norflash_write_page(privinfo_t *priv, ms_const_ptr_t buf, ms_uint32_t addr, ms_size_t len)
{
    ms_uint8_t   dump;
    ms_spi_msg_t msgs[2];
    ms_uint8_t   tx[5];

    if (buf) {

        __norflash_write_enable(priv);
        __norflash_wait_busy(priv, 0);

        tx[0] = priv->cur_chip_info->cmds->wr_data;

        if (priv->cur_chip_info->en_4byte_addr == 0) {
            msgs[0].len = 4;
            tx[1]       = (ms_uint8_t)(addr >> 16);
            tx[2]       = (ms_uint8_t)(addr >> 8);
            tx[3]       = (ms_uint8_t)(addr);
        } else {
            msgs[0].len = 5;
            tx[1]       = (ms_uint8_t)(addr >> 24);
            tx[2]       = (ms_uint8_t)(addr >> 16);
            tx[3]       = (ms_uint8_t)(addr >> 8);
            tx[4]       = (ms_uint8_t)(addr);
        }

        msgs[0].tx_buf  =  tx;
        msgs[0].rx_buf  = &dump;
        msgs[0].flags   =  MS_SPI_M_RX_FIX | MS_SPI_M_WRITE | MS_SPI_M_BEGIN;

        dump = 0;
        msgs[1].len     =  len;
        msgs[1].tx_buf  =  buf;
        msgs[1].rx_buf  = &dump;
        msgs[1].flags   =  MS_SPI_M_RX_FIX | MS_SPI_M_WRITE | MS_SPI_M_END;

        ms_spi_device_trans(&priv->spi_dev, msgs, 2);
        __norflash_wait_busy(priv, 0);

    }
}

/*
 *  brief:  Write SPI flash,  Start to write the data of the specified length at the specified
 *          address. The function has erase operation!
 *  buf:    Data storage area
 *  addr:   Address to start writing
 *  len:    Number of bytes to write
 */
static void __norflash_write(privinfo_t *priv, ms_const_ptr_t buf, ms_uint32_t addr, ms_size_t len)
{
    ms_uint32_t len_wr_able;

    __RAWFLASH_LOCK();

    while (len) {
        len_wr_able = priv->cur_chip_info->page_size - (addr % priv->cur_chip_info->page_size);
        if (len_wr_able > len) {
            len_wr_able = len;
        }

        __norflash_write_page(priv, buf, addr, len_wr_able);

        addr += len_wr_able;
        buf  += len_wr_able;
        len  -= len_wr_able;
    }

    __RAWFLASH_UNLOCK();
}

/*
 * brief:   erase a sector, at least spend 150ms
 * addr:    address
 */
static void __norflash_erase(privinfo_t *priv, ms_uint32_t addr)
{
    ms_uint8_t   dump;
    ms_spi_msg_t msg;
    ms_size_t    len;
    ms_uint8_t   tx[5];

    __RAWFLASH_LOCK();

    tx[0]  = priv->cur_chip_info->cmds->sector_erase;

    if (priv->cur_chip_info->en_4byte_addr == 0) {
        len   = 4;
        tx[1] = (ms_uint8_t)(addr >> 16);
        tx[2] = (ms_uint8_t)(addr >> 8);
        tx[3] = (ms_uint8_t)(addr);
    } else {
        len   = 5;
        tx[1] = (ms_uint8_t)(addr >> 24);
        tx[2] = (ms_uint8_t)(addr >> 16);
        tx[3] = (ms_uint8_t)(addr >> 8);
        tx[4] = (ms_uint8_t)(addr);
    }

    msg.len    =  len;
    msg.rx_buf = &dump;
    msg.tx_buf =  tx;
    msg.flags  =  MS_SPI_M_RX_FIX | MS_SPI_M_WRITE | MS_SPI_M_ONCE;

    __norflash_write_enable(priv);
    __norflash_wait_busy(priv, 0);
    ms_spi_device_trans(&priv->spi_dev, &msg, 1);
    __norflash_wait_busy(priv, 1);

    __RAWFLASH_UNLOCK();
}

/*
 * Read a region in a block. Negative error codes are propogated to the user.
 */
static int __norflash_block_read(const struct lfs_config *c, lfs_block_t block,
                                 lfs_off_t off, void *buffer, lfs_size_t size)
{
    ms_uint32_t  addr = block * c->block_size + off;

    __norflash_read(c->context, buffer, addr, size);

    return LFS_ERR_OK;
}

/*
 * Program a region in a block. The block must have previously been erased. Negative error codes are
 * propogated to the user.May return LFS_ERR_CORRUPT if the block should be considered bad.
 */
static int __norflash_block_prog(const struct lfs_config *c, lfs_block_t block,
                                 lfs_off_t off, const void *buffer, lfs_size_t size)
{
    ms_uint32_t addr = block * c->block_size + off;

    __norflash_write(c->context, buffer, addr, size);

    return LFS_ERR_OK;
}

/*
 * Erase a block. A block must be erased before being programmed.
 * The state of an erased block is undefined. Negative error codes are propogated to the user.
 * May return LFS_ERR_CORRUPT if the block should be considered bad.
 */
static int __norflash_block_erase(const struct lfs_config *c, lfs_block_t block)
{
    ms_uint32_t addr = block * c->block_size;

    __norflash_erase(c->context, addr);

    return LFS_ERR_OK;
}

/*
 * brief:   read norflash Status register
 * regno:   Register number
 * ret:     The value of the register
 */
static ms_uint16_t __at45d_norflash_read_sr(privinfo_t *priv)
{
    ms_uint8_t   cmd;
    ms_uint8_t   dump;
    ms_uint8_t   rx[2];
    ms_uint16_t  status = 0;
    ms_spi_msg_t msgs[2];

    cmd = priv->cur_chip_info->cmds->rd_sta_reg1;

    msgs[0].len    =  1;
    msgs[0].tx_buf = &cmd;
    msgs[0].rx_buf = &dump;
    msgs[0].flags  =  MS_SPI_M_RX_FIX | MS_SPI_M_WRITE | MS_SPI_M_BEGIN;

    dump = 0;
    msgs[1].len    =  2;
    msgs[1].tx_buf = &dump;
    msgs[1].rx_buf = &rx;
    msgs[1].flags  =  MS_SPI_M_TX_FIX | MS_SPI_M_READ | MS_SPI_M_END;

    if (ms_spi_device_trans(&priv->spi_dev, msgs, 2) == 2) {
        status |= rx[0] << 8;
        status |= rx[1];
    }

    return status;
}

/*
 * wait free
 */
static void __at45d_norflash_wait_busy(privinfo_t *priv, ms_uint32_t delay_ms)
{
    while ((__at45d_norflash_read_sr(priv) & 0x80) == 0x00) {
        if (delay_ms) {
            ms_thread_sleep_ms(delay_ms);
        }
    }
}

/*
 * Sync the state of the underlying block device. Negative error codes
 * are propogated to the user.
 */
static int __norflash_block_sync(const struct lfs_config *c)
{
    return LFS_ERR_OK;
}

#define AT45DXX_DEBUG_LOG       0
#define AT45DXX_DUMMY           0x00
/*
 * Read a region in a block. Negative error codes are propogated to the user.
 */
static int __at45d_norflash_block_read(const struct lfs_config *c, lfs_block_t block,
                                       lfs_off_t off, void *buffer, lfs_size_t size)
{
    ms_uint32_t  addr = block * c->block_size + off;
    privinfo_t  *priv = c->context;

    ms_uint8_t   dump;
    ms_uint8_t   tx[5];
    ms_spi_msg_t msgs[2];

    if (buffer) {
        tx[0]   = priv->cur_chip_info->cmds->rd_data;
        tx[1]   = (ms_uint8_t)(addr >> 16);
        tx[2]   = (ms_uint8_t)(addr >> 8);
        tx[3]   = (ms_uint8_t)(addr);
        tx[4]   = AT45DXX_DUMMY;

        msgs[0].len     =  5;
        msgs[0].tx_buf  =  tx;
        msgs[0].rx_buf  = &dump;
        msgs[0].flags   =  MS_SPI_M_RX_FIX | MS_SPI_M_WRITE | MS_SPI_M_BEGIN;

        dump = 0;
        msgs[1].len     =  size;
        msgs[1].tx_buf  = &dump;
        msgs[1].rx_buf  =  buffer;
        msgs[1].flags   =  MS_SPI_M_TX_FIX | MS_SPI_M_READ | MS_SPI_M_END;

        ms_spi_device_trans(&priv->spi_dev, msgs, 2);

#if AT45DXX_DEBUG_LOG > 0
        ms_printk(MS_PK_ERR, "read block[%d] off[%d] addr[%#x] len[%d]:\r\n", block, off, addr, size);
        int i;
        for (i = 0; i < size; i++) {
            ms_printk(MS_PK_ERR, "%x ", ((ms_uint8_t*)buffer)[i]);
        }
        ms_printk(MS_PK_ERR, "finish\r\n");
#endif
    }

    return LFS_ERR_OK;
}

/*
 * brief:  SPI writes data in a page, Write up to 256 bytes of data at the specified address
 * buf:    Data storage area
 * addr:   Address to start writing Address to start writing
 * len:    The number of bytes to write, which should not exceed the remaining
 *         bytes of the page!!!
 */
static void __at45d_norflash_write_page(privinfo_t *priv, ms_const_ptr_t buf, ms_uint32_t addr, ms_size_t len)
{
    static ms_uint8_t   _at45_buffer;
    ms_uint8_t          dump;
    ms_spi_msg_t        msgs[2];
    ms_uint8_t          tx[5];

    if (buf) {

        tx[0] = _at45_buffer ? priv->cur_chip_info->cmds->wr_data :
                               priv->cur_chip_info->cmds->wr_data2;
        _at45_buffer = !_at45_buffer;

        tx[1] = (ms_uint8_t)(addr >> 16);
        tx[2] = (ms_uint8_t)(addr >> 8);
        tx[3] = (ms_uint8_t)(addr);

        msgs[0].len = 4;
        msgs[0].tx_buf  =  tx;
        msgs[0].rx_buf  = &dump;
        msgs[0].flags   =  MS_SPI_M_RX_FIX | MS_SPI_M_WRITE | MS_SPI_M_BEGIN;

        dump = 0;
        msgs[1].len     =  len;
        msgs[1].tx_buf  =  buf;
        msgs[1].rx_buf  = &dump;
        msgs[1].flags   =  MS_SPI_M_RX_FIX | MS_SPI_M_WRITE | MS_SPI_M_END;

        ms_spi_device_trans(&priv->spi_dev, msgs, 2);
        if (_at45_buffer)
            __at45d_norflash_wait_busy(priv, 0);
    }
}

/*
 * Program a region in a block. The block must have previously been erased. Negative error codes are
 * propogated to the user.May return LFS_ERR_CORRUPT if the block should be considered bad.
 */
static int __at45d_norflash_block_prog(const struct lfs_config *c, lfs_block_t block,
                                       lfs_off_t off, const void *buffer, lfs_size_t size)
{
    ms_uint32_t  addr = block * c->block_size + off;
    privinfo_t  *priv = c->context;

    ms_uint32_t len_wr_able;

    __at45d_norflash_wait_busy(priv, 0);
    while (size) {
        len_wr_able = priv->cur_chip_info->page_size - (addr % priv->cur_chip_info->page_size);
        if (len_wr_able > size) {
            len_wr_able = size;
        }

        __at45d_norflash_write_page(priv, buffer, addr, len_wr_able);

#if AT45DXX_DEBUG_LOG > 0
        ms_printk(MS_PK_ERR, "write block[%d] off[%d] addr[%#x] len[%d]:\r\n", block, off, addr, len_wr_able);
        int i;
        for (i = 0; i < len_wr_able; i++) {
            ms_printk(MS_PK_ERR, "%x ", ((ms_uint8_t*)buffer)[i]);
        }
        ms_printk(MS_PK_ERR, "finish\r\n");
#endif

        addr += len_wr_able;
        buffer  += len_wr_able;
        size  -= len_wr_able;
    }
    __at45d_norflash_wait_busy(priv, 0);

    return LFS_ERR_OK;
}

/*
 * Erase a block. A block must be erased before being programmed.
 * The state of an erased block is undefined. Negative error codes are propogated to the user.
 * May return LFS_ERR_CORRUPT if the block should be considered bad.
 */
static int __at45d_norflash_block_erase(const struct lfs_config *c, lfs_block_t block)
{
    ms_uint8_t   dump;
    ms_uint32_t  addr = block * c->block_size;
    privinfo_t  *priv = c->context;

    ms_spi_msg_t msg;
    ms_uint8_t   tx[4];

    tx[0]  = priv->cur_chip_info->cmds->sector_erase;
    tx[1] = (ms_uint8_t)(addr >> 16);
    tx[2] = (ms_uint8_t)(addr >> 8);
    tx[3] = (ms_uint8_t)(addr);

    msg.len    =  4;
    msg.rx_buf = &dump;
    msg.tx_buf =  tx;
    msg.flags  =  MS_SPI_M_RX_FIX | MS_SPI_M_WRITE | MS_SPI_M_ONCE;

    __at45d_norflash_wait_busy(priv, 0);
    ms_spi_device_trans(&priv->spi_dev, &msg, 1);
    __at45d_norflash_wait_busy(priv, 1);

#if AT45DXX_DEBUG_LOG > 0
    ms_printk(MS_PK_ERR, "erase block[%d] addr[%#x]\r\n", block, addr);
#endif

    return LFS_ERR_OK;
}

/*
 * Sync the state of the underlying block device. Negative error codes
 * are propogated to the user.
 */
static int __at45d_norflash_block_sync(const struct lfs_config *c)
{
    return LFS_ERR_OK;
}

/*
 * Confirm Chip ID
 */
static ms_err_t __norflash_confirm_chips_id(privinfo_t *priv)
{
    ms_uint32_t chip_id;
    ms_err_t    err;

    chip_id = __norflash_read_id(priv);
    if (chip_id == priv->cur_chip_info->id) {
        err = MS_ERR_NONE;
    } else {
        err = MS_ERR_IO;
    }

    return err;
}

/*
 * configure address mode
 */
static void __norflash_cfg_addr_mode(privinfo_t *priv)
{
    ms_uint8_t   dump;
    ms_spi_msg_t msg;
    ms_uint8_t   temp;

    if (priv->cur_chip_info->en_4byte_addr != 0) {
        temp = __norflash_read_sr(priv, 3);
        if ((temp & 0X01) == 0) {
            msg.len    =  1;
            msg.rx_buf = &dump;
            msg.tx_buf = &priv->cur_chip_info->cmds->en_4byte_addr;
            msg.flags  =  MS_SPI_M_RX_FIX | MS_SPI_M_WRITE | MS_SPI_M_ONCE;
            ms_spi_device_trans(&priv->spi_dev, &msg, 1);
        }
    }
}

/*
 * at45d series chip could configure buffer and page size
 * to ensure compatibility of file system, setting buffer and page size to
 * "power of 2" binary page size
 */
static void __norflash_cfg_page_size(privinfo_t *priv)
{
    ms_uint8_t   dump;
    ms_spi_msg_t msg;
    ms_uint8_t   tx[4];

#define AT45DXX_ID_MASK     0xFFE000
#define AT45DXX_SERIES_ID   0x1F2000
#define AT45DXX_BINARY_PAGE 0x3D2A80A6
    if ((priv->cur_chip_info->id & AT45DXX_ID_MASK) == AT45DXX_SERIES_ID) {
        if ((__at45d_norflash_read_sr(priv) & 0x100) == 0x0) {
            tx[0] = (ms_uint8_t)(AT45DXX_BINARY_PAGE >> 24);
            tx[1] = (ms_uint8_t)(AT45DXX_BINARY_PAGE >> 16);
            tx[2] = (ms_uint8_t)(AT45DXX_BINARY_PAGE >> 8);
            tx[3] = (ms_uint8_t)(AT45DXX_BINARY_PAGE);

            msg.len    =  4;
            msg.rx_buf = &dump;
            msg.tx_buf =  tx;
            msg.flags  =  MS_SPI_M_RX_FIX | MS_SPI_M_WRITE | MS_SPI_M_ONCE;
            ms_spi_device_trans(&priv->spi_dev, &msg, 1);
        }
    }
}

static const ms_chip_info *__norflash_is_supported(const char *dev_name)
{
    ms_ssize_t         i;
    ms_ssize_t         j;
    char               chip_name_low[20];
    char               dev_name_low[20];

    for (i = 0; i < MS_ARRAY_SIZE(all_chip_info); i++) {
        bzero(chip_name_low, sizeof(chip_name_low));
        bzero(dev_name_low, sizeof(dev_name_low));

        for (j = 0; all_chip_info[i].chip_name[j]; j++) {
            chip_name_low[j] = tolower(all_chip_info[i].chip_name[j]);
        }

        for (j = 0; dev_name[j]; j++) {
            dev_name_low[j] = tolower(dev_name[j]);
        }

        if (strcmp(chip_name_low, dev_name_low) == 0) {
            return  &all_chip_info[i];
        }
    }

    return  MS_NULL;
}

/*
 * create norflash device
 */
ms_err_t ms_nor_dev_create(ms_nor_port_t *norflash_port)
{
    ms_err_t             err = MS_ERR;
    const ms_chip_info  *chip_info = MS_NULL;
    ms_nor_dev_t        *dev;
    struct lfs_config   *lfs_cfg;

    if ((norflash_port->mount_path    != MS_NULL) &&
        (norflash_port->bus_name      != MS_NULL) &&
        (norflash_port->dev_path      != MS_NULL) &&
        (norflash_port->dev_name      != MS_NULL) &&
        (norflash_port->gpio_init     != MS_NULL) &&
        (norflash_port->cs            != MS_NULL)) {
        chip_info = __norflash_is_supported(norflash_port->dev_name);
        if (chip_info != MS_NULL) {
            dev     = ms_kmalloc(sizeof(ms_nor_dev_t));
            lfs_cfg = ms_kmalloc(sizeof(struct lfs_config));
            if (dev != MS_NULL && lfs_cfg != MS_NULL) {
                privinfo_t *priv = &dev->priv;

                bzero(dev, sizeof(ms_nor_dev_t));
                bzero(lfs_cfg, sizeof(struct lfs_config));

                priv->cur_chip_info      =  chip_info;
                priv->spi_dev.cs         =  norflash_port->cs;
                priv->spi_dev.nnode.name =  norflash_port->dev_path;
                err = ms_spi_device_attach(&priv->spi_dev, norflash_port->bus_name);
                if (err == MS_ERR_NONE) {
                    err = ms_spi_device_ioctl(&priv->spi_dev, MS_SPI_CMD_SET_PARAM, &norflash_port->spi_param);
                    if (err == MS_ERR_NONE) {
                        norflash_port->gpio_init();
                        /*
                         * confirm id is right
                         */
                        err = __norflash_confirm_chips_id(priv);
                        if (err == MS_ERR_NONE) {
                            __norflash_cfg_addr_mode(priv);
                            __norflash_cfg_page_size(priv);

                            lfs_cfg->context        = &dev->priv;
                            lfs_cfg->read_size      =  1U;
                            lfs_cfg->prog_size      =  priv->cur_chip_info->page_size;
                            lfs_cfg->block_size     =  priv->cur_chip_info->sector_size;
                            lfs_cfg->block_count    =  priv->cur_chip_info->size / priv->cur_chip_info->sector_size;
                            lfs_cfg->cache_size     =  priv->cur_chip_info->page_size;
                            lfs_cfg->block_cycles   =  500U;
                            lfs_cfg->lookahead_size =  8U * ((priv->cur_chip_info->size / priv->cur_chip_info->sector_size + 63U) / 64U);
                            if ((priv->cur_chip_info->id & AT45DXX_ID_MASK) == AT45DXX_SERIES_ID) {
                                lfs_cfg->read           =  __at45d_norflash_block_read,
                                lfs_cfg->prog           =  __at45d_norflash_block_prog,
                                lfs_cfg->erase          =  __at45d_norflash_block_erase,
                                lfs_cfg->sync           =  __at45d_norflash_block_sync;
                            }else {
                                lfs_cfg->read           =  __norflash_block_read,
                                lfs_cfg->prog           =  __norflash_block_prog,
                                lfs_cfg->erase          =  __norflash_block_erase,
                                lfs_cfg->sync           =  __norflash_block_sync;
                            }
                            err = ms_io_device_register(&dev->dev, norflash_port->dev_path, "ms_null", lfs_cfg);
                            if (err == MS_ERR_NONE) {
                                err = ms_io_mount(norflash_port->mount_path,
                                                  norflash_port->dev_path, MS_LITTLEFS_NAME, MS_NULL);
                                if (err != MS_ERR_NONE) {
                                    ms_io_device_unregister(&dev->dev);
                                }
                            }
                        }
                    }
                    if (err != MS_ERR_NONE) {
                        ms_spi_device_detach(&priv->spi_dev, norflash_port->bus_name);
                    }
                }

                if (err != MS_ERR_NONE) {
                    (void)ms_kfree(dev);
                    (void)ms_kfree(lfs_cfg);
                }
            } else {
                err = MS_ERR_KERN_HEAP_NO_MEM;

                if (dev != MS_NULL) {
                    (void)ms_kfree(dev);
                }

                if (lfs_cfg != MS_NULL) {
                    (void)ms_kfree(lfs_cfg);
                }
            }
        }
    } else {
        err = MS_ERR_ARG_NULL_PTR;
    }

    if (err == MS_ERR_NONE) {
        err = ms_mutex_create("rawflash_lock", MS_WAIT_TYPE_PRIO, &rawflash_lock);
        if (err != MS_ERR_NONE) {
            err = MS_ERR_KERN_HEAP_NO_MEM;
        }
    }

    return err;
}

/*
 * Open device
 */
static int __norflash_raw_open(ms_ptr_t ctx, ms_io_file_t *file, int oflag, ms_mode_t mode)
{
    ms_atomic_inc(MS_IO_DEV_REF(file));

    return 0;
}

/*
 * Close device
 */
static int __norflash_raw_close(ms_ptr_t ctx, ms_io_file_t *file)
{
    ms_atomic_dec(MS_IO_DEV_REF(file));

    return 0;
}

/*
 * Read or write one message data from rawflash
 */
static int __norflash_raw_rw_msg(partinfo_t *part, ms_rawflash_msg_t *msg, ms_uint8_t access_mode)
{
    ms_size_t rawflash_size = part->part_size;
    ms_size_t block_size;
    int ret;

    if (msg->memaddr >= rawflash_size ||
        (msg->memaddr + msg->len) > (part->part_base + rawflash_size)) {
        ms_thread_set_errno(EINVAL);
        ret = -1;

    } else {
        ms_uint8_t *pbuf = msg->buf;
        ms_uint32_t addr = part->part_base + msg->memaddr;
        ms_uint32_t len;
        ms_uint32_t next_addr;
        ms_uint32_t already_len = 0; /* the length of already rw */

        ret = 0;
        block_size = part->priv->cur_chip_info->sector_size;

        while (already_len < msg->len) {
            next_addr = (addr + block_size) / block_size * block_size;

            len = next_addr - addr;
            if ((already_len + len) > msg->len) {
                len = msg->len - already_len;
            }

            if (access_mode == MS_ACCESS_R) {
                __norflash_read(part->priv, pbuf, addr, len);

            } else {
                __norflash_write(part->priv, pbuf, addr, len);
            }

            addr = next_addr;
            pbuf += len;
            already_len += len;
        }
    }

    return ret;
}

static ms_ssize_t  __norflash_raw_rw(partinfo_t *part, ms_rawflash_msg_t *msg, ms_size_t len, ms_uint8_t access_mode)
{
    ms_ssize_t ret;
    ms_uint8_t buf_access_mode;

    if (access_mode == MS_ACCESS_W) {
        buf_access_mode = MS_ACCESS_R;
    } else {
        buf_access_mode = MS_ACCESS_W;
    }

    if (len % sizeof(ms_rawflash_msg_t) == 0) {
        ms_uint32_t n_msg = len / sizeof(ms_rawflash_msg_t);
        ms_uint32_t i;

        for (i = 0; i < n_msg; i++) {
            /*
             * access permission check
             */
            if (!ms_access_ok((void*)msg->buf, msg->len, buf_access_mode)) {
                ms_thread_set_errno(EFAULT);
                break;
            }

            /*
             * process reading or writing
             */
            if (__norflash_raw_rw_msg(part, msg, access_mode) != 0) {
                break;
            }

            msg++;
        }

        if (i == n_msg) {
            ret = len;
        } else {
            ret = -1;
        }

    } else {
        ms_thread_set_errno(EFAULT);
        ret = -1;
    }

    return ret;
}


/*
 * Read device
 */
static ms_ssize_t __norflash_raw_read(ms_ptr_t ctx, ms_io_file_t *file, ms_ptr_t buf, ms_size_t len)
{
    return __norflash_raw_rw((partinfo_t *)ctx, (ms_rawflash_msg_t *)buf, len, MS_ACCESS_R);
}

/*
 * Write device
 */
static ms_ssize_t __norflash_raw_write(ms_ptr_t ctx, ms_io_file_t *file, ms_const_ptr_t buf, ms_size_t len)
{
    return __norflash_raw_rw((partinfo_t *)ctx, (ms_rawflash_msg_t *)buf, len, MS_ACCESS_W);
}

/*
 * Control device
 */
static int __norflash_raw_ioctl(ms_ptr_t ctx, ms_io_file_t *file, int cmd, void *arg)
{
    partinfo_t *part = (partinfo_t *)ctx;
    privinfo_t *priv = part->priv;
    int ret;

    switch (cmd) {
    case MS_RAWFLASH_CMD_GET_GEOMETRY:
        if (ms_access_ok(arg, sizeof(ms_rawflash_geometry_t), MS_ACCESS_W)) {
            ms_rawflash_geometry_t *geometry = (ms_rawflash_geometry_t *)arg;
            geometry->sector_size  = priv->cur_chip_info->sector_size;
            geometry->sector_count = part->part_size/priv->cur_chip_info->sector_size;
            ret = 0;

        } else {
            ms_thread_set_errno(EFAULT);
            ret = -1;
        }
        break;

    case MS_RAWFLASH_CMD_ERASE_SECTOR:
        if (ms_access_ok(arg, sizeof(ms_rawflash_erase_t), MS_ACCESS_R)) {
            ms_uint32_t i;
            ms_uint32_t block_count = part->part_size/priv->cur_chip_info->sector_size;
            ms_uint32_t block_base  = part->part_base/priv->cur_chip_info->sector_size;
            ms_rawflash_erase_t *erase_msg = (ms_rawflash_erase_t *)arg;
            for (i = 0; i < erase_msg->count; i++) {
                if (erase_msg->sector + i < block_count) {
                    __norflash_erase(priv, (block_base + erase_msg->sector + i) * priv->cur_chip_info->sector_size);
                } else {
                    break;
                }
            }
            ret = 0;

        } else {
            ms_thread_set_errno(EFAULT);
            ret = -1;
        }
        break;

    default:
        ms_thread_set_errno(EINVAL);
        ret = -1;
        break;
    }

    return ret;
}

/*
 * Device operating function set
 */
static ms_io_driver_ops_t ms_nor_raw_drv_ops = {
        .type   = MS_IO_DRV_TYPE_CHR,
        .open   = __norflash_raw_open,
        .close  = __norflash_raw_close,
        .write  = __norflash_raw_write,
        .read   = __norflash_raw_read,
        .ioctl  = __norflash_raw_ioctl,
};


/*
 * Device driver
 */
static ms_io_driver_t __norflash_raw_drv = {
    .nnode = {
        .name = MS_RAWFLASH_DRV_NAME,
    },
    .ops = &ms_nor_raw_drv_ops,
};

/*
 * Register rawflash device driver
 */
ms_err_t ms_nor_rawflash_drv_register(void)
{
    return ms_io_driver_register(&__norflash_raw_drv);
}
/*
 * create norflash device
 */
ms_err_t ms_nor_dev_create_with_parts(ms_nor_port_t *norflash_port)
{
    ms_err_t           err = MS_ERR;
    ms_ssize_t         i, chip_index;
    ms_nor_dev_t      *dev;

    ms_uint32_t        part_num = 0;
    ms_nor_partattr_t *parts_attr;
    ms_uint32_t        hw_init = 0;
    ms_uint32_t        part_offt = 0;
    ms_uint32_t        part_size = 0;

    if ( (norflash_port->parts_get == MS_NULL) &&
         (norflash_port->bus_name  == MS_NULL) &&
         (norflash_port->dev_name  == MS_NULL) &&
         (norflash_port->gpio_init == MS_NULL) &&
         (norflash_port->cs        == MS_NULL)) {

        return MS_ERR;
    }

    for (chip_index = 0; chip_index < MS_ARRAY_SIZE(all_chip_info); chip_index++) {
        if (strcmp(all_chip_info[chip_index].chip_name, norflash_port->dev_name) == 0) {
            break;
        }
    }

    if (chip_index >= MS_ARRAY_SIZE(all_chip_info)) {
        return MS_ERR;
    }

    err = ms_mutex_create("rawflash_lock", MS_WAIT_TYPE_PRIO, &rawflash_lock);
    if (err != MS_ERR_NONE) {
        ms_printk(MS_PK_ERR, "create rawflash_lock fail!\n");
        goto error_exit;
    }

    part_num = norflash_port->parts_get(&parts_attr);

    for (i = 0; i < part_num; i++) {
        dev = ms_kmalloc(sizeof(ms_nor_dev_t));
        if (dev == MS_NULL ) {
            goto error_exit;
        }

        bzero(dev, sizeof(ms_nor_dev_t));

        privinfo_t *priv = &dev->priv;
        priv->cur_chip_info      = &all_chip_info[chip_index];
        priv->spi_dev.cs         = norflash_port->cs;
        err = ms_spi_device_attach(&priv->spi_dev, norflash_port->bus_name);
        if (err != MS_ERR_NONE) {
            goto error_exit;
        }

        if (hw_init == 0) {
            err = ms_spi_device_ioctl(&priv->spi_dev, MS_SPI_CMD_SET_PARAM, &norflash_port->spi_param);
            if (err != MS_ERR_NONE) {
                goto error_exit;
            }

            norflash_port->gpio_init();
            err = __norflash_confirm_chips_id(priv);
            if (err != MS_ERR_NONE) {
                goto error_exit;
            }
            __norflash_cfg_addr_mode(priv);
            hw_init = 1;
        }

        part_size = priv->cur_chip_info->size * parts_attr[i].size_percent/100;

        if (parts_attr[i].is_rawflash) {

            partinfo_t *part_info = ms_kmalloc(sizeof(partinfo_t));
            if (part_info == MS_NULL ) {
                (void)ms_kfree(dev);
                goto error_exit;
            }

            part_info->priv      = priv;
            part_info->part_size = part_size;
            part_info->part_base = part_offt;

            err = ms_io_device_register(&dev->dev, parts_attr[i].dev_path, MS_RAWFLASH_DRV_NAME, part_info);
            if (err != MS_ERR_NONE) {
                (void)ms_kfree(dev);
                (void)ms_kfree(part_info);
                goto error_exit;
            }

        } else {
            struct lfs_config *lfs_cfg = ms_kmalloc(sizeof(struct lfs_config));
            if (lfs_cfg == MS_NULL ) {
                (void)ms_kfree(dev);
                goto error_exit;
            }
            bzero(lfs_cfg, sizeof(struct lfs_config));
            lfs_cfg->context        = &dev->priv;
            lfs_cfg->read_size      =  1U;
            lfs_cfg->prog_size      =  priv->cur_chip_info->page_size;
            lfs_cfg->block_size     =  priv->cur_chip_info->sector_size;
            lfs_cfg->block_count    =  part_size/ priv->cur_chip_info->sector_size;
            lfs_cfg->cache_size     =  priv->cur_chip_info->page_size;
            lfs_cfg->block_cycles   =  500U;
            lfs_cfg->lookahead_size =  8U * ((lfs_cfg->block_count + 63U) / 64U);
            lfs_cfg->read           =  __norflash_block_read;
            lfs_cfg->prog           =  __norflash_block_prog;
            lfs_cfg->erase          =  __norflash_block_erase;
            lfs_cfg->sync           =  __norflash_block_sync;
            err = ms_io_device_register(&dev->dev, parts_attr[i].dev_path, "ms_null", lfs_cfg);
            if (err != MS_ERR_NONE) {
                (void)ms_kfree(dev);
                (void)ms_kfree(lfs_cfg);
                goto error_exit;
            }
            err = ms_io_mount(parts_attr[i].mount_path,
                              parts_attr[i].dev_path, MS_LITTLEFS_NAME, MS_NULL);
            if (err != MS_ERR_NONE) {
                ms_io_device_unregister(&dev->dev);
                (void)ms_kfree(dev);
                (void)ms_kfree(lfs_cfg);
                goto error_exit;
            }
        }
        part_offt += part_size;
    }

    return err;

error_exit:
     ms_printk(MS_PK_ERR, "ms_nor_dev_create_with_parts error %d.\n", err);
    return err;
}

3.2 使用 SPI 驱动进行 ADC

#define __MS_IO
#include "ms_kern.h"
#include "ms_io_core.h"
#include "ms_driver.h"
#include "ms_drv_ad76xx.h"

#include <string.h>

/**
 * @brief ad76xx device driver.
 */

#define MS_AD76XX_DRV_NAME           "ad76xx"

#define AD_RESET_H()    do { \
                            if (priv->port->reset) { \
                                priv->port->reset(AD76XX_RESET_H); \
                            } \
                        } while (0)

#define AD_RESET_L()    do { \
                            if (priv->port->reset) { \
                                priv->port->reset(AD76XX_RESET_L); \
                            } \
                        } while (0)


typedef enum {
    ID_AD7605_4,
    ID_AD7606_8,
    ID_AD7606_6,
    ID_AD7606_4,
    ID_AD7616,
} ms_ad76xx_type_t;

/*
 * device geometry description
 */
typedef struct {
    char        name[32];
    ms_uint32_t driver_data;                                            /*  Data private to the driver  */
} ad76xx_type_geom_t;

static const ad76xx_type_geom_t ms_ad76xx_devices[] = {
    { "ad7605-4", ID_AD7605_4 },
    { "ad7606-4", ID_AD7606_4 },
    { "ad7606-6", ID_AD7606_6 },
    { "ad7606-8", ID_AD7606_8 },
    { "ad7616",   ID_AD7616 },
    {}
};

/*
 * private info
 */
typedef struct {
    ms_spi_device_t    spi_dev;
    ms_ad76xx_port_t  *port;

    ms_handle_t        semb;
    ms_handle_t        lock;
} privinfo_t;

/*
 * ad76xx device
 */
typedef struct {
    privinfo_t         priv;
    ms_io_device_t     dev;
} ms_ad76xx_dev_t;

/*
 *  reset ad76xx
 */
static void __ad76xx_reset(privinfo_t *priv)
{
    AD_RESET_L();
    ms_thread_sleep_ms(10);
    AD_RESET_H();
    ms_thread_sleep_ms(50);
}

/*
 * ad76xx spi transfer
 */
static int __ad76xx_spi_trans(privinfo_t  *priv,
                              ms_uint16_t  tx_data,
                              ms_uint16_t *rx_data,
                              ms_size_t    len,
                              ms_uint16_t  flags)
{
    ms_spi_msg_t x;
    ms_ssize_t   status;

    x.len    = len;
    x.tx_buf = &tx_data;
    x.rx_buf = rx_data;
    x.flags  = flags | MS_SPI_M_ONCE;

    status = ms_spi_device_trans(&priv->spi_dev, &x, 1);

    return (status == 1 ? 0 : -1);
}

/*
 *  ad76xx read / write register.
 */
static void __ad76xx_spi_read_write(privinfo_t  *priv,
                                    ms_uint8_t   reg_addr,
                                    ms_uint16_t *reg_data,
                                    ms_uint8_t   opt_type)
{
    ms_uint16_t  cmd = 0;
    ms_uint16_t  rd_buf;
    ms_uint32_t  len;

    if (opt_type == AD76XX_WRITE) {
        cmd |= 0x8000;
    }

    cmd |= ((reg_addr & 0x3F) << 9);

    if (opt_type == AD76XX_WRITE) {
        cmd |= (*reg_data) & 0x1FF;
    }

    len = sizeof(cmd) / sizeof(ms_uint16_t);
    __ad76xx_spi_trans(priv, cmd, &rd_buf, len, MS_SPI_M_WRITE);

    if (opt_type == AD76XX_READ) {
        __ad76xx_spi_trans(priv, cmd, &rd_buf, len, MS_SPI_M_READ);
        *reg_data = rd_buf & 0x1FF;
    }
}

/*
 *  ad76xx read sample.
 */
static ms_err_t __ad76xx_read_data(privinfo_t *priv, ms_uint16_t *buf, ms_size_t len)
{
    return (__ad76xx_spi_trans(priv, 0, buf, len, MS_SPI_M_READ | MS_SPI_M_TX_FIX));
}

/*
 *  ad76xx hardware init.
 */
static ms_err_t __ad76xx_hw_init(privinfo_t *priv)
{
    ms_err_t err;

    err = priv->port->gpio_init();
    if (err == MS_ERR_NONE) {
        err = priv->port->int_init(priv->port->int_gpio_path);
        if (err == MS_ERR_NONE) {
            err = ms_spi_device_ioctl(&priv->spi_dev, MS_SPI_CMD_SET_PARAM, &priv->port->spi_param);
        } else {
            ms_printk(MS_PK_ERR, "ad76xx int gpio init failed.\n");
        }
    } else {
        ms_printk(MS_PK_ERR, "ad76xx normal gpio init failed.\n");
    }

    return err;
}

/*
 * ad76xx isr handler.
 */
static void __ad76xx_isr(ms_ptr_t arg)
{
    privinfo_t *priv = (privinfo_t *)arg;

    /*
     * gpio int disable
     */
    priv->port->int_enable(priv->port->int_gpio, MS_FALSE);
    ms_semb_post(priv->semb);
}

/*
 *  set ad76xx register
 */
static void __ad76xx_reg_set(privinfo_t *priv)
{
    ms_uint16_t temp = 0;
    int i;

    __ad76xx_reset(priv);

    __ad76xx_spi_read_write(priv, CFG_REG, &temp, AD76XX_READ);
    while (temp & SDEF) {
        __ad76xx_reset(priv);
        __ad76xx_spi_read_write(priv, CFG_REG, &temp, AD76XX_READ);
    }

    /*
     *  Init the range register
     */
    temp = V3A(RANGE_USR) | V2A(RANGE_USR) | V1A(RANGE_USR) | V0A(RANGE_USR);
    __ad76xx_spi_read_write(priv, INPUT_RANGE_REG1, &temp, AD76XX_WRITE);

    temp = V7A(RANGE_USR) | V6A(RANGE_USR) | V5A(RANGE_USR) | V4A(RANGE_USR);
    __ad76xx_spi_read_write(priv, INPUT_RANGE_REG2, &temp, AD76XX_WRITE);

    temp = V3B(RANGE_USR) | V2B(RANGE_USR) | V1B(RANGE_USR) | V0B(RANGE_USR);
    __ad76xx_spi_read_write(priv, INPUT_RANGE_REG3, &temp, AD76XX_WRITE);

    temp = V7B(RANGE_USR) | V6B(RANGE_USR) | V5B(RANGE_USR) | V4B(RANGE_USR);
    __ad76xx_spi_read_write(priv, INPUT_RANGE_REG4, &temp, AD76XX_WRITE);

    /*
     *  Init Sequencer
     */
    for (i = 0; i < 8; i++) {
        __ad76xx_spi_read_write(priv, ARRAYSTACK_REG_BASE + i, &temp, AD76XX_READ);
        while (temp != (i * 0x11)) {
            temp = i * 0x11;
            __ad76xx_spi_read_write(priv, ARRAYSTACK_REG_BASE + i, &temp, AD76XX_WRITE);
            __ad76xx_spi_read_write(priv, ARRAYSTACK_REG_BASE + i, &temp, AD76XX_READ);
        }
    }

    for (i = 8; i < 14; i++) {
        __ad76xx_spi_read_write(priv, ARRAYSTACK_REG_BASE + i, &temp, AD76XX_READ);
        while (temp != (0x11 * (14 - i))) {
            temp = 0x11 * (14 - i);
            __ad76xx_spi_read_write(priv, ARRAYSTACK_REG_BASE + i, &temp, AD76XX_WRITE);
            __ad76xx_spi_read_write(priv, ARRAYSTACK_REG_BASE + i, &temp, AD76XX_READ);
        }
    }

    __ad76xx_spi_read_write(priv, ARRAYSTACK_REG_BASE + i, &temp, AD76XX_WRITE);
    temp = 0xffff;
    while (temp != 0x100) {
        temp = 0x100;
        __ad76xx_spi_read_write(priv, ARRAYSTACK_REG_BASE + i, &temp, AD76XX_WRITE);
        __ad76xx_spi_read_write(priv, ARRAYSTACK_REG_BASE + i, &temp, AD76XX_READ);
    }

    /*
     *  Init the configuration register
     */
    temp = BURSTEN | SEQEN | OS(2);
    __ad76xx_spi_read_write(priv, CFG_REG, &temp, AD76XX_WRITE);
}

/*
 *  initialize ad76xx
 */
static ms_err_t __ad76xx_init(privinfo_t *priv)
{
    ms_err_t err;

    AD_RESET_H();

    err = __ad76xx_hw_init(priv);
    if (err == MS_ERR_NONE) {
        __ad76xx_reg_set(priv);
        err = ms_semb_create("ad76xx_semb", MS_FALSE, MS_WAIT_TYPE_PRIO, &priv->semb);
        if (err == MS_ERR_NONE) {

            /*
             *  install int call back
             */
            priv->port->install_isr(priv->port->int_gpio, __ad76xx_isr, priv);

            /*
             *  enable int
             */
            priv->port->int_enable(priv->port->int_gpio, MS_TRUE);

        } else {
            ms_printk(MS_PK_ERR, "ad76xx semb create failed.\n");
        }
    } else {
        ms_printk(MS_PK_ERR, "ad76xx hw init failed.\n");
    }

    return err;
}

/*
 *  deinit ad76xx
 */
static void __ad76xx_deinit(privinfo_t *priv)
{
    ms_semb_destroy(priv->semb);
}

/*
 * Open device
 */
static int __ad76xx_open(ms_ptr_t ctx, ms_io_file_t *file, int oflag, ms_mode_t mode)
{
    ms_atomic_inc(MS_IO_DEV_REF(file));

    return MS_ERR_NONE;
}

/*
 * Close device
 */
static int __ad76xx_close(ms_ptr_t ctx, ms_io_file_t *file)
{
    ms_atomic_dec(MS_IO_DEV_REF(file));

    return 0;
}

/*
 * Read device
 */
static ms_ssize_t __ad76xx_read(ms_ptr_t ctx, ms_io_file_t *file, ms_ptr_t buf, ms_size_t len)
{
    privinfo_t *priv = ctx;

    ms_semb_wait(priv->semb, MS_TIMEOUT_FOREVER);

    ms_mutex_lock(priv->lock, MS_TIMEOUT_FOREVER);

    __ad76xx_read_data(priv, buf, len / sizeof(ms_uint16_t));

    ms_mutex_unlock(priv->lock);

    /*
     * gpio int enable
     */
    priv->port->int_enable(priv->port->int_gpio, MS_TRUE);

    return len;
}

/*
 * ioctl
 */
static int __ad76xx_ioctl(ms_ptr_t ctx, ms_io_file_t *file, int cmd, ms_ptr_t arg)
{
    privinfo_t       *priv = ctx;
    ms_ad76xx_reg_op *op_param;
    int ret;

    ms_mutex_lock(priv->lock, MS_TIMEOUT_FOREVER);

    switch (cmd) {
        case MS_AD76XX_CMD_REG_OP:
            if (ms_access_ok(arg, sizeof(ms_ad76xx_reg_op), MS_ACCESS_RW)) {
                op_param = (ms_ad76xx_reg_op *)arg;
                __ad76xx_spi_read_write(priv, op_param->reg_addr, &op_param->reg_data, op_param->op_flag);
                ret = 0;
            } else {
                ms_thread_set_errno(EFAULT);
                ret = -1;
            }
            break;

        default:
            ms_thread_set_errno(EINVAL);
            ret = -1;
            break;
    }

    ms_mutex_unlock(priv->lock);

    return ret;
}

/*
 * Device operating function set
 */
static ms_io_driver_ops_t ms_ad76xx_drv_ops = {
        .type   = MS_IO_DRV_TYPE_CHR,
        .open   = __ad76xx_open,
        .close  = __ad76xx_close,
        .read   = __ad76xx_read,
        .ioctl  = __ad76xx_ioctl,
};

/*
 * Device driver
 */
static ms_io_driver_t ms_ad76xx_drv = {
        .nnode = {
            .name = MS_AD76XX_DRV_NAME,
        },
        .ops = &ms_ad76xx_drv_ops,
};

/*
 * Register ad76xx device driver
 */
ms_err_t ms_ad76xx_drv_register(void)
{
    return ms_io_driver_register(&ms_ad76xx_drv);
}

/*
 * Create ad76xx device file
 */
ms_err_t ms_ad76xx_dev_create(ms_ad76xx_port_t *ad76xx_port)
{
    ms_ad76xx_dev_t *dev;
    ms_err_t err = MS_ERR;
    int i;

    if ((ad76xx_port                != MS_NULL) &&
        (ad76xx_port->bus_name      != MS_NULL) &&
        (ad76xx_port->dev_path      != MS_NULL) &&
        (ad76xx_port->dev_name      != MS_NULL) &&
        (ad76xx_port->int_gpio_path != MS_NULL) &&
        (ad76xx_port->gpio_init     != MS_NULL) &&
        (ad76xx_port->int_init      != MS_NULL) &&
        (ad76xx_port->install_isr   != MS_NULL) &&
        (ad76xx_port->int_enable    != MS_NULL) &&
        (ad76xx_port->cs            != MS_NULL) &&
        (ad76xx_port->reset         != MS_NULL)) {
        for (i = 0; i < MS_ARRAY_SIZE(ms_ad76xx_devices); i++) {
            if (strcmp(ms_ad76xx_devices[i].name, ad76xx_port->dev_name) == 0) {
                dev = ms_kmalloc(sizeof(ms_ad76xx_dev_t));
                if (dev != MS_NULL) {
                    privinfo_t *priv = &dev->priv;

                    /*
                     * Make sure clear priv.slots
                     */
                    bzero(priv, sizeof(privinfo_t));

                    priv->port = ad76xx_port;
                    priv->spi_dev.cs         = ad76xx_port->cs;
                    priv->spi_dev.nnode.name = ad76xx_port->dev_name;

                    err = ms_spi_device_attach(&priv->spi_dev, ad76xx_port->bus_name);
                    if (err == MS_ERR_NONE) {
                        err = __ad76xx_init(priv);
                        if (err == MS_ERR_NONE) {
                            err = ms_mutex_create("ad76xx_lock", MS_WAIT_TYPE_PRIO, &priv->lock);
                            if (err == MS_ERR_NONE) {
                                err = ms_io_device_register(&dev->dev,
                                                            ad76xx_port->dev_path,
                                                            MS_AD76XX_DRV_NAME,
                                                            &dev->priv);
                            }

                            if (err != MS_ERR_NONE) {
                                __ad76xx_deinit(priv);
                            }
                        }

                        if (err != MS_ERR_NONE) {
                            ms_spi_device_detach(&priv->spi_dev, ad76xx_port->bus_name);
                        }
                    }

                    if (err != MS_ERR_NONE) {
                        (void)ms_kfree(dev);
                    }
                } else {
                    err = MS_ERR_KERN_HEAP_NO_MEM;
                }
            }
        }
    } else {
        err = MS_ERR_ARG_NULL_PTR;
    }

    return err;
}

附录(Appendix)

1. Reference

STM32 SPIopen in new window

2. FAQ

(1)Stand SPI、Dual SPI 和 Qual SPI?

对于 SPI Flash,有标准 spi flash、dual spi、qual spi 三种类型,分别对应 3-wire、4-wire、6-wire,在相同 clock 下,线数越多,传输速率越高。

  • Stand SPI:标准 SPI 通常就称 SPI,它是一种串行外设接口规范,有4根引脚信号:clk、cs、mosi、miso 这是全双工模式,输入和输出可以同时进行(mosi、miso 分别对应的是 DI 和 DO)。

  • Dual SPI:它只是针对 SPI Flash 而言,不是针对所有 SPI 外设。对于 SPI Flash,全双工并不常用,那么就有人想着让 MOSI、MISO 同时向一个方向传输数据,变成半双工通信, 每一个时钟传输两个位,这样传输速度不就加倍了吗?因此扩展了 mosi 和 miso 的用法,让它们工作在半双工,用以加倍数据传输。也就是对于 Dual SPI Flash,可以发送一个命令字节进入 dual mode,这样 mosi 变成 SIO0(serial io 0),mosi 变成 SIO1(serial io 1),这样一个时钟周期内就能传输 2 个 bit 数据,加倍了数据传输,如果传输八个位,那么 MOSI 传输偶数位 0 2 4 6, MISO 传输奇数位 1 3 5 7。6 根引脚信号:CLK,/CS,IO0,IO1,/WP,/Hold。这是半双工模式,mosi 和 miso 对应的是 IO0 和 IO1,这样单次 SPI 的传输就可以传输 2 个 Bit。

  • Qual SPI:与 Dual SPI 类似,也是针对 SPI Flash,Qual SPI Flash 增加了两根 I/O 线(SIO2,SIO3),目的是一个时钟内传输 4 个 bit,如果传输 8 个位,那么 MOSI 传输 0 4, MISO 传输 1 5。SIO2 传输 2 6,SIO3 传输 3 7。引脚信号:CLK,/CS,IO0,IO1,/WP(IO2),/Hold(IO3)。同样是半双工模式,Qual SPI增加了 2 个 IO 口(WP ,HOLD),增加的目的是将 SPI 的单次传输数据量加大到 4 个 Bit。

文档内容是否对您有所帮助?
有帮助
没帮助