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 通讯使用 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。
(2)SPI 信号和响应
SPI 一共有四种通讯模式,它们的主要区别是总线空闲时 SCK 的时钟状态以及数据采样时刻。为方便说明,在此引入时钟极性 CPOL 和时钟相位 CPHA 的概念。
SPI 模式 | CPOL(时钟极性) | CPHA(时钟相位) | 空闲时 SCK 时钟 | 数据采样时刻 |
---|---|---|---|---|
0 | 0 | 0 | 低电平 | 奇数边沿 |
1 | 0 | 1 | 低电平 | 偶数边沿 |
2 | 1 | 0 | 高电平 | 奇数边沿 |
3 | 1 | 1 | 高电平 | 偶数边沿 |
时钟极性 CPOL 是指 SPI 通讯设备处于空闲状态时,SCK 信号线的电平信号(即 SPI 通讯开始前、 NSS 线为高电平时 SCK 的状态)。CPOL = 0 时, SCK 在空闲状态时为低电平,CPOL = 1 时,则相反。
时钟相位 CPHA 是指数据的采样的时刻,当 CPHA = 0 时,MOSI 或 MISO 数据线上的信号将会在 SCK 时钟线的奇数边沿被采样。当 CPHA = 1 时,数据线在 SCK 的偶数边沿采样。
1.3 STM32 的 SPI 架构
关于 STM32 SPI 的使用,可以自行在网上搜索。
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 总线的过程。
上图由上到下分别为:应用层、IO 驱动层、子系统框架层、驱动适配层。不一定所有类型的驱动都有驱动框架。在注册 SPI 总线驱动时,ms_spi_bus_t
将作为 SPI 总线控制器设备的 privinfo_t
的一员;在应用程序操作 SPI 总线时,将调用到 ms_spi_bus_drv_ops
,实际上将调用 privinfo_t
里 ms_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_SLAVE | SPI 从机模式 |
MS_SPI_MODE_MASTER | SPI 主机模式 |
- SPI 数据线数(direction)
可选配置 | 描述 |
---|---|
MS_SPI_DIR_MODE_2LINES | SPI 使用两线模式 |
MS_SPI_DIR_MODE_2LINES_RXONLY | SPI 使用两线模式,只使能接收 |
MS_SPI_DIR_MODE_1LINE | SPI 使用一线模式 |
- SPI 数据帧大小(data_size)
可选配置 | 描述 |
---|---|
MS_SPI_DATA_SIZE_8BIT | SPI 数据帧大小为 8bit |
MS_SPI_DATA_SIZE_16BIT | SPI 数据帧大小为 16bit |
- SPI 时钟/相位/大小端(frame_mode)
可选配置 | 描述 |
---|---|
MS_SPI_CLK_POLARITY_LOW | SPI 时钟极性,总线空闲时 SCLK 引脚为低电平 |
MS_SPI_CLK_POLARITY_HIGH | SPI 时钟极性,总线空闲时 SCLK 引脚为高电平 |
MS_SPI_CLK_PHASE_1EDGE | SPI 时钟相位,在 SCLK 第一个变化沿捕获数据 |
MS_SPI_CLK_PHASE_2EDGE | SPI 时钟相位,在 SCLK 第二个变化沿捕获数据 |
MS_SPI_FIRST_BIT_MSB | SPI 数据端序,总线上先发送 MSB |
MS_SPI_FIRST_BIT_LSB | SPI 数据端序,总线上先发送 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_BEGIN | Assert CS before transfer |
MS_SPI_M_END | Deassert CS after transfer |
MS_SPI_M_ONCE | Assert CS before transfer and deassert CS after transfer |
MS_SPI_M_TX_FIX | Only transmit tx_buf[0] in this transfer |
MS_SPI_M_RX_FIX | Only store recieve data to rx_buf[0] in this transfer |
MS_SPI_M_READ | MSG use read operation |
MS_SPI_M_WRITE | MSG 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 = ℞
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 = ℞
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
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。