【龙芯1c库】封装硬件SPI接口和使用示例

龙芯1c库是把龙芯1c的常用外设的常用功能封装为一个库,类似于STM32库。完整源码请移步到https://gitee.com/caogos/OpenLoongsonLib1c

本文首先介绍龙芯1c库中封装的硬件SPI相关的几个接口函数,然后使用双路16位ADC芯片TM7705来测试硬件SPI相关接口是否正常工作,然后以linux为参考,分析了硬件SPI接口的要点,最后才是接口函数的源码清单。

接口简介

SPI初始化——spi_init()

函数原型

/*
 * 初始化指定SPI模块
 * @spi_info_p SPI模块信息
 */
void spi_init(ls1c_spi_info_t *spi_info_p);

入参的类型如下

// 硬件SPI信息
typedef struct
{
    ls1c_spi_t SPIx;                    // SPI模块编号
    unsigned long max_speed_hz;         // 最大通信速度,单位hz
    unsigned char cs;                   // 片选
    unsigned char cpol;                 // 时钟极性
    unsigned char cpha;                 // 时钟相位
}ls1c_spi_info_t;

结构体中成员可能的取值如下

// SPI模块编号
typedef enum
{
    LS1C_SPI_0 = 0,
    LS1C_SPI_1,
}ls1c_spi_t;

// 片选
#define LS1C_SPI_INVALID_CS             (-1)
#define LS1C_SPI_CS_0                   (0)
#define LS1C_SPI_CS_1                   (1)
#define LS1C_SPI_CS_2                   (2)
#define LS1C_SPI_CS_3                   (3)

// 时钟极性和相位
#define SPI_CPOL_1                      (1)
#define SPI_CPOL_0                      (0)
#define SPI_CPHA_1                      (1)
#define SPI_CPHA_0                      (0)

使用示例

假设将TM7705接在SPI0的CS1上,通信速度为100khz,cpol=1,cpha=1,那么初始化SPI的代码如下

ls1c_spi_info_t tm7705_spi_info = {0};

tm7705_spi_info.SPIx   = LS1C_SPI_0;
tm7705_spi_info.cs     = LS1C_SPI_CS_1;
tm7705_spi_info.max_speed_hz   = 100*1000;
tm7705_spi_info.cpol   = SPI_CPOL_1;
tm7705_spi_info.cpha   = SPI_CPHA_1;

spi_init(&tm7705_spi_info);

设置片选——spi_set_cs()

函数原型

/*
 * 设置指定片选为指定状态
 * @spi_info_p SPI模块信息
 * @new_status 片选引脚的新状态,取值为0或1,即高电平或低电平
 */
void spi_set_cs(ls1c_spi_info_t *spi_info_p, int new_status);

使用示例

spi_set_cs(&tm7705_spi_info, 0);

把cs拉低

spi_set_cs(&tm7705_spi_info, 1);

把cs拉高

收发一个字节——spi_txrx_byte()

函数原型

/*
 * 通过指定SPI发送接收一个字节
 * 注意,在多任务的系统中,此函数需要互斥。
 * 即保证在和某个从设备收发某个字节的过程中,不能被切换到其它任务同时与另外的在同一个SPI总线上的从设备通信
 * 因为龙芯1c的每路SPI上可能接有不同的从设备,通信频率、模式等可能不同
 * @spi_info_p SPI接口
 * @tx_ch 待发送的数据
 * @ret 收到的数据
 */
unsigned char spi_txrx_byte(ls1c_spi_info_t *spi_info_p, unsigned char tx_ch);

SPI的特性就是接收和发送是同时进行的,所以函数spi_txrx_byte()是收发一体的。如果只关注发送,那么可以忽略返回值。如果只是关注接收,那么可以把发送的内容设为0。如果发送和返回值都关心,那么入参和返回值都有意义。

使用示例

unsigned char tx = 0;
tx = TM7705_REG_DATA | TM7705_READ | channel;
spi_txrx_byte(&tm7705_spi_info, tx);

把需要发送的内容发送出去了,接收到的信息被忽略了,即函数spi_txrx_byte()的返回值被忽略了。

unsigned char rx[2] = {0};
rx[0] = spi_txrx_byte(&tm7705_spi_info, 0);
rx[1] = spi_txrx_byte(&tm7705_spi_info, 0);

只关注接收到的信息,发送的内容可以为任意值,这里选择发送0。

注意事项

龙芯1c的支持两路SPI,每路都有4个片选。在每路的不同片选上可能接有不同的SPI从设备,使用不同的时钟、cpol和cpha。
Linux中,同一个SPI不同片选上所有从设备的收发都是集中在一个线程中完成的。虽然解决了互斥问题,但是在每次收发之前,可能需要重新设置spi时钟,cpol和cpha等,具体是调用ls1x_spi_setup_transfer()来实现的。
如果不采用linux这种方式,即同一个SPI的不同片选上的设备分别由不同的任务来单独执行收发,这时就需要注意互斥了。比如出现一个任务还未收发完成,就调换到另外一个任务中对另外一个spi从设备收发数据。
Linux中的函数ls1x_spi_setup_transfer()中的内容已整合到龙芯1c库中的spi_init()了,所以当在不同设备间切换时,需要调用spi_init()和spi_set_cs()重新设置spi相关寄存器,然后再调用spi_txrx_byte()收发数据。

打印指定SPI模块的所有寄存器的值(调试用)——spi_print_all_regs_info()

函数原型

/*
 * 打印指定SPI模块的所有寄存器的值
 * @spi_info_p SPI模块信息
 */
void spi_print_all_regs_info(ls1c_spi_info_t *spi_info_p);

使用示例

spi_print_all_regs_info(&tm7705_spi_info);

将tm7705所在spi模块的所有寄存器信息打印出来,其中变量tm7705_spi_info已在前面初始化了。

测试示例——使用ADC芯片TM7705

如果对TM7705的电路和具体使用细节还不大了解,请先移步到

《【龙印】在龙芯1c上用TM7705+NTC热敏电阻实现温度测量 》http://blog.csdn.net/caogos/article/details/53126628
《【龙印】龙芯1c上双路16位AD芯片TM7705的linux驱动 》http://blog.csdn.net/caogos/article/details/53034196

实物图


重点代码分析

函数tm7705_init()

/*
 * tm7705初始化
 */
void tm7705_init(void)
{
    // 初始化DRDY和RESET引脚
    gpio_init(TM7705_DRDY_PIN, gpio_mode_input);
    gpio_init(TM7705_RESET_PIN, gpio_mode_output);
    gpio_set(TM7705_RESET_PIN, gpio_level_high);

    // 初始化SPI

    // tm7705在SPI0的CS1上,通信速度为100khz,cpol=1,cpha=1
    tm7705_spi_info.SPIx   = LS1C_SPI_0;
    tm7705_spi_info.cs     = LS1C_SPI_CS_1;

/*
    // SPI0 CS3
    tm7705_spi_info.SPIx   = LS1C_SPI_0;
    tm7705_spi_info.cs     = LS1C_SPI_CS_3;
*/
/*
    // SPI1 CS0
    pin_set_remap(LS1C_SPI_1_MISO_GPIO, PIN_REMAP_THIRD);
    pin_set_remap(LS1C_SPI_1_MOSI_GPIO, PIN_REMAP_THIRD);
    pin_set_remap(LS1C_SPI_1_CLK_GPIO,  PIN_REMAP_THIRD);
    pin_set_remap(LS1C_SPI_1_CS_0_GPIO, PIN_REMAP_THIRD);   // cs0
    tm7705_spi_info.SPIx   = LS1C_SPI_1;
    tm7705_spi_info.cs     = LS1C_SPI_CS_0;                 // cs0
*/
/*
    // SPI1 CS1
    pin_set_remap(LS1C_SPI_1_MISO_GPIO, PIN_REMAP_THIRD);
    pin_set_remap(LS1C_SPI_1_MOSI_GPIO, PIN_REMAP_THIRD);
    pin_set_remap(LS1C_SPI_1_CLK_GPIO,  PIN_REMAP_THIRD);
    pin_set_remap(LS1C_SPI_1_CS_1_GPIO, PIN_REMAP_THIRD);   // cs1
    tm7705_spi_info.SPIx   = LS1C_SPI_1;
    tm7705_spi_info.cs     = LS1C_SPI_CS_1;                 // cs1
*/

    tm7705_spi_info.max_speed_hz   = 100*1000;
    tm7705_spi_info.cpol   = SPI_CPOL_1;
    tm7705_spi_info.cpha   = SPI_CPHA_1;

    spi_init(&tm7705_spi_info);
    
    // 复位tm7705并重新配置
    tm7705_reset_and_reconfig();

    return ;
}

重点关注spi初始化那部分,即设置全局变量tm7705_spi_info。我把TM7705接在SPI0的CS1、CS3,SPI1的CS0、CS1,共测试了4种情况,其它片选也类似。除了设置全局变量tm7705_spi_info,调用spi_init()外,需要复用的可以调用函数pin_set_remap()设置相应的复用。

函数tm7705_read_channel()

/*
 * 读取指定通道的ad值
 * @channel 通道
 * @adc_p 读取的AD值
 * @ret 成功 或 失败
 */
int tm7705_read_channel(int channel, unsigned short *adc_p)
{
    int ret = TM7705_RET_TIMEOUT;
    unsigned char tx = 0;
    unsigned char rx[2] = {0};
    unsigned short ad = 0;

    // 等待转换完成
    ret = tm7705_wait_DRDY();
    if (TM7705_RET_OK != ret)
    {
        myprintf("[%s] tm7705 timeout!\r\n", __FUNCTION__);
        return ret;
    }

    // 读
    spi_set_cs(&tm7705_spi_info, 0);
    delay_us(1);
    
    tx = TM7705_REG_DATA | TM7705_READ | channel;
    spi_txrx_byte(&tm7705_spi_info, tx);
    rx[0] = spi_txrx_byte(&tm7705_spi_info, 0);
    rx[1] = spi_txrx_byte(&tm7705_spi_info, 0);
    
    spi_set_cs(&tm7705_spi_info, 1);
    delay_us(1);
    
    ad = (rx[0] << 8) + rx[1];
    if (0xfff == ad)
    {
        myprintf("[%s] ad=0xfff\r\n", __FUNCTION__);
        return TM7705_RET_OTHER_ERR;
    }

    *adc_p = ad;

    return TM7705_RET_OK;
}

读取一次TM7705的流程是,先调用spi_set_cs()将cs拉低,然后调用spi_txrx_byte()发送命令并读取AD值,最后调用spi_set_cs()将cs拉高。
读取一次TM7705的过程中,包含三次调用spi_txrx_byte(),第一次调用为发送命令,所以忽略了返回值,第二次和第三次调用为读取AD值,主要是为了获取返回值,发送的数据是多少不重要,这里选择发送零,也可以选其它的。
注意,这是在裸机编程环境中测试的,可以认为测试时tm7705独占SPI0,。当在RT-Thread中,并且在SPI0上还接有其它设备时,需要注意互斥。

完整的测试代码清单

test_spi.c

// 测试硬件spi源文件

#include "../lib/ls1c_public.h"
#include "../lib/ls1c_pin.h"
#include "../lib/ls1c_spi.h"
#include "../lib/ls1c_gpio.h"
#include "../lib/ls1c_delay.h"


// spi复用
#define LS1C_SPI_1_CS_0_GPIO            (49)        // gpio49/spi1_cs0/CAMHSYNC
#define LS1C_SPI_1_CS_1_GPIO            (50)        // gpio50/spi1_cs1/CAMDATA0
#define LS1C_SPI_1_CS_2_GPIO            (51)        // gpio51/spi1_cs2/CAMDATA1
#define LS1C_SPI_1_CS_3_GPIO            (52)        // gpio52/spi1_cs3/CAMDATA2
#define LS1C_SPI_1_MISO_GPIO            (47)        // gpio47/spi1_miso/CAMCLKOUT
#define LS1C_SPI_1_MOSI_GPIO            (48)        // gpio48/spi1_mosi/CAMVSYNC
#define LS1C_SPI_1_CLK_GPIO             (46)        // gpio46/spi1_clk/CAMPCLKIN


// 通信寄存器bit定义
enum 
{
	// 寄存器选择  RS2 RS1 RS0
	TM7705_REG_COMM	        = (0 << 4), // 通信寄存器
	TM7705_REG_SETUP	    = (1 << 4), // 设置寄存器
	TM7705_REG_CLOCK	    = (2 << 4), // 时钟寄存器
	TM7705_REG_DATA	        = (3 << 4), // 数据寄存器
	TM7705_REG_TEST         = (4 << 4), // 测试寄存器
	TM7705_REG_OFFSET       = (6 << 4), // 偏移寄存器
	TM7705_REG_GAIN         = (7 << 4), // 增益寄存器
	
    // 读写操作
	TM7705_WRITE 		    = (0 << 3), // 写操作
	TM7705_READ 		    = (1 << 3), // 读操作

	// 通道
	TM7705_CH_1		        = 0,    // AIN1+  AIN1-
	TM7705_CH_2		        = 1,    // AIN2+  AIN2-
	TM7705_CH_3		        = 2,    // AIN1-  AIN1-
	TM7705_CH_4		        = 3     // AIN1-  AIN2-
};


/* 设置寄存器bit定义 */
enum
{
	TM7705_MD_NORMAL		= (0 << 6),	/* 正常模式 */
	TM7705_MD_CAL_SELF		= (1 << 6),	/* 自校准模式 */
	TM7705_MD_CAL_ZERO		= (2 << 6),	/* 校准0刻度模式 */
	TM7705_MD_CAL_FULL		= (3 << 6),	/* 校准满刻度模式 */

	TM7705_GAIN_1			= (0 << 3),	/* 增益 */
	TM7705_GAIN_2			= (1 << 3),	/* 增益 */
	TM7705_GAIN_4			= (2 << 3),	/* 增益 */
	TM7705_GAIN_8			= (3 << 3),	/* 增益 */
	TM7705_GAIN_16			= (4 << 3),	/* 增益 */
	TM7705_GAIN_32			= (5 << 3),	/* 增益 */
	TM7705_GAIN_64			= (6 << 3),	/* 增益 */
	TM7705_GAIN_128		    = (7 << 3),	/* 增益 */

	/* 无论双极性还是单极性都不改变任何输入信号的状态,它只改变输出数据的代码和转换函数上的校准点 */
	TM7705_BIPOLAR			= (0 << 2),	/* 双极性输入 */
	TM7705_UNIPOLAR		    = (1 << 2),	/* 单极性输入 */

	TM7705_BUF_NO			= (0 << 1),	/* 输入无缓冲(内部缓冲器不启用) */
	TM7705_BUF_EN			= (1 << 1),	/* 输入有缓冲 (启用内部缓冲器) */

	TM7705_FSYNC_0			= 0,    // 模拟调制器和滤波器正常处理数据
	TM7705_FSYNC_1			= 1		// 模拟调制器和滤波器不启用
};



/* 时钟寄存器bit定义 */
enum
{
	TM7705_CLKDIS_0	        = (0 << 4),		/* 时钟输出使能 (当外接晶振时,必须使能才能振荡) */
	TM7705_CLKDIS_1	        = (1 << 4),		/* 时钟禁止 (当外部提供时钟时,设置该位可以禁止MCK_OUT引脚输出时钟以省电 */

    TM7705_CLKDIV_0         = (0 << 3),     // 不分频
    TM7705_CLKDIV_1         = (1 << 3),     // 2分频,外部晶振为4.9152Mhz时,应2分频

    TM7705_CLK_0            = (0 << 2),     // 主时钟=1Mhz并且CLKDIV=0,主时钟=2Mhz并且CLKDIV=1
    TM7705_CLK_1            = (1 << 2),     // 主时钟=2.4576Mhz并且CLKDIV=0, 主时钟=4.9152Mhz并且CLKDIV=1

    // 注意输出更新率与clk位有关
    // 当TM7705_CLK_0时,输出更新率只能为20,25,100,200
    TM7705_UPDATE_20        = (0),
    TM7705_UPDATE_25        = (1),
    TM7705_UPDATE_100       = (2),
    TM7705_UPDATE_200       = (3),
    // 当TM7705_CLK_1时,输出更新率只能为50,60,250,500
    TM7705_UPDATE_50        = (0),
    TM7705_UPDATE_60        = (1),
    TM7705_UPDATE_250       = (2),
    TM7705_UPDATE_500       = (3)
};



#define TM7705_CHANNEL_NUM              (2)     // tm7705通道个数
#define TM7705_DRDY_PIN                 (87)    // GPIO87/I2S_DI   tm7705的引脚DRDY 
#define TM7705_RESET_PIN                (89)    // GPIO89/I2S_LRCK  tm7705的引脚RESET

#define TM7705_AD_MAX                   ((0x1 << 10) - 1)   // ad的最大值,只用了十位的精度
#define TM7705_IS_VALID_AD(ad)          ((TM7705_AD_MAX >= (ad)) && (0 <= (ad)))

// 返回值
#define TM7705_RET_TIMEOUT              (-1)    // 超时
#define TM7705_RET_OK                   (0)     // 正常返回
#define TM7705_RET_OTHER_ERR            (1)     // 其它错误


// 以下根据ntc热敏电阻参数用脚本生成的adc值与温度一一对应的表格
// 左边为adc值,右边为温度(单位:摄氏度)
// 详细请参考源码目录中的脚本"createTemperatureLookup.py"
// python createTemperatureLookup.py
// Thermistor lookup table for RepRap Temperature Sensor Boards (http://make.rrrf.org/ts)
// Made with createTemperatureLookup.py (http://svn.reprap.org/trunk/reprap/firmware/Arduino/utilities/createTemperatureLookup.py)
// ./createTemperatureLookup.py --r0=100000 --t0=25 --r1=0 --r2=4700 --beta=3950 --max-adc=1023
// r0: 100000
// t0: 25
// r1: 0
// r2: 4700
// beta: 3950
// max adc: 1023
#define TM7705_NTC_NUMTEMPS 40
const short tm7705_ntc_temptable[TM7705_NTC_NUMTEMPS][2] = {
   {1, 938},
   {27, 326},
   {53, 269},
   {79, 239},
   {105, 219},
   {131, 204},
   {157, 192},
   {183, 182},
   {209, 174},
   {235, 166},
   {261, 160},
   {287, 153},
   {313, 148},
   {339, 143},
   {365, 138},
   {391, 133},
   {417, 129},
   {443, 125},
   {469, 120},
   {495, 116},
   {521, 113},
   {547, 109},
   {573, 105},
   {599, 101},
   {625, 98},
   {651, 94},
   {677, 90},
   {703, 86},
   {729, 82},
   {755, 78},
   {781, 74},
   {807, 70},
   {833, 65},
   {859, 60},
   {885, 54},
   {911, 48},
   {937, 41},
   {963, 31},
   {989, 18},
   {1015, -8}
};



ls1c_spi_info_t tm7705_spi_info = {0};


/*
 * 通过RESET引脚复位tm7705
 */
void tm7705_reset(void)
{
    gpio_set(TM7705_RESET_PIN, gpio_level_high);
    delay_ms(1);
    gpio_set(TM7705_RESET_PIN, gpio_level_low);
    delay_ms(2);
    gpio_set(TM7705_RESET_PIN, gpio_level_high);
    delay_ms(1);

    return ;
}


/*
 * 同步SPI接口时序
 */
void tm7705_sync_spi(void)
{
    // 在至少32个串行时钟内向TM7705的DIN脚写入逻辑'1'
    spi_set_cs(&tm7705_spi_info, 0);
    delay_us(1);
    
    spi_txrx_byte(&tm7705_spi_info, 0xFF);
    spi_txrx_byte(&tm7705_spi_info, 0xFF);
    spi_txrx_byte(&tm7705_spi_info, 0xFF);
    spi_txrx_byte(&tm7705_spi_info, 0xFF);
    
    spi_set_cs(&tm7705_spi_info, 1);
    delay_us(0);

    return ;
}


/*
 * 等待内部操作完成, 时间较长,约180ms
 * @ret 成功 或者 超时
 */
int tm7705_wait_DRDY(void)
{
    int i = 0;
    int time_cnt = 500;

    for (i=0; i<time_cnt; i++)
    {
        if (gpio_level_low == gpio_get(TM7705_DRDY_PIN))
        {
            break;
        }
        delay_ms(1);
    }

    if (i >= time_cnt)
    {
        return TM7705_RET_TIMEOUT;
    }

    return TM7705_RET_OK;
}


/*
 * 自校准
 * @channel 通道
 */
void tm7705_calib_self(unsigned char channel)
{
    unsigned char val = 0;

    spi_set_cs(&tm7705_spi_info, 0);
    delay_us(1);

    val = TM7705_REG_SETUP | TM7705_WRITE | channel;
    spi_txrx_byte(&tm7705_spi_info, val);
    val = TM7705_MD_CAL_SELF | TM7705_GAIN_1 | TM7705_UNIPOLAR | TM7705_BUF_EN | TM7705_FSYNC_0;
    spi_txrx_byte(&tm7705_spi_info, val);

    spi_set_cs(&tm7705_spi_info, 1);
    delay_us(1);

    // 等待内部操作完成, 时间较长,约180ms
    tm7705_wait_DRDY();

    delay_ms(50);

    return ;
}



/*
 * 配置tm7705的指定通道
 * @channel 通道
 */
void tm7705_config_channel(unsigned char channel)
{
    unsigned char val = 0;

    spi_set_cs(&tm7705_spi_info, 0);
    delay_us(1);

    val = TM7705_REG_CLOCK | TM7705_WRITE | channel;
    spi_txrx_byte(&tm7705_spi_info, val);
    val = TM7705_CLKDIS_0 | TM7705_CLKDIV_1 | TM7705_CLK_1 | TM7705_UPDATE_50;
    spi_txrx_byte(&tm7705_spi_info, val);

    spi_set_cs(&tm7705_spi_info, 1);
    delay_us(1);

    // 自校准
    tm7705_calib_self(channel);

    return ;
}


/*
 * 复位tm7705并重新配置
 */
void tm7705_reset_and_reconfig(void)
{
    // 通过RESET引脚复位tm7705
    tm7705_reset();

    // 同步SPI接口时序
    delay_ms(5);
    tm7705_sync_spi();
    delay_ms(5);

    // 配置tm7705的指定通道
    tm7705_config_channel(TM7705_CH_1);

    return ;
}


/*
 * tm7705初始化
 */
void tm7705_init(void)
{
    // 初始化DRDY和RESET引脚
    gpio_init(TM7705_DRDY_PIN, gpio_mode_input);
    gpio_init(TM7705_RESET_PIN, gpio_mode_output);
    gpio_set(TM7705_RESET_PIN, gpio_level_high);

    // 初始化SPI
/*    
    // tm7705在SPI0的CS1上,通信速度为100khz,cpol=1,cpha=1
    tm7705_spi_info.SPIx   = LS1C_SPI_0;
    tm7705_spi_info.cs     = LS1C_SPI_CS_1;
*/
/*
    // SPI0 CS3
    tm7705_spi_info.SPIx   = LS1C_SPI_0;
    tm7705_spi_info.cs     = LS1C_SPI_CS_3;
*/
/*
    // SPI1 CS0
    pin_set_remap(LS1C_SPI_1_MISO_GPIO, PIN_REMAP_THIRD);
    pin_set_remap(LS1C_SPI_1_MOSI_GPIO, PIN_REMAP_THIRD);
    pin_set_remap(LS1C_SPI_1_CLK_GPIO,  PIN_REMAP_THIRD);
    pin_set_remap(LS1C_SPI_1_CS_0_GPIO, PIN_REMAP_THIRD);   // cs0
    tm7705_spi_info.SPIx   = LS1C_SPI_1;
    tm7705_spi_info.cs     = LS1C_SPI_CS_0;                 // cs0
*/

    // SPI1 CS1
    pin_set_remap(LS1C_SPI_1_MISO_GPIO, PIN_REMAP_THIRD);
    pin_set_remap(LS1C_SPI_1_MOSI_GPIO, PIN_REMAP_THIRD);
    pin_set_remap(LS1C_SPI_1_CLK_GPIO,  PIN_REMAP_THIRD);
    pin_set_remap(LS1C_SPI_1_CS_1_GPIO, PIN_REMAP_THIRD);   // cs1
    tm7705_spi_info.SPIx   = LS1C_SPI_1;
    tm7705_spi_info.cs     = LS1C_SPI_CS_1;                 // cs1


    tm7705_spi_info.max_speed_hz   = 100*1000;
    tm7705_spi_info.cpol   = SPI_CPOL_1;
    tm7705_spi_info.cpha   = SPI_CPHA_1;

    spi_init(&tm7705_spi_info);
    
    // 复位tm7705并重新配置
    tm7705_reset_and_reconfig();

    return ;
}


/*
 * 读取指定通道的ad值
 * @channel 通道
 * @adc_p 读取的AD值
 * @ret 成功 或 失败
 */
int tm7705_read_channel(int channel, unsigned short *adc_p)
{
    int ret = TM7705_RET_TIMEOUT;
    unsigned char tx = 0;
    unsigned char rx[2] = {0};
    unsigned short ad = 0;

    // 等待转换完成
    ret = tm7705_wait_DRDY();
    if (TM7705_RET_OK != ret)
    {
        myprintf("[%s] tm7705 timeout!\r\n", __FUNCTION__);
        return ret;
    }

    // 读
    spi_set_cs(&tm7705_spi_info, 0);
    delay_us(1);
    
    tx = TM7705_REG_DATA | TM7705_READ | channel;
    spi_txrx_byte(&tm7705_spi_info, tx);
    rx[0] = spi_txrx_byte(&tm7705_spi_info, 0);
    rx[1] = spi_txrx_byte(&tm7705_spi_info, 0);
    
    spi_set_cs(&tm7705_spi_info, 1);
    delay_us(1);
    
    ad = (rx[0] << 8) + rx[1];
    if (0xfff == ad)
    {
        myprintf("[%s] ad=0xfff\r\n", __FUNCTION__);
        return TM7705_RET_OTHER_ERR;
    }

    *adc_p = ad;

    return TM7705_RET_OK;
}



/*
 * 获取tm7705输出的AD值
 * @channel 通道
 * @adc_p 读取的AD值
 * @ret 成功 或者 失败
 */
int tm7705_get_ad(int channel, unsigned short *adc_p)
{
    int i = 0;
    int ret = TM7705_RET_TIMEOUT;
    unsigned short ad = 0;

    // 连续读2次
    // 第一次读取的值为上一次采集的结果,第二次读取的才是当前的结果     *
    for (i=0; i<2; i++)
    {
        ret = tm7705_read_channel(channel, &ad);
        if (TM7705_RET_OK != ret)
        {
            // 失败,则重启tm7705并重新配置
            tm7705_reset_and_reconfig();
            myprintf("[%s] tm7705 reset and reconfig!\r\n", __FUNCTION__);
            return ret;
        }

        // ls1c速度相对tm7705太快,延时一下避免在一次读完后DRDY还未及时改变状态
        delay_ms(1);
    }
//    spi_print_all_regs_info(&tm7705_spi_info);

    myprintf("[%s] ad=0x%x\r\n", __FUNCTION__, ad);

    ad = ad >> 6;       // 只需要10位的精度
    *adc_p = ad;

    return TM7705_RET_OK;
}


/*
 * 根据ad值计算温度值
 * @ad 从tm7705读取的AD值
 * @ret 温度值
 *
 * ntc热敏电阻的阻值温度曲线被分为n段,每段可以近似为直线
 * 所以温度值的计算就转变为查表再计算
 */
float tm7705_calc_temp_from_ad(unsigned short ad)
{
    float temp_f;
    int i = 0;
    
    // 判断ad值是否在量程范围内
    if (!(TM7705_IS_VALID_AD(ad)))
    {
        return 0;
    }

    // 判断是否在表格所表示的范围内
    if (ad < tm7705_ntc_temptable[0][0])                        // 小于表格的最小adc
    {
        return tm7705_ntc_temptable[0][1];                      // 取最小值
    }
    if (ad > tm7705_ntc_temptable[TM7705_NTC_NUMTEMPS-1][0])    // 大于表格的最大adc
    {
        return tm7705_ntc_temptable[TM7705_NTC_NUMTEMPS-1][1];  // 取最大值
    }

    // 查表
    for (i=1; i<TM7705_NTC_NUMTEMPS; i++)
    {
        if (ad < tm7705_ntc_temptable[i][0])
        {
//            myprintf("[%s] ad=%d\r\n", __FUNCTION__, ad);
            // t = t0 + (adc - adc0) * k
            temp_f = tm7705_ntc_temptable[i-1][1] +               // t0
                   (ad - tm7705_ntc_temptable[i-1][0]) *        // adc - adc0
                   ((float)(tm7705_ntc_temptable[i][1] - tm7705_ntc_temptable[i-1][1]) 
                    / (float)(tm7705_ntc_temptable[i][0] - tm7705_ntc_temptable[i-1][0]));
            return temp_f;
        }
    }

    return 0;
}


/*
 * 获取温度值
 * @ret 温度值
 */
float tm7705_get_temperature(void)
{
    int ret = TM7705_RET_TIMEOUT;
    float temp_f = 0;
    unsigned short ad = 0;

    // 获取AD值
    ret = tm7705_get_ad(TM7705_CH_1, &ad);
    if (TM7705_RET_OK != ret)
    {
        myprintf("[%s] tm7705_get_ad() fail, ret=%d\r\n", __FUNCTION__, ret);
        return ret;
    }

    // 计算温度值
//    temp_f = tm7705_calc_temp_from_ad(ad);

    return temp_f;
}


/*
 * 用tm7705测试硬件SPI
 * 具体为tm7705+ntc热敏电阻实现温度测量(3d打印机就可以采用此方案测量温度)
 */
void test_spi_tm7705(void)
{
    float temp_f;
    int temp = 0;       // 温度值
    
    // tm7705初始化
    tm7705_init();

    while (1)
    {
        // 获取温度值
        // 这里浮点运算有点小问题,暂不清楚原因,不知道是不是栈没有8字节对齐
        // 所以只把从tm7705读取的AD值打印出来
        temp_f = tm7705_get_temperature();
        temp = temp_f;
//        myprintf("[%s] temp=%d\r\n", __FUNCTION__, temp);

        // 延时
        delay_s(2);
    }
}





test_spi.h

// 测试硬件spi头文件

#ifndef __OPENLOONGSON_TEST_SPI_H
#define __OPENLOONGSON_TEST_SPI_H



/*
 * 用tm7705测试硬件SPI
 * 具体为tm7705+ntc热敏电阻实现温度测量(3d打印机就可以采用此方案测量温度)
 */
void test_spi_tm7705(void);


#endif


main.c

#include "../lib/ls1c_public.h"
#include "../lib/ls1c_irq.h"
#include "../lib/ls1c_gpio.h"
#include "../lib/ls1c_delay.h"
#include "../lib/ls1c_mipsregs.h"
#include "../lib/ls1c_uart.h"
#include "../lib/ls1c_sys_tick.h"
#include "../example/test_gpio.h"
#include "../example/test_pwm.h"
#include "../example/test_delay.h"
#include "../example/test_simulate_i2c.h"
#include "../example/test_timer.h"
#include "../example/test_fpu.h"
#include "../example/test_i2c.h"
#include "../example/test_uart.h"
#include "../example/test_sys_tick.h"
#include "../example/test_spi.h"



// pmon提供的打印接口
struct callvectors *callvec;


// 硬浮点初始化
void fpu_init(void)
{
    unsigned int c0_status = 0;
    unsigned int c1_status = 0;

    // 使能协处理器1--FPU
    c0_status = read_c0_status();
    c0_status |= (ST0_CU1 | ST0_FR);
    write_c0_status(c0_status);

    // 配置FPU
    c1_status = read_c1_status();
    c1_status |= (FPU_CSR_FS | FPU_CSR_FO | FPU_CSR_FN);    // set FS, FO, FN
    c1_status &= ~(FPU_CSR_ALL_E);                          // disable exception
    c1_status = (c1_status & (~FPU_CSR_RM)) | FPU_CSR_RN;   // set RN
    write_c1_status(c1_status);

    return ;
}


void bsp_init(void)
{
    // 初始化调试串口
    uart2_init();
    
    // 硬浮点初始化
    fpu_init();

    /*
     * 滴答定时器初始化
     * 必须在中断使能之前初始化滴答定时器
     * 否则由于默认的滴答定时器定时时间太短,导致反复进入中断,影响正常使用
     */
    sys_tick_init();

    // 初始化异常
    exception_init();

    return ;
}


int main(int argc, char **argv, char **env, struct callvectors *cv)
{
	callvec = cv;       // 这条语句之后才能使用pmon提供的打印功能

    bsp_init();

    // -------------------------测试gpio----------------------
    /*
     * 测试库中gpio作为输出时的相关接口
     * led闪烁10次
     */
//    test_gpio_output();
    
    /*
     * 测试库中gpio作为输入时的相关接口
     * 按键按下时,指示灯点亮,否则,熄灭
     */
//    test_gpio_input();

    /*
     * 测试库中外部中断(gpio输入中断)的相关接口
     * 按键被按下后,会产生一个中断
     */
//    test_gpio_key_irq();


    // ------------------------测试串口-----------------------
    // 通过串口2打印调试信息
//    test_uart2_print();


    // ------------------------测试PWM--------------------------------    
    // 测试硬件pwm产生连续的pwm波形
//    test_pwm_normal();

    // 测试硬件pwm产生pwm脉冲
//    test_pwm_pulse();
    
    /*
     * 测试gpio04复用为pwm,gpio06作为普通gpio使用
     * PWM0的默认引脚位GPIO06,但也可以复用为GPIO04
     * 当gpio06还是保持默认为pwm时,复用gpio04为pwm0,那么会同时在两个引脚输出相同的pwm波形
     * 本函数旨在证明可以在gpio04复用为pwm0时,还可以将(默认作为pwm0的)gpio06作为普通gpio使用
     */
//    test_pwm_gpio04_gpio06();

    // 测试pwm最大周期
//    test_pwm_max_period();


    // ------------------------测试软件延时--------------------------------  
    // 测试延时函数delay_1ms()
//    test_delay_1ms();
    
    // 测试延时函数delay_1us()
//    test_delay_1us();
    
    // 测试延时函数delay_1s()
//    test_delay_1s();


    // ------------------------测试模拟I2C------------------------------  
    // 测试模拟I2C
//    test_simulate_i2c_am2320();

    // ------------------------测试硬件I2C---------------------------
    // 用温湿度传感器测试硬件i2c
//    test_i2c_am2320();


    // ------------------------测试硬件定时器---------------------------  
    // 测试硬件定时器的定时功能(读取中断状态位的方式判断是否超时)
//    test_timer_poll_time_out();

    // 测试硬件定时器的中断
//    test_timer_irq();

    // 测试硬件定时器的计时
//    test_timer_get_time();

    // ------------------------测试硬浮点(FPU)---------------------------
    // 测试使用硬浮点进行浮点数的加减乘除
//    test_fpu();

    // ------------------------测试滴答定时器---------------------------
    // 通过获取当前tick值来测试滴答定时器,默认已经使能了滴答定时器,每秒1000个tick
//    test_sys_tick();


    // ------------------------测试硬件SPI---------------------------
    // 用tm7705测试硬件SPI
    // 具体为tm7705+ntc热敏电阻实现温度测量(3d打印机就可以采用此方案测量温度)
    test_spi_tm7705();


    while (1)
        ;

	return(0);
}




更多更完整的代码,请移步到龙芯1c库的git上查看(本文最开始的时候已经把git地址给出来了)。

测试结果

串口打印

Press <Enter> to execute loading image:tftp://192.168.1.3/OpenLoongsonLib1c
Press any other key to abort.
00
Loading file: tftp://192.168.1.3/OpenLoongsonLib1c (elf)
0x80200000/20496 + 0x80205010/5520(z) + 180 syms-
Entry address is 80204ac8
g root=/dev/nfs rw nfsroot=192.168.1.4:/nfsramdisk/LS1xrootfs-demo noinitrd init=/linuxrc console=ttyS2,115200 ip=192.168.1.254:::::eth0:off
   zero      at       v0       v1       a0       a1       a2       a3   
 00000000 00000000 00000000 00000000 00000008 a10ffbf0 a10ffc14 8008e650
    t0       t1       t2       t3       t4       t5       t6       t7   
 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
    s0       s1       s2       s3       s4       s5       s6       s7   
 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
    t8       t9       k0       k1       gp       sp       s8       ra   
 00000000 00000000 00000000 00000000 00000000 a10ffbd0 00000000 8005a840
[tm7705_get_ad] ad=0xd
[tm7705_get_ad] ad=0xd
[tm7705_get_ad] ad=0xd
[tm7705_get_ad] ad=0xd

龙芯1C的硬件SPI简介

每路SPI支持4个片选,也就是说在同一个SPI(SPI0或SPI1)上,有可能接有多个SPI从设备,通过片选来控制当前访问的是那个SPI从设备。有可能这几个从设备的时钟、通信模式(CPOL和CPHA)不同,也就是说每次发送时都最好重新设置一下。在多任务的实时系统中需要注意互斥,以保证每个字节能正常的收发完成。

封装硬件SPI接口

接口要点

要封装硬件SPI接口,需要对SPI协议有一定了解,如果还不是很了解,先百度一下。
除了对SPI协议有一定了解之外,当然肯定还要对龙芯1C处理器中SPI那部分比较熟悉才行。这可以到龙芯官网下载龙芯1C处理器用户手册。虽然手册中把每个寄存器中每一位的用途都讲了,但仅靠看手册想把硬件SPI接口封装出来估计有一定难度。所以我是参考linux中的代码来封装硬件SPI接口的。Linux中龙芯1C的SPI相关源码位于drivers\spi\spi_ls1x.c。

重点看上图中箭头所指的那几个函数。
其中linux中的ls1x_spi_hw_init(),ls1x_spi_div(),ls1x_spi_setup(),ls1x_spi_setup_transfer()被整合为龙芯1C库中的spi_init();
Linux中的ls1x_spi_chipselect()对应龙芯1C库中的spi_set_cs();
Linux中的ls1x_spi_txrx_bufs()是收发n个字节的,在龙芯1C库中改为了收发单个字节的spi_txrx_byte()。
这就是大致的对应关系。

Linux中硬件SPI相关源码(供对比参考)

Linux中一次spi传输的详细过程

Linux中spi的数据传输流程参考了网上的一篇文章——《和菜鸟一起学linux总线驱动之初识spi驱动数据传输流程》http://blog.csdn.net/eastmoon502136/article/details/7921846
bitbang_work()中执行一次读写任务,依次调用bitbang->setup_transfer(),bitbang->chipselect(),bitbang->txrx_bufs(),bitbang->chipselect()。
ls1x_spi_probe()中注册这几个函数为ls1x_spi_setup_transfer(),ls1x_spi_chipselect(),ls1x_spi_txrx_bufs()


函数bitbang_work()所在目录为drivers\spi\spi_bitbang.c,源码如下

/*
 * SECOND PART ... simple transfer queue runner.
 *
 * This costs a task context per controller, running the queue by
 * performing each transfer in sequence.  Smarter hardware can queue
 * several DMA transfers at once, and process several controller queues
 * in parallel; this driver doesn't match such hardware very well.
 *
 * Drivers can provide word-at-a-time i/o primitives, or provide
 * transfer-at-a-time ones to leverage dma or fifo hardware.
 */
static void bitbang_work(struct work_struct *work)
{
	struct spi_bitbang	*bitbang =
		container_of(work, struct spi_bitbang, work);
	unsigned long		flags;

	spin_lock_irqsave(&bitbang->lock, flags);
	bitbang->busy = 1;
	while (!list_empty(&bitbang->queue)) {
		struct spi_message	*m;
		struct spi_device	*spi;
		unsigned		nsecs;
		struct spi_transfer	*t = NULL;
		unsigned		tmp;
		unsigned		cs_change;
		int			status;
		int			do_setup = -1;

		m = container_of(bitbang->queue.next, struct spi_message,
				queue);
		list_del_init(&m->queue);
		spin_unlock_irqrestore(&bitbang->lock, flags);

		/* FIXME this is made-up ... the correct value is known to
		 * word-at-a-time bitbang code, and presumably chipselect()
		 * should enforce these requirements too?
		 */
		nsecs = 100;

		spi = m->spi;
		tmp = 0;
		cs_change = 1;
		status = 0;

		list_for_each_entry (t, &m->transfers, transfer_list) {

			/* override speed or wordsize? */
			if (t->speed_hz || t->bits_per_word)
				do_setup = 1;

			/* init (-1) or override (1) transfer params */
			if (do_setup != 0) {
				status = bitbang->setup_transfer(spi, t);
				if (status < 0)
					break;
				if (do_setup == -1)
					do_setup = 0;
			}

			/* set up default clock polarity, and activate chip;
			 * this implicitly updates clock and spi modes as
			 * previously recorded for this device via setup().
			 * (and also deselects any other chip that might be
			 * selected ...)
			 */
			if (cs_change) {
				bitbang->chipselect(spi, BITBANG_CS_ACTIVE);
				ndelay(nsecs);
			}
			cs_change = t->cs_change;
			if (!t->tx_buf && !t->rx_buf && t->len) {
				status = -EINVAL;
				break;
			}

			/* transfer data.  the lower level code handles any
			 * new dma mappings it needs. our caller always gave
			 * us dma-safe buffers.
			 */
			if (t->len) {
				/* REVISIT dma API still needs a designated
				 * DMA_ADDR_INVALID; ~0 might be better.
				 */
				if (!m->is_dma_mapped)
					t->rx_dma = t->tx_dma = 0;
				status = bitbang->txrx_bufs(spi, t);
			}
			if (status > 0)
				m->actual_length += status;
			if (status != t->len) {
				/* always report some kind of error */
				if (status >= 0)
					status = -EREMOTEIO;
				break;
			}
			status = 0;

			/* protocol tweaks before next transfer */
			if (t->delay_usecs)
				udelay(t->delay_usecs);

			if (!cs_change)
				continue;
			if (t->transfer_list.next == &m->transfers)
				break;

			/* sometimes a short mid-message deselect of the chip
			 * may be needed to terminate a mode or command
			 */
			ndelay(nsecs);
			bitbang->chipselect(spi, BITBANG_CS_INACTIVE);
			ndelay(nsecs);
		}

		m->status = status;
		m->complete(m->context);

		/* normally deactivate chipselect ... unless no error and
		 * cs_change has hinted that the next message will probably
		 * be for this chip too.
		 */
		if (!(status == 0 && cs_change)) {
			ndelay(nsecs);
			bitbang->chipselect(spi, BITBANG_CS_INACTIVE);
			ndelay(nsecs);
		}

		spin_lock_irqsave(&bitbang->lock, flags);
	}
	bitbang->busy = 0;
	spin_unlock_irqrestore(&bitbang->lock, flags);
}

Linux中龙芯1c的SPI代码

源码所在位置是drivers\spi\spi_ls1x.c,源码如下

/*
 * Loongson1 ls1x SPI master driver
 *
 * Copyright (C) 2013 Tang, Haifeng <[email protected]>
 *
 * Based on spi_oc_tiny.c
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi_bitbang.h>
#include <linux/spi/spi_ls1x.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/clk.h>

#define DRV_NAME "spi_ls1x"

#define REG_SPCR		0x00	//鎺у埗瀵勫瓨鍣?
#define REG_SPSR		0x01	//鐘舵€佸瘎瀛樺櫒
#define REG_TXFIFO		0x02	//鏁版嵁浼犺緭瀵勫瓨鍣?杈撳嚭
#define REG_RXFIFO		0x02	//鏁版嵁浼犺緭瀵勫瓨鍣?杈撳叆
#define REG_SPER		0x03	//澶栭儴瀵勫瓨鍣?
#define REG_PARAM		0x04	//SPI Flash鍙傛暟鎺у埗瀵勫瓨鍣?
#define REG_SOFTCS		0x05	//SPI Flash鐗囬€夋帶鍒跺瘎瀛樺櫒
#define REG_TIMING		0x06	//SPI Flash鏃跺簭鎺у埗瀵勫瓨鍣?

struct ls1x_spi {
	/* bitbang has to be first */
	struct spi_bitbang bitbang;
	struct completion done;

	void __iomem *base;
	int irq;
	unsigned int div;
	unsigned int speed_hz;
	unsigned int mode;
	unsigned int len;
	unsigned int txc, rxc;
	const u8 *txp;
	u8 *rxp;
#ifdef CONFIG_SPI_CS_USED_GPIO
	unsigned int gpio_cs_count;
	int *gpio_cs;
#endif

	struct clk *clk;
};

static inline struct ls1x_spi *ls1x_spi_to_hw(struct spi_device *sdev)
{
	return spi_master_get_devdata(sdev->master);
}

static unsigned int ls1x_spi_div(struct spi_device *spi, unsigned int hz)
{
	struct ls1x_spi *hw = ls1x_spi_to_hw(spi);
	unsigned int div, div_tmp, bit;
	unsigned long clk;

	clk = clk_get_rate(hw->clk);
	div = DIV_ROUND_UP(clk, hz);

	if (div < 2)
		div = 2;

	if (div > 4096)
		div = 4096;

	bit = fls(div) - 1;
	switch(1 << bit) {
		case 16: 
			div_tmp = 2;
			if (div > (1<<bit)) {
				div_tmp++;
			}
			break;
		case 32:
			div_tmp = 3;
			if (div > (1<<bit)) {
				div_tmp += 2;
			}
			break;
		case 8:
			div_tmp = 4;
			if (div > (1<<bit)) {
				div_tmp -= 2;
			}
			break;
		default:
			div_tmp = bit - 1;
			if (div > (1<<bit)) {
				div_tmp++;
			}
			break;
	}
	dev_dbg(&spi->dev, "clk = %ld hz = %d div_tmp = %d bit = %d\n", 
			clk, hz, div_tmp, bit);

	return div_tmp;
}

static void ls1x_spi_chipselect(struct spi_device *spi, int is_active)
{
	struct ls1x_spi *hw = ls1x_spi_to_hw(spi);

#ifdef CONFIG_SPI_CS_USED_GPIO
	if (hw->gpio_cs_count) {
		gpio_set_value(hw->gpio_cs[spi->chip_select],
			(spi->mode & SPI_CS_HIGH) ? is_active : !is_active);
	}
#elif CONFIG_SPI_CS
	u8 ret;
	ret = readb(hw->base + REG_SOFTCS);
	ret = (ret & 0xf0) | (0x01 << spi->chip_select);
	
	if (unlikely(spi->mode & SPI_CS_HIGH)) {
		if (is_active) {
			ret = ret | (0x10 << spi->chip_select);
			writeb(ret, hw->base + REG_SOFTCS);
		} else {
			ret = ret & (~(0x10 << spi->chip_select));
			writeb(ret, hw->base + REG_SOFTCS);
		}
	} else {
		if (is_active) {
			ret = ret & (~(0x10 << spi->chip_select));
			writeb(ret, hw->base + REG_SOFTCS);
		} else {
			ret = ret | (0x10 << spi->chip_select);
			writeb(ret, hw->base + REG_SOFTCS);
		}
	}
#endif
}

static int ls1x_spi_setup(struct spi_device *spi)
{
	struct ls1x_spi *hw = ls1x_spi_to_hw(spi);

	/* 娉ㄦ剰spi bit per word 鎺у埗鍣ㄦ敮鎸?bit */
//	bpw = t ? t->bits_per_word : spi->bits_per_word;

	if (spi->max_speed_hz != hw->speed_hz) {
		hw->speed_hz = spi->max_speed_hz;
		hw->div = ls1x_spi_div(spi, hw->speed_hz);
	}
	hw->mode = spi->mode & (SPI_CPOL | SPI_CPHA);
	return 0;
}

static int ls1x_spi_setup_transfer(struct spi_device *spi,
				   struct spi_transfer *t)
{
	struct ls1x_spi *hw = ls1x_spi_to_hw(spi);
	u8 ret;

	ls1x_spi_setup(spi);

/*	if (t) {
		if (t->speed_hz && (t->speed_hz != hw->speed_hz))
			div = ls1x_spi_div(spi, t->speed_hz);
	}*/

	ret = readb(hw->base + REG_SPCR);
	ret = ret & 0xf0;
	ret = ret | (hw->mode << 2) | (hw->div & 0x03);
	writeb(ret, hw->base + REG_SPCR);

	ret = readb(hw->base + REG_SPER);
	ret = ret & 0xfc;
	ret = ret | (hw->div >> 2);
	writeb(ret, hw->base + REG_SPER);

	return 0;
}

static inline void ls1x_spi_wait_rxe(struct ls1x_spi *hw)
{
	u8 ret;

	ret = readb(hw->base + REG_SPSR);
	ret = ret | 0x80;
	writeb(ret, hw->base + REG_SPSR);	/* Int Clear */

	ret = readb(hw->base + REG_SPSR);
	if (ret & 0x40) {
		writeb(ret & 0xbf, hw->base + REG_SPSR);	/* Write-Collision Clear */
	}
}

static inline void ls1x_spi_wait_txe(struct ls1x_spi *hw)
{
	int timeout = 20000;

	while (timeout) {
		if (readb(hw->base + REG_SPSR) & 0x80) {
			break;
		}
		timeout--;
//		cpu_relax();
	}

//	if (timeout == 0) {
//		printk(KERN_ERR "spi transfer wait time out !\n");
//	}
}

static int ls1x_spi_txrx_bufs(struct spi_device *spi, struct spi_transfer *t)
{
	struct ls1x_spi *hw = ls1x_spi_to_hw(spi);

#if defined(CONFIG_SPI_IRQ_MODE)
	if (hw->irq >= 0) {
		/* use intrrupt driven data transfer */
		hw->len = t->len;
		hw->txp = t->tx_buf;
		hw->rxp = t->rx_buf;
		hw->txc = 0;
		hw->rxc = 0;

		/* send the first byte */
		writeb(hw->txp ? *hw->txp++ : 0, hw->base + REG_TXFIFO);
		hw->txc++;
		wait_for_completion(&hw->done);
	}
#elif CONFIG_SPI_POLL_MODE
	const u8 *txp = t->tx_buf;
	u8 *rxp = t->rx_buf;
	unsigned int i;

	if (txp && rxp) {
		for (i = 0; i < t->len; i += 1) {
			writeb(*txp++, hw->base + REG_TXFIFO);
			ls1x_spi_wait_txe(hw);
			*rxp++ = readb(hw->base + REG_RXFIFO);
			ls1x_spi_wait_rxe(hw);
		}
	} else if (rxp) {
		for (i = 0; i < t->len; i += 1) {
			writeb(0, hw->base + REG_TXFIFO);
			ls1x_spi_wait_txe(hw);
			*rxp++ = readb(hw->base + REG_RXFIFO);
			ls1x_spi_wait_rxe(hw);
		}
	} else if (txp) {
		for (i = 0; i < t->len; i += 1) {
			writeb(*txp++, hw->base + REG_TXFIFO);
			ls1x_spi_wait_txe(hw);
			readb(hw->base + REG_RXFIFO);
			ls1x_spi_wait_rxe(hw);
		}
	} else {
		for (i = 0; i < t->len; i += 1) {
			writeb(0, hw->base + REG_TXFIFO);
			ls1x_spi_wait_txe(hw);
			readb(hw->base + REG_RXFIFO);
			ls1x_spi_wait_rxe(hw);
		}
	}
#endif
	return t->len;
}

#if defined(CONFIG_SPI_IRQ_MODE)
static irqreturn_t ls1x_spi_irq(int irq, void *dev)
{
	struct ls1x_spi *hw = dev;
	u8 ret = readb(hw->base + REG_SPSR);

	writeb(ret | 0x80, hw->base + REG_SPSR);

	if (hw->rxc + 1 == hw->len) {
		if (hw->rxp) {
			*hw->rxp++ = readb(hw->base + REG_RXFIFO);
		} else {
			readb(hw->base + REG_RXFIFO);
		}
		hw->rxc++;
		complete(&hw->done);
	} else {
		if (hw->rxp) {
			*hw->rxp++ = readb(hw->base + REG_RXFIFO);
		} else {
			readb(hw->base + REG_RXFIFO);
		}
		hw->rxc++;
		if (hw->txc < hw->len) {
			writeb(hw->txp ? *hw->txp++ : 0, hw->base + REG_TXFIFO);
			hw->txc++;
		}
	}

	return IRQ_HANDLED;
}
#endif

#ifdef CONFIG_SPI_CS_USED_GPIO
#ifdef CONFIG_OF
#include <linux/of_gpio.h>

static int __devinit ls1x_spi_of_probe(struct platform_device *pdev)
{
	struct ls1x_spi *hw = platform_get_drvdata(pdev);
	struct device_node *np = pdev->dev.of_node;
	unsigned int i;
	const __be32 *val;
	int len;

	if (!np)
		return 0;
	hw->gpio_cs_count = of_gpio_count(np);
	if (hw->gpio_cs_count) {
		hw->gpio_cs = devm_kzalloc(&pdev->dev,
				hw->gpio_cs_count * sizeof(unsigned int),
				GFP_KERNEL);
		if (!hw->gpio_cs)
			return -ENOMEM;
	}
	for (i = 0; i < hw->gpio_cs_count; i++) {
		hw->gpio_cs[i] = of_get_gpio_flags(np, i, NULL);
		if (hw->gpio_cs[i] < 0)
			return -ENODEV;
	}
	hw->bitbang.master->dev.of_node = pdev->dev.of_node;
/*	val = of_get_property(pdev->dev.of_node,
			      "clock-frequency", &len);
	if (val && len >= sizeof(__be32))
		hw->freq = be32_to_cpup(val);
	val = of_get_property(pdev->dev.of_node, "div-width", &len);
	if (val && len >= sizeof(__be32))
		hw->baudwidth = be32_to_cpup(val);*/
	return 0;
}
#else /* !CONFIG_OF */
static int __devinit ls1x_spi_of_probe(struct platform_device *pdev)
{
	return 0;
}
#endif /* CONFIG_OF */
#endif

static void ls1x_spi_hw_init(struct ls1x_spi *hw)
{
	u8 val;

	/* 浣胯兘SPI鎺у埗鍣紝master妯″紡锛屼娇鑳芥垨鍏抽棴涓柇 */
	if (hw->irq >= 0) {
		writeb(0xd3, hw->base + REG_SPCR);
	} else {
		writeb(0x53, hw->base + REG_SPCR);
	}
	/* 娓呯┖鐘舵€佸瘎瀛樺櫒 */
	writeb(0xc0, hw->base + REG_SPSR);
	/* 1瀛楄妭浜х敓涓柇锛岄噰鏍?璇?涓庡彂閫?鍐?鏃舵満鍚屾椂 */
	writeb(0x03, hw->base + REG_SPER);
#ifdef CONFIG_SPI_CS_USED_GPIO
	writeb(0x00, hw->base + REG_SOFTCS);
#elif CONFIG_SPI_CS
	writeb(0xff, hw->base + REG_SOFTCS);
#endif
	/* 鍏抽棴SPI flash */
	val = readb(hw->base + REG_PARAM);
	val &= 0xfe;
	writeb(val, hw->base + REG_PARAM);
	/* SPI flash鏃跺簭鎺у埗瀵勫瓨鍣?*/
	writeb(0x05, hw->base + REG_TIMING);
}

static int __devinit ls1x_spi_probe(struct platform_device *pdev)
{
	struct ls1x_spi_platform_data *platp = pdev->dev.platform_data;
	struct ls1x_spi *hw;
	struct spi_master *master;
	struct resource *res;
	int err = -ENODEV;
#ifdef CONFIG_SPI_CS_USED_GPIO
	unsigned int i;
#endif

	master = spi_alloc_master(&pdev->dev, sizeof(struct ls1x_spi));
	if (!master)
		return err;

	/* setup the master state. */
	master->bus_num = pdev->id;
	master->num_chipselect = 32;
	master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
	master->setup = ls1x_spi_setup;

	hw = spi_master_get_devdata(master);
	platform_set_drvdata(pdev, hw);

	/* setup the state for the bitbang driver */
	hw->bitbang.master = spi_master_get(master);
	if (!hw->bitbang.master)
		return err;
	hw->bitbang.setup_transfer = ls1x_spi_setup_transfer;
	hw->bitbang.chipselect = ls1x_spi_chipselect;
	hw->bitbang.txrx_bufs = ls1x_spi_txrx_bufs;

	/* find and map our resources */
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res)
		goto exit_busy;
	if (!devm_request_mem_region(&pdev->dev, res->start, resource_size(res),
				     pdev->name))
		goto exit_busy;
	hw->base = devm_ioremap_nocache(&pdev->dev, res->start,
					resource_size(res));
	if (!hw->base)
		goto exit_busy;
#if defined(CONFIG_SPI_IRQ_MODE)
	/* irq is optional */
	hw->irq = platform_get_irq(pdev, 0);
	if (hw->irq >= 0) {
		init_completion(&hw->done);
		err = devm_request_irq(&pdev->dev, hw->irq, ls1x_spi_irq, 0,
				       pdev->name, hw);
		if (err)
			goto exit;
	}
#else
	hw->irq = -1;
#endif

#ifdef CONFIG_SPI_CS_USED_GPIO
	/* find platform data */
	if (platp) {
		hw->gpio_cs_count = platp->gpio_cs_count;
		hw->gpio_cs = platp->gpio_cs;
		if (platp->gpio_cs_count && !platp->gpio_cs)
			goto exit_busy;
	} else {
		err = ls1x_spi_of_probe(pdev);
		if (err)
			goto exit;
	}
	for (i = 0; i < hw->gpio_cs_count; i++) {
		err = gpio_request(hw->gpio_cs[i], dev_name(&pdev->dev));
		if (err)
			goto exit_gpio;
		gpio_direction_output(hw->gpio_cs[i], 1);
	}
	hw->bitbang.master->num_chipselect = max(1U, hw->gpio_cs_count);
#elif CONFIG_SPI_CS
	hw->bitbang.master->num_chipselect = platp->cs_count;
#endif

	ls1x_spi_hw_init(hw);

	hw->clk = clk_get(&pdev->dev, "apb");
	if (IS_ERR(hw->clk)) {
		dev_err(&pdev->dev, "No clock for device\n");
		err = PTR_ERR(hw->clk);
		goto err_no_clk;
	}

	/* register our spi controller */
	err = spi_bitbang_start(&hw->bitbang);
	if (err)
		goto exit;
	dev_info(&pdev->dev, "base %p, irq %d\n", hw->base, hw->irq);

	return 0;

err_no_clk:
#ifdef CONFIG_SPI_CS_USED_GPIO
exit_gpio:
	while (i-- > 0)
		gpio_free(hw->gpio_cs[i]);
#endif
exit_busy:
	err = -EBUSY;
exit:
	platform_set_drvdata(pdev, NULL);
	spi_master_put(master);
	return err;
}

static int __devexit ls1x_spi_remove(struct platform_device *pdev)
{
	struct ls1x_spi *hw = platform_get_drvdata(pdev);
	struct spi_master *master = hw->bitbang.master;
#ifdef CONFIG_SPI_CS_USED_GPIO
	unsigned int i;
#endif

	spi_bitbang_stop(&hw->bitbang);
#ifdef CONFIG_SPI_CS_USED_GPIO
	for (i = 0; i < hw->gpio_cs_count; i++)
		gpio_free(hw->gpio_cs[i]);
#endif
	platform_set_drvdata(pdev, NULL);
	spi_master_put(master);
	return 0;
}

#ifdef CONFIG_OF
static const struct of_device_id ls1x_spi_match[] = {
	{ .compatible = "loongson1,ls1x-spi", },
	{},
};
MODULE_DEVICE_TABLE(of, ls1x_spi_match);
#else /* CONFIG_OF */
#define ls1x_spi_match NULL
#endif /* CONFIG_OF */

static struct platform_driver ls1x_spi_driver = {
	.probe = ls1x_spi_probe,
	.remove = __devexit_p(ls1x_spi_remove),
	.driver = {
		.name = DRV_NAME,
		.owner = THIS_MODULE,
		.pm = NULL,
		.of_match_table = ls1x_spi_match,
	},
};

static int __init ls1x_spi_init(void)
{
	return platform_driver_register(&ls1x_spi_driver);
}
module_init(ls1x_spi_init);

static void __exit ls1x_spi_exit(void)
{
	platform_driver_unregister(&ls1x_spi_driver);
}
module_exit(ls1x_spi_exit);

MODULE_DESCRIPTION("Loongson1 SPI driver");
MODULE_AUTHOR("Tang, Haifeng <[email protected]>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" DRV_NAME);

龙芯1C库中硬件SPI接口源码清单

ls1c_spi.h

// 硬件spi接口的头文件

#ifndef __OPENLOONGSON_SPI_H
#define __OPENLOONGSON_SPI_H


// SPI模块编号
typedef enum
{
    LS1C_SPI_0 = 0,
    LS1C_SPI_1,
}ls1c_spi_t;


// 片选
#define LS1C_SPI_INVALID_CS             (-1)
#define LS1C_SPI_CS_0                   (0)
#define LS1C_SPI_CS_1                   (1)
#define LS1C_SPI_CS_2                   (2)
#define LS1C_SPI_CS_3                   (3)

// 时钟极性和相位
#define SPI_CPOL_1                      (1)
#define SPI_CPOL_0                      (0)
#define SPI_CPHA_1                      (1)
#define SPI_CPHA_0                      (0)


// 硬件SPI信息
typedef struct
{
    ls1c_spi_t SPIx;                    // SPI模块编号
    unsigned long max_speed_hz;         // 最大通信速度,单位hz
    unsigned char cs;                   // 片选
    unsigned char cpol;                 // 时钟极性
    unsigned char cpha;                 // 时钟相位
}ls1c_spi_info_t;



/*
 * 初始化指定SPI模块
 * @spi_info_p SPI模块信息
 */
void spi_init(ls1c_spi_info_t *spi_info_p);


/*
 * 设置指定片选为指定状态
 * @spi_info_p SPI模块信息
 * @new_status 片选引脚的新状态,取值为0或1,即高电平或低电平
 */
void spi_set_cs(ls1c_spi_info_t *spi_info_p, int new_status);


/*
 * 通过指定SPI发送接收一个字节
 * 注意,在多任务的系统中,此函数需要互斥。
 * 即保证在和某个从设备收发某个字节的过程中,不能被切换到其它任务同时与另外的在同一个SPI总线上的从设备通信
 * 因为龙芯1c的每路SPI上可能接有不同的从设备,通信频率、模式等可能不同
 * @spi_info_p SPI接口
 * @tx_ch 待发送的数据
 * @ret 收到的数据
 */
unsigned char spi_txrx_byte(ls1c_spi_info_t *spi_info_p, unsigned char tx_ch);


/*
 * 打印指定SPI模块的所有寄存器的值
 * @spi_info_p SPI模块信息
 */
void spi_print_all_regs_info(ls1c_spi_info_t *spi_info_p);


#endif


ls1c_spi.c

// 硬件spi接口源文件


#include <string.h>
#include "ls1c_public.h"
#include "ls1c_regs.h"
#include "ls1c_clock.h"
#include "ls1c_spi.h"


// 寄存器偏移
#define LS1C_SPI_SPCR_OFFSET                (0)     // 控制寄存器
#define LS1C_SPI_SPSR_OFFSET                (1)     // 状态寄存器
#define LS1C_SPI_TxFIFO_OFFSET              (2)     // 发送的数据寄存器,与接收数据寄存器的偏移相同
#define LS1C_SPI_RxFIFO_OFFSET              (2)     // 接收的数据寄存器,与发送数据寄存器的偏移相同
#define LS1C_SPI_SPER_OFFSET                (3)     // 外部寄存器
#define LS1C_SPI_SFC_PARAM_OFFSET           (4)     // 参数控制寄存器
#define LS1C_SPI_SFC_SOFTCS_OFFSET          (5)     // 片选控制寄存器
#define LS1C_SPI_SFC_TIMING_OFFSET          (6)     // 时序控制寄存器

// 寄存器SPCR中的位域
#define LS1C_SPI_SPCR_SPIE_BIT              (7)
#define LS1C_SPI_SPCR_SPIE_MASK             (0x01 << LS1C_SPI_SPCR_SPIE_BIT)
#define LS1C_SPI_SPCR_SPE_BIT               (6)
#define LS1C_SPI_SPCR_SPE_MASK              (0x01 << LS1C_SPI_SPCR_SPE_BIT)
#define LS1C_SPI_SPCR_CPOL_BIT              (3)
#define LS1C_SPI_SPCR_CPOL_MASK             (0x01 << LS1C_SPI_SPCR_CPOL_BIT)
#define LS1C_SPI_SPCR_CPHA_BIT              (2)
#define LS1C_SPI_SPCR_CPHA_MASK             (0x01 << LS1C_SPI_SPCR_CPHA_BIT)
#define LS1C_SPI_SPCR_SPR_BIT               (0)
#define LS1C_SPI_SPCR_SPR_MASK              (0x03 << LS1C_SPI_SPCR_SPR_BIT)

// 寄存器SPSR中的位域
#define LS1C_SPI_SPSR_SPIF_BIT              (7)
#define LS1C_SPI_SPSR_SPIF_MASK             (0x01 << LS1C_SPI_SPSR_SPIF_BIT)
#define LS1C_SPI_SPSR_WCOL_BIT              (6)
#define LS1C_SPI_SPSR_WCOL_MASK             (0x01 << LS1C_SPI_SPSR_WCOL_BIT)

// 寄存器SPER中的位域
#define LS1C_SPI_SPER_SPRE_BIT              (0)
#define LS1C_SPI_SPER_SPRE_MASK             (0x3 << LS1C_SPI_SPER_SPRE_BIT)

// 寄存器SFC_SOFTCS的位域
#define LS1C_SPI_SFC_SOFTCS_CSN_BIT         (4)
#define LS1C_SPI_SFC_SOFTCS_CSN_MASK        (0x0f << LS1C_SPI_SFC_SOFTCS_CSN_BIT)
#define LS1C_SPI_SFC_SOFTCS_CSEN_BIT        (0)
#define LS1C_SPI_SFC_SOFTCS_CSEN_MASK       (0x0f << LS1C_SPI_SFC_SOFTCS_CSEN_BIT)

// 发送超时的门限值
#define LS1C_SPI_TX_TIMEOUT                 (20000)


/*
 * 获取指定SPI模块的基地址
 * @SPIx SPI模块的编号
 */
inline void *spi_get_base(ls1c_spi_t SPIx)
{
    void *base = NULL;

    switch (SPIx)
    {
        case LS1C_SPI_0:
            base = (void *)LS1C_SPI0_BASE;
            break;

        case LS1C_SPI_1:
            base = (void *)LS1C_SPI1_BASE;
            break;

        default:
            base = NULL;
            break;
    }

    return base;
}


/*
 * 打印指定SPI模块的所有寄存器的值
 * @spi_info_p SPI模块信息
 */
void spi_print_all_regs_info(ls1c_spi_info_t *spi_info_p)
{
    void *spi_base = spi_get_base(spi_info_p->SPIx);

    myprintf("[%s] SPI%d's info:\r\n\
              SPCR=0x%x, SPSR=0x%x, SPER=0x%x, SFC_PARAM=0x%x, SFC_SOFTCS=0x%x, SFC_TIMING=0x%x\r\n",
              __FUNCTION__, spi_info_p->SPIx,
              reg_read_8(spi_base + LS1C_SPI_SPCR_OFFSET),
              reg_read_8(spi_base + LS1C_SPI_SPSR_OFFSET),
              reg_read_8(spi_base + LS1C_SPI_SPER_OFFSET),
              reg_read_8(spi_base + LS1C_SPI_SFC_PARAM_OFFSET),
              reg_read_8(spi_base + LS1C_SPI_SFC_SOFTCS_OFFSET),
              reg_read_8(spi_base + LS1C_SPI_SFC_TIMING_OFFSET));

    return ;
}


/*
 * 根据SPI时钟频率计算分频系数
 * @max_speed_hz SPI最大通信速度
 * @ret 分频系数
 */
unsigned int spi_get_div(unsigned int max_speed_hz)
{
    unsigned long clk = 0;
    unsigned int div = 0;
    unsigned int div_tmp = 0;
    unsigned int bit = 0;

    clk = clk_get_apb_rate();
    div = DIV_ROUND_UP(clk, max_speed_hz);

    if (div < 2)
        div = 2;

    if (div > 4096)
        div = 4096;

    bit = ls1c_fls(div) - 1;
    switch (1 << bit)
    {
        case 16:
            div_tmp = 2;
            if (div > (1 << bit))
            {
                div_tmp++;
            }
            break;

        case 32:
            div_tmp = 3;
            if (div > (1 << bit))
            {
                div_tmp += 2;
            }
            break;

        case 8:
            div_tmp = 4;
            if (div > (1 << bit))
            {
                div_tmp -= 2;
            }
            break;

        default:
            div_tmp = bit - 1;
            if (div > (1 << bit))
            {
                div_tmp++;
            }
            break;
    }
/*    
    myprintf("[%s] clk=%ld, max_speed_hz=%d, div_tmp=%d, bit=%d\r\n", 
              __FUNCTION__, clk, max_speed_hz, div_tmp, bit);
*/
    return div_tmp;
}


/*
 * 设置时钟
 * @spi_info_p SPI模块信息
 */
void spi_set_clock(ls1c_spi_info_t *spi_info_p)
{
    void *spi_base = spi_get_base(spi_info_p->SPIx);
    unsigned int div = 0;
    unsigned char val = 0;

    // 获取分频系数
    div = spi_get_div(spi_info_p->max_speed_hz);

    // 设置spr
    val = reg_read_8(spi_base + LS1C_SPI_SPCR_OFFSET);
    val &= (~LS1C_SPI_SPCR_SPR_MASK);                       // spr清零
    val |= (div & LS1C_SPI_SPCR_SPR_MASK);                  // 设置新的spr
    reg_write_8(val, spi_base + LS1C_SPI_SPCR_OFFSET);

    // 设置spre
    val = reg_read_8(spi_base + LS1C_SPI_SPER_OFFSET);
    val &= (~LS1C_SPI_SPER_SPRE_MASK);                      // spre清零
    val |= ((div >> 2) & LS1C_SPI_SPER_SPRE_MASK);        // 设置新的spre
    reg_write_8(val, spi_base + LS1C_SPI_SPER_OFFSET);

    return ;
}


/*
 * 设置通信模式(时钟极性和相位)
 * @spi_info_p SPI模块信息
 */
void spi_set_mode(ls1c_spi_info_t *spi_info_p)
{
    void *spi_base = spi_get_base(spi_info_p->SPIx);
    unsigned char val = 0;

    val = reg_read_8(spi_base + LS1C_SPI_SPCR_OFFSET);
    
    // 设置时钟极性--cpol
    val &= (~LS1C_SPI_SPCR_CPOL_MASK);                      // cpol清0
    val |= (spi_info_p->cpol << LS1C_SPI_SPCR_CPOL_BIT);    // 写入新的cpol
    
    // 设置时钟相位--cpha
    val &= (~LS1C_SPI_SPCR_CPHA_MASK);                      // cpha清0
    val |= (spi_info_p->cpha << LS1C_SPI_SPCR_CPHA_BIT);    // 写入新的cpha
    
    reg_write_8(val, spi_base + LS1C_SPI_SPCR_OFFSET);

    return ;
}


/*
 * 设置指定片选为指定状态
 * @spi_info_p SPI模块信息
 * @new_status 片选引脚的新状态,取值为0或1,即高电平或低电平
 */
void spi_set_cs(ls1c_spi_info_t *spi_info_p, int new_status)
{
    void *spi_base = spi_get_base(spi_info_p->SPIx);
    unsigned char cs = spi_info_p->cs;
    unsigned char val = 0;

    val = 0xf0 | (0x01 << cs);          // 全部csn=1,指定的csen=1
    if (new_status)         // cs = 1
    {
        val |= (0x10 << cs);            // 指定csn=1
    }
    else                    // cs = 0
    {
        val &= ~(0x10 << cs);           // 指定csn=0
    }
    reg_write_8(val, spi_base + LS1C_SPI_SFC_SOFTCS_OFFSET);

    return ;
}


/*
 * 初始化指定SPI模块
 * @spi_info_p SPI模块信息
 */
void spi_init(ls1c_spi_info_t *spi_info_p)
{
    void *spi_base = spi_get_base(spi_info_p->SPIx);
    unsigned char val = 0;

    // 使能SPI控制器,master模式,关闭中断
    reg_write_8(0x53, spi_base + LS1C_SPI_SPCR_OFFSET);

    // 清空状态寄存器
    reg_write_8(0xc0, spi_base + LS1C_SPI_SPSR_OFFSET);

    // 1字节产生中断,采样(读)与发送(写)时机同时
    reg_write_8(0x03, spi_base + LS1C_SPI_SPER_OFFSET);

    // 关闭SPI flash
    val = reg_read_8(spi_base + LS1C_SPI_SFC_PARAM_OFFSET);
    val &= 0xfe;
    reg_write_8(val, spi_base + LS1C_SPI_SFC_PARAM_OFFSET);

    // spi flash时序控制寄存器
    reg_write_8(0x05, spi_base + LS1C_SPI_SFC_TIMING_OFFSET);

    // 设置时钟
    spi_set_clock(spi_info_p);

    // 设置通信模式(时钟极性和相位)
    spi_set_mode(spi_info_p);

    // 打印寄存器信息(用于调试)
//    spi_print_all_regs_info(spi_info_p);

    return ;
}


/*
 * 等待收发完成
 */
inline void spi_wait_txrx_done(ls1c_spi_info_t *spi_info_p)
{
    void *spi_base = spi_get_base(spi_info_p->SPIx);
    int timeout = LS1C_SPI_TX_TIMEOUT;

    while (timeout--)
    {
        if (LS1C_SPI_SPSR_SPIF_MASK & reg_read_8(spi_base + LS1C_SPI_SPSR_OFFSET))
            break;
    }
    
    return ;
}


/*
 * 清中断和标志位
 */
inline void spi_clear(ls1c_spi_info_t *spi_info_p)
{
    void *spi_base = spi_get_base(spi_info_p->SPIx);
    unsigned char val = 0;

    // 清中断
    val = reg_read_8(spi_base + LS1C_SPI_SPSR_OFFSET);
    val |= LS1C_SPI_SPSR_SPIF_MASK;
    reg_write_8(val, spi_base + LS1C_SPI_SPSR_OFFSET);

    // 清溢出标志位(Write-Collision Clear)
    val = reg_read_8(spi_base + LS1C_SPI_SPSR_OFFSET);
    if (LS1C_SPI_SPSR_WCOL_MASK & val)
    {
        myprintf("[%s] clear register SPSR's wcol!\r\n");       // 手册和linux源码中不一样,加个打印看看
        reg_write_8(val & ~LS1C_SPI_SPSR_WCOL_MASK, spi_base + LS1C_SPI_SPSR_OFFSET);   // 写0,linux源码中是写0
//        reg_write_8(val | LS1C_SPI_SPSR_WCOL_MASK, spi_base + LS1C_SPI_SPSR_OFFSET);  // 写1,按照1c手册,应该写1
    }

    return ;
}



/*
 * 通过指定SPI发送接收一个字节
 * 注意,在多任务的系统中,此函数需要互斥。
 * 即保证在和某个从设备收发某个字节的过程中,不能被切换到其它任务同时与另外的在同一个SPI总线上的从设备通信
 * 因为龙芯1c的每路SPI上可能接有不同的从设备,通信频率、模式等可能不同
 * @spi_info_p SPI接口
 * @tx_ch 待发送的数据
 * @ret 收到的数据
 */
unsigned char spi_txrx_byte(ls1c_spi_info_t *spi_info_p, unsigned char tx_ch)
{
    void *spi_base = spi_get_base(spi_info_p->SPIx);
    unsigned char rx_ch = 0;

    // 收发数据
    reg_write_8(tx_ch, spi_base + LS1C_SPI_TxFIFO_OFFSET);      // 开始发送
    spi_wait_txrx_done(spi_info_p);                             // 等待收发完成
    rx_ch = reg_read_8(spi_base + LS1C_SPI_RxFIFO_OFFSET);      // 读取收到的数据
    spi_clear(spi_info_p);                                      // 清中断和标志位

    return rx_ch;
}





感谢阅读!


猜你喜欢

转载自blog.csdn.net/caogos/article/details/78353988