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

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

将温湿度传感器AM2320接在一个硬件I2C引脚上,使用本文封装好的接口与AM2320通信,读取温湿度信息。以此验证硬件I2C接口是否正常工作,其后在详细讲解是如何封装硬件I2C接口的。

龙芯1c库中硬件I2C接口使用示例

硬件I2C接口简介

I2C初始化i2c_init()

函数原型

/*
 * 初始化指定i2c模块
 * @i2c_info_p 某个i2c模块的信息
 */
void i2c_init(ls1c_i2c_info_t *i2c_info_p);

风格和STM32库类似,看到函数原型应该大概能猜到了。重点来看下入参“ls1c_i2c_info_t *i2c_info_p”,变量类型ls1c_i2c_info_t的定义如下

// 硬件I2C信息
typedef struct
{
    ls1c_i2c_t I2Cx;                    // i2c模块编号
    unsigned long clock;                // i2c时钟频率,单位hz
}ls1c_i2c_info_t;

// I2C模块编号
typedef enum
{
    LS1C_I2C_0 = 0,
    LS1C_I2C_1,
    LS1C_I2C_2
}ls1c_i2c_t;

代码已经很清楚了,初始化函数i2c_init()的入参中包含I2C编号(I2C0,I2C1或I2C2)和I2C时钟频率。

除了I2C0有默认引脚(即可以不复用)外,I2C1和I2C2都需要复用,并且不止一种复用,具体复用到那个引脚根据自己喜好。需要注意的是,初始化函数中没有设置引脚复用的代码,需要另外单独调用引脚复用的函数(pin_set_remap())进行设置。

使用示例

比如,使用I2C0,时钟为50k,可以如下使用

    ls1c_i2c_info_t i2c_info;
    i2c_info.clock = 50*1000;       // 50kb/s
    i2c_info.I2Cx = LS1C_I2C_0;
    i2c_init(&i2c_info);

引脚复用pin_set_remap()

函数原型

/*
 * 设置指定pin为第n复用
 * @gpio gpio编号
 * @remap 第n复用
 */
void pin_set_remap(unsigned int gpio, pin_remap_t remap)

// 引脚复用
typedef enum
{
    PIN_REMAP_FIRST = 0,                // 第一复用
    PIN_REMAP_SECOND,                   // 第二复用
    PIN_REMAP_THIRD,                    // 第三复用
    PIN_REMAP_FOURTH,                   // 第四复用
    PIN_REMAP_FIFTH,                    // 第五复用
}pin_remap_t;

引脚复用的这个函数其实早已封装好的,函数pin_set_remap()的第一个入参为GPIO编号,第二个参数为引脚的第n复用,想知道那些引脚可以复用为I2C,需要查询《龙芯1C处理器数据手册》。比如

由上图可知,GPIO54和GPIO55可以复用为I2C1,GPIO56和GPIO57可以复用为I2C2,都是第四复用。

使用示例

前面分析的I2C1和I2C2,可以通过下面的调用实现复用

// I2C1,引脚CAMDATA4(GPIO54)和CAMDATA5(GPIO55)的第四复用
#define LS1C_I2C_SDA1_GPIO54            (54)
#define LS1C_I2C_SCL1_GPIO55            (55)

// I2C2,引脚CAMDATA6(GPIO56)和CAMDATA7(GPIO57)的第四复用
#define LS1C_I2C_SDA2_GPIO56            (56)
#define LS1C_I2C_SCL2_GPIO57            (57)

// 使用I2C1,引脚CAMDATA4(GPIO54)和CAMDATA5(GPIO55)的第四复用
pin_set_remap(LS1C_I2C_SDA1_GPIO54, PIN_REMAP_FOURTH);
pin_set_remap(LS1C_I2C_SCL1_GPIO55, PIN_REMAP_FOURTH);

// I2C2,引脚CAMDATA6(GPIO56)和CAMDATA7(GPIO57)的第四复用
pin_set_remap(LS1C_I2C_SDA2_GPIO56, PIN_REMAP_FOURTH);
pin_set_remap(LS1C_I2C_SCL2_GPIO57, PIN_REMAP_FOURTH);

注意,还有其它引脚也可以复用为I2C1或I2C2.

另外,单独写了一篇引脚复用的博文,有兴趣的可以移步到《【龙芯1c库】封装引脚复用接口和使用示例》 http://blog.csdn.net/caogos/article/details/72529394

发送START信号和地址i2c_send_start_and_addr()

龙芯1c的I2C硬件本身决定了,没有单独发送地址的命令,即发送开始信号后,硬件自动将数据寄存器中的值作为地址发送出去。所以I2C的开始信号和地址封装成了一个函数。

函数原型

/*
 * 发送START信号和地址
 * @i2c_info_p i2c模块信息
 * @slave_addr 从机地址
 * @direction 数据传输方向(读、写)
 */
ls1c_i2c_ret_t i2c_send_start_and_addr(ls1c_i2c_info_t *i2c_info_p, 
                                       unsigned char slave_addr,
                                       ls1c_i2c_direction_t direction)

// I2C数据传输方向
typedef enum
{
    LS1C_I2C_DIRECTION_WRITE = 0,       // 主机向从机写信息
    LS1C_I2C_DIRECTION_READ,            // 主机向从机读信息
}ls1c_i2c_direction_t;

注意,函数i2c_send_start_and_addr()的第二个参数slave_addr不是I2C开始信号后发出去的8字节数据。开始信号后的8字节数据=(slave_addr << 1) | ((LS1C_I2C_DIRECTION_READ == direction) ? 1 : 0),所以第二个参数——从机地址是7位的数,不是8位的,在具体应用时要特别注意。

使用示例

比如,温湿度传感器AM2320手册中说AM2320的地址为0xB8,如下图

注意,AM2320手册中说的地址为0xB8是不能直接作为函数i2c_send_start_and_addr()的第二个参数的,需要右移一位,即slave_addr = 0xb8 >> 1。可能大家会怀疑,那么接着来看AM2320的手册。


在读取温湿度信息时,地址为0xB9.

函数i2c_send_start_and_addr()完整的示例为

ls1c_i2c_info_t i2c_info;
int slave_addr = 0xb8 >> 1;

i2c_info.clock = 50*1000;       // 50kb/s
i2c_info.I2Cx = LS1C_I2C_2;
i2c_send_start_and_addr(&i2c_info, slave_addr, LS1C_I2C_DIRECTION_WRITE);

发送STOP信号i2c_send_stop()

函数i2c_send_stop()发送I2C协议中的STOP信号。

函数原型

/*
 * 发送STOP信号
 * @i2c_info_p i2c模块信息
 */
void i2c_send_stop(ls1c_i2c_info_t *i2c_info_p)

这个函数很简单,不用解释吧。

使用示例

ls1c_i2c_info_t i2c_info;

i2c_info.clock = 50*1000;       // 50kb/s
i2c_info.I2Cx = LS1C_I2C_2;
i2c_send_stop(&i2c_info);

接收从机的ACK信号i2c_receive_ack()

I2C协议规定,在向从机发送一个字节信息后,从机需要回应一个ACK信号。函数i2c_receive_ack()就是用于接收这个ACK信号的。

函数原型

/*
 * (再发送一个字节数据之后)接收从机发送的ACK信号
 * @i2c_info_p i2c模块信息
 * @ret LS1C_I2C_ACK or LS1C_I2C_NACK
 */
ls1c_i2c_ack_t i2c_receive_ack(ls1c_i2c_info_t *i2c_info_p)

// I2C应答
typedef enum
{
    LS1C_I2C_ACK = 0,                   // 收到应答
    LS1C_I2C_NACK = 1,                  // 没收到应答
}ls1c_i2c_ack_t;

入参i2c_info_p指示在哪个I2C总线上读取ACK信号,函数返回是否收到ACK信号。

使用示例

ls1c_i2c_info_t i2c_info;
i2c_info.clock = 50*1000;       // 50kb/s
i2c_info.I2Cx = LS1C_I2C_2;
i2c_receive_ack(&i2c_info);

这里没有判断返回值,有需要的自己判断一下。

发送数据i2c_send_data()

注意,这里说的数据是指除I2C从机地址,START信号和STOP信号外的其它信息。对于具体的某个I2C传感器来说,这里的数据包括渎写传感器的命令。比如,温湿度传感器AM2320的读命令,就是用函数i2c_send_data()发送的,即AM2320的读命令就是这里说的数据。

函数原型

/*
 * 发送数据
 * @i2c_info_p i2c模块信息
 * @data 待发送的数据
 * @len 待发送数据的长度
 */
ls1c_i2c_ret_t i2c_send_data(ls1c_i2c_info_t *i2c_info_p, unsigned char *data, int len)

// 函数返回值
typedef enum
{
    LS1C_I2C_RET_OK = 0,                // OK
    LS1C_I2C_RET_TIMEOUT,               // 超时
}ls1c_i2c_ret_t;

使用示例

比如,向AM2320发送读命令

ls1c_i2c_info_t i2c_info;
unsigned char send_buff[] = {0x03, 0x00, 0x04};
i2c_info.clock = 50*1000;       // 50kb/s
i2c_info.I2Cx = LS1C_I2C_2;
i2c_send_data(&i2c_info, send_buff, sizeof(send_buff));

接收数据i2c_receive_data()

函数原型

/*
 * 接收数据
 * @i2c_info_p i2c模块信息
 * @buf 数据缓存
 * @len 待接收数据的长度
 */
ls1c_i2c_ret_t i2c_receive_data(ls1c_i2c_info_t *i2c_info_p, unsigned char *buf, int len)

// 函数返回值
typedef enum
{
    LS1C_I2C_RET_OK = 0,                // OK
    LS1C_I2C_RET_TIMEOUT,               // 超时
}ls1c_i2c_ret_t;

使用示例

比如,接收温湿度传感器AM2320回复的温湿度信息。

// am2320返回的数据长度
#define AM2320_RSP_DATA_SIZE            (8)

ls1c_i2c_info_t i2c_info;
unsigned char recv_buff[AM2320_RSP_DATA_SIZE] = {0};

i2c_info.clock = 50*1000;       // 50kb/s
i2c_info.I2Cx = LS1C_I2C_2;
i2c_receive_data(&i2c_info, recv_buff, AM2320_RSP_DATA_SIZE);

用温湿度传感器AM2320测试硬件I2C接口

实物图

这里测试选用的是智龙首发版


实际上智龙v2.0和v2.1也是可以的。只是v2.0和v2.1上的I2C0的I2C_SCL0和I2C_SDA0两个引脚上有电容,会将I2C信号滤掉,也就是说需要如果需要使用智龙V2.0和V2.1上默认的I2C0的话,需要将电容焊下来,如下图所示。

不一定非要用I2C0的,I2C1和I2C2也都可以用,代码中对I2C1和I2C2也进行了测试,每个只选了一种复用情况进行测试,其它复用情况的用法类似。

测试源码

main.c

#include "../lib/ls1c_public.h"
#include "../lib/ls1c_gpio.h"
#include "../lib/ls1c_delay.h"
#include "../lib/ls1c_mipsregs.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"



// 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)
{
    // 硬浮点初始化
    fpu_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();


    // ------------------------测试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();


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

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


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

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

	return(0);
}

test_i2c.c

// 测试硬件i2c源文件


#include "../lib/ls1c_public.h"
#include "../lib/ls1c_delay.h"
#include "../lib/ls1c_i2c.h"
#include "../lib/ls1c_pin.h"
#include "test_simulate_i2c.h"


// am2320返回的数据长度
#define AM2320_RSP_DATA_SIZE            (8)

// I2C1,引脚CAMDATA4(GPIO54)和CAMDATA5(GPIO55)的第四复用
#define LS1C_I2C_SDA1_GPIO54            (54)
#define LS1C_I2C_SCL1_GPIO55            (55)

// I2C2,引脚CAMDATA6(GPIO56)和CAMDATA7(GPIO57)的第四复用
#define LS1C_I2C_SDA2_GPIO56            (56)
#define LS1C_I2C_SCL2_GPIO57            (57)


// 用温湿度传感器测试硬件i2c
void test_i2c_am2320(void)
{
    ls1c_i2c_info_t i2c_info;
    int slave_addr = 0xb8 >> 1;     // AM2320手册中的地址0xB8是8位的,而我们这里地址是指其前7位
    unsigned char send_buff[] = {0x03, 0x00, 0x04};
    unsigned char recv_buff[AM2320_RSP_DATA_SIZE] = {0};
    unsigned short recved_crc, calced_crc;
    int temp, humi;
    
    // I2C2,引脚CAMDATA6(GPIO56)和CAMDATA7(GPIO57)的第四复用
    pin_set_remap(LS1C_I2C_SDA2_GPIO56, PIN_REMAP_FOURTH);
    pin_set_remap(LS1C_I2C_SCL2_GPIO57, PIN_REMAP_FOURTH);
    i2c_info.clock = 50*1000;       // 50kb/s
    i2c_info.I2Cx = LS1C_I2C_2;
    i2c_init(&i2c_info);
    
/*
    // 使用I2C1,引脚CAMDATA4(GPIO54)和CAMDATA5(GPIO55)的第四复用
    pin_set_remap(LS1C_I2C_SDA1_GPIO54, PIN_REMAP_FOURTH);
    pin_set_remap(LS1C_I2C_SCL1_GPIO55, PIN_REMAP_FOURTH);
    i2c_info.clock = 50*1000;       // 50kb/s
    i2c_info.I2Cx = LS1C_I2C_1;
    i2c_init(&i2c_info);
*/

/*
    // 使用I2C0,不需要复用
    i2c_info.clock = 50*1000;       // 50kb/s
    i2c_info.I2Cx = LS1C_I2C_0;
    i2c_init(&i2c_info);
*/

    while (1)
    {
        // 唤醒传感器
        i2c_send_start_and_addr(&i2c_info, slave_addr, LS1C_I2C_DIRECTION_WRITE);
        i2c_receive_ack(&i2c_info);
        delay_ms(1);
        i2c_send_stop(&i2c_info);

        // 发送读指令
        i2c_send_start_and_addr(&i2c_info, slave_addr, LS1C_I2C_DIRECTION_WRITE);
        i2c_receive_ack(&i2c_info);
        i2c_send_data(&i2c_info, send_buff, sizeof(send_buff));
        i2c_send_stop(&i2c_info);

        // 读取温湿度信息
        delay_ms(2);
        i2c_send_start_and_addr(&i2c_info, slave_addr, LS1C_I2C_DIRECTION_READ);
        i2c_receive_ack(&i2c_info);
        i2c_receive_data(&i2c_info, recv_buff, AM2320_RSP_DATA_SIZE);
        i2c_send_stop(&i2c_info);

        // 校验crc
        recved_crc = (recv_buff[AM2320_RSP_CRC_HIGH] << 8) + recv_buff[AM2320_RSP_CRC_LOW];
        calced_crc = am2320_crc16(recv_buff, 6);
        if (recved_crc != calced_crc)
        {
            myprintf("[%s] crc error! recved_crc=0x%x, calced_crc=0x%x\n", 
                      __FUNCTION__, recved_crc, calced_crc);
        }

        // 计算温度湿度
        humi = (recv_buff[AM2320_RSP_HUMI_HIGH] * 0xff + recv_buff[AM2320_RSP_HUMI_LOW]) / 10;
        temp = (recv_buff[AM2320_RSP_TEMP_HIGH] * 0xff + recv_buff[AM2320_RSP_TEMP_LOW]) / 10;
        myprintf("[%s] temp=%d, humi=%d\n", __FUNCTION__, temp, humi);
        
        // 间隔3s,再采集
        delay_s(3);
    }

    return ;
}




test_i2c.h

// 测试硬件i2c头文件


#ifndef __OPENLOONGSON_I2C_H
#define __OPENLOONGSON_I2C_H




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


#endif

测试结果

串口打印

Supported filesystems [mtd, net, fat, fs, disk, socket, tty, ram]
This software may be redistributed under the BSD copyright.
Copyright 2000-2002, Opsycon AB, Sweden.
Copyright 2005, ICT CAS.
CPU Loongson 1C-300A ZhiLong @ 252.00 MHz / Bus @ 126.00 MHz
Memory size  32 MB ( 32 MB Low memory,   0 MB High memory) .
Primary Instruction cache size 16kb (32 line, 4 way)
Primary Data cache size 16kb (32 line, 4 way)

BEV1
BEV0
BEV in SR set to zero.
lxy: update_usb, no !
AUTO
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/16400 + 0x80204010/4240(z) + 130 syms|
Entry address is 80203468
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
[test_i2c_am2320] temp=29, humi=56
[test_i2c_am2320] temp=29, humi=56
[test_i2c_am2320] temp=28, humi=56
[test_i2c_am2320] temp=28, humi=56
[test_i2c_am2320] temp=28, humi=56
[test_i2c_am2320] temp=28, humi=57
[test_i2c_am2320] temp=28, humi=57
[test_i2c_am2320] temp=28, humi=57
[test_i2c_am2320] temp=28, humi=57
[test_i2c_am2320] temp=28, humi=57
[test_i2c_am2320] temp=28, humi=57
[test_i2c_am2320] temp=28, humi=57
[test_i2c_am2320] temp=28, humi=57

用示波器查看波形

这是一次采集的全过程

这是唤醒传感器的波形


这是发送读指令的波形

这是读取温湿度信息的波形

龙芯1c的硬件I2C简介

相关寄存器简介

龙芯1c上的I2C有控制寄存器,数据寄存器,命令寄存器,状态寄存器,除此之外,还有分频锁存器。每个寄存器都是8个字节,正是由于每个寄存器都是8个字节,分频锁存器被独立出来,并且分为高低8位两个寄存器。数据寄存器分为发送数据寄存器和接收数据寄存器,一个只能用于发送,一个只能用于接收,偏移地址相同,代码中将其统称为数据寄存器。命令寄存器和状态寄存器的偏移地址也是一样的,命令寄存器只写,状态寄存器只读。
分频锁存器配置I2C总线的时钟频率
控制寄存器中包含使能位
数据寄存器存放待发送或已经接收到的数据
命令寄存器用于控制I2C模块发送不同的I2C命令,比如START,STOP,READ,WRITE,ACK等。
状态寄存器用于获取当前I2C总线的状态。包括总线是否忙,数据是否传输完毕,是否收到应答等。
以上就是我概括的每个寄存器的主要功能,对照龙芯1c的用户手册中的详细描述,很快就能理解的。

封装硬件I2C接口

接口要点

要封装硬件I2C的接口,需要对I2C协议比较熟悉,如果还不熟悉,先自己百度一下。
虽然龙芯1C的I2C模块只有几个寄存器,并且都比较简单。但只靠那几个寄存器的描述就开始写代码似乎还是有点茫然。不妨先查看一下linux源码中的硬件I2C源码,源码位于“drivers\i2c\busses\i2c-ls1x.c”中,如下图所示

上图中,左边是函数列表,其中ls1x_i2c_hwinit()为初始化函数,ls1x_xfer()为发送和接收的总入口,即每次发生和接收都是从这个函数开始的,for循环执行一次就发送或接收一次数据,每次都包括发送START信号,从机地址,数据,STOP信号。在发送了START信号和从机地址后,会根据当前的传输类型(发送或接收),选择相应的函数。比如发送数据则调用ls1x_xfer_write(),接收数据则调用ls1x_xfer_read()。函数ls1x_poll_status()则是查询当前状态的,从函数名字就能看出,是通过poll方式查询状态寄存器的,其中有个超时,即等待多久之后,仍然未能等到某种状态,则超时。好了,有了这些基础,再仔细阅读代码,应该很快能明白的。

发送数据时,先将数据写入数据寄存器,然后把命令写入命令寄存器;读数据时,先写命令,再读数据。

龙芯1c库中的硬件I2C源码清单

ls1c_i2c.h

// 硬件i2c接口的头文件

#ifndef __OPENLOONGSON_I2C_H
#define __OPENLOONGSON_I2C_H



// I2C的时钟频率
#define LS1C_I2C_CLOCK_DEFAULT              (100000)  // Hz. 默认频率
#define LS1C_I2C_CLOCK_MAX                  (400000)  // Hz, max 400 Kbits/sec


// I2C模块编号
typedef enum
{
    LS1C_I2C_0 = 0,
    LS1C_I2C_1,
    LS1C_I2C_2
}ls1c_i2c_t;


// I2C数据传输方向
typedef enum
{
    LS1C_I2C_DIRECTION_WRITE = 0,       // 主机向从机写信息
    LS1C_I2C_DIRECTION_READ,            // 主机向从机读信息
}ls1c_i2c_direction_t;


// 硬件I2C信息
typedef struct
{
    ls1c_i2c_t I2Cx;                    // i2c模块编号
    unsigned long clock;                // i2c时钟频率,单位hz
}ls1c_i2c_info_t;


// I2C应答
typedef enum
{
    LS1C_I2C_ACK = 0,                   // 收到应答
    LS1C_I2C_NACK = 1,                  // 没收到应答
}ls1c_i2c_ack_t;


// 函数返回值
typedef enum
{
    LS1C_I2C_RET_OK = 0,                // OK
    LS1C_I2C_RET_TIMEOUT,               // 超时
}ls1c_i2c_ret_t;



/*
 * 初始化指定i2c模块
 * @i2c_info_p 某个i2c模块的信息
 */
void i2c_init(ls1c_i2c_info_t *i2c_info_p);


/*
 * (再发送一个字节数据之后)接收从机发送的ACK信号
 * @i2c_info_p i2c模块信息
 * @ret LS1C_I2C_ACK or LS1C_I2C_NACK
 */
ls1c_i2c_ack_t i2c_receive_ack(ls1c_i2c_info_t *i2c_info_p);


/*
 * 接收数据
 * @i2c_info_p i2c模块信息
 * @buf 数据缓存
 * @len 待接收数据的长度
 */
ls1c_i2c_ret_t i2c_receive_data(ls1c_i2c_info_t *i2c_info_p, unsigned char *buf, int len);



/*
 * 发送START信号和地址
 * @i2c_info_p i2c模块信息
 * @slave_addr 从机地址
 * @direction 数据传输方向(读、写)
 */
ls1c_i2c_ret_t i2c_send_start_and_addr(ls1c_i2c_info_t *i2c_info_p, 
                                       unsigned char slave_addr,
                                       ls1c_i2c_direction_t direction);


/*
 * 发送数据
 * @i2c_info_p i2c模块信息
 * @data 待发送的数据
 * @len 待发送数据的长度
 */
ls1c_i2c_ret_t i2c_send_data(ls1c_i2c_info_t *i2c_info_p, unsigned char *data, int len);


/*
 * 发送STOP信号
 * @i2c_info_p i2c模块信息
 */
void i2c_send_stop(ls1c_i2c_info_t *i2c_info_p);



#endif

ls1c_i2c.c

// 封装硬件i2c接口

#include "ls1c_public.h"
#include "ls1c_regs.h"
#include "ls1c_clock.h"
#include "ls1c_i2c.h"
#include "ls1c_delay.h"



/*
 * I2C各个寄存器相对基地址的偏移
 * 发送数据寄存器和接收数据寄存器的偏移是相同的
 * 命令寄存器和状态寄存器的偏移是相同的,不同的是命令寄存器只写,状态寄存器只读
 */
#define LS1C_I2C_PRER_LOW_OFFSET            (0)     // 分频锁存器低字节寄存器偏移
#define LS1C_I2C_PRER_HIGH_OFFSET           (1)     // 分频锁存器高字节寄存器偏移
#define LS1C_I2C_CONTROL_OFFSET             (2)     // 控制寄存器偏移
#define LS1C_I2C_DATA_OFFSET                (3)     // 发送数据寄存器和接收数据寄存器的偏移是相同的
#define LS1C_I2C_CMD_OFFSET                 (4)     // 命令寄存器偏移,只写
#define LS1C_I2C_STATUS_OFFSET              (4)     // 状态寄存器偏移,只读


// 控制寄存器的位域
#define LS1C_I2C_CONTROL_EN                 (0x80)  // i2c模块使能
#define LS1C_I2C_CONTROL_IEN                (0x40)  // 中断使能

// 命令寄存器的位域
#define LS1C_I2C_CMD_START                  (0x90)  // 产生START信号
#define LS1C_I2C_CMD_STOP                   (0x40)  // 产生STOP信号
#define LS1C_I2C_CMD_READ                   (0x20)  // 产生读信号,即产生ACK信号
#define LS1C_I2C_CMD_WRITE                  (0x10)  // 产生写信号
#define LS1C_I2C_CMD_READ_ACK               (0x20)  // 产生ACK信号,与读信号相同
#define LS1C_I2C_CMD_READ_NACK              (0x28)  // 产生NACK信号
#define LS1C_I2C_CMD_IACK                   (0x00)  // 产生中断应答信号

// 状态寄存器的位域
#define LS1C_I2C_STATUS_IF                  (0x01)  // 中断标志位
#define LS1C_I2C_STATUS_TIP                 (0x02)  // 指示传输的过程。1,正在传输;0,传输完成
#define LS1C_I2C_STATUS_ARBLOST             (0x20)  // I2C核失去I2C总线的控制权
#define LS1C_I2C_STATUS_BUSY                (0x40)  // I2C总线忙标志
#define LS1C_I2C_STATUS_NACK                (0x80)  // 应答位标志。1,没收到应答位;0,收到应答位


/*
 * 获取指定i2c模块的基地址
 * @I2Cx I2C模块的编号
 */
void *i2c_get_base(ls1c_i2c_t I2Cx)
{
    void *base = NULL;
    
    switch (I2Cx)
    {
        case LS1C_I2C_0:
            base = (void *)LS1C_I2C0_BASE;
            break;

        case LS1C_I2C_1:
            base = (void *)LS1C_I2C1_BASE;
            break;

        case LS1C_I2C_2:
            base = (void *)LS1C_I2C2_BASE;
            break;

        default:
            base = NULL;
            break;
    }

    return base;
}


/*
 * 向命令寄存器写命令
 * @i2c_info_p i2c模块信息
 * @cmd 命令
 */
void i2c_cmd(ls1c_i2c_info_t *i2c_info_p, unsigned char cmd)
{
    void *i2c_base = i2c_get_base(i2c_info_p->I2Cx);

    reg_write_8(cmd, i2c_base + LS1C_I2C_CMD_OFFSET);

    return ;
}


/*
 * 执行START命令,发送START信号
 * @i2c_info_p i2c模块信息
 */
void i2c_cmd_start(ls1c_i2c_info_t *i2c_info_p)
{
    i2c_cmd(i2c_info_p, LS1C_I2C_CMD_START);
    return ;
}


/*
 * 执行STOP命令,发送STOP信号
 * @i2c_info_p i2c模块信息
 */
void i2c_cmd_stop(ls1c_i2c_info_t *i2c_info_p)
{
    i2c_cmd(i2c_info_p, LS1C_I2C_CMD_STOP);
    return ;
}


/*
 * 执行写命令
 * @i2c_info_p i2c模块信息
 */
void i2c_cmd_write(ls1c_i2c_info_t *i2c_info_p)
{
    i2c_cmd(i2c_info_p, LS1C_I2C_CMD_WRITE);
    return ;
}


/*
 * 执行读ack命令,发送读ack信号
 * @i2c_info_p i2c模块信息
 */
void i2c_cmd_read_ack(ls1c_i2c_info_t *i2c_info_p)
{
    i2c_cmd(i2c_info_p, LS1C_I2C_CMD_READ_ACK);
    return ;
}


/*
 * 执行读nack命令,发送读nack信号
 * @i2c_info_p i2c模块信息
 */
void i2c_cmd_read_nack(ls1c_i2c_info_t *i2c_info_p)
{
    i2c_cmd(i2c_info_p, LS1C_I2C_CMD_READ_NACK);
    return ;
}


/*
 * 发送中断应答信号
 * @i2c_info_p i2c模块信息
 */
void i2c_cmd_iack(ls1c_i2c_info_t *i2c_info_p)
{
    i2c_cmd(i2c_info_p, LS1C_I2C_CMD_IACK);
    return ;
}


/*
 * 获取状态寄存器的值
 * @i2c_info_p i2c模块信息
 * @ret 状态寄存器的值
 */
unsigned char i2c_get_status(ls1c_i2c_info_t *i2c_info_p)
{
    void *i2c_base = i2c_get_base(i2c_info_p->I2Cx);

    return reg_read_8(i2c_base + LS1C_I2C_STATUS_OFFSET);
}


/*
 * Poll the i2c status register until the specified bit is set.
 * Returns 0 if timed out (100 msec).
 * @i2c_info_p i2c模块信息
 * @bit 寄存器的某一位
 * @ret true or false
 */
int i2c_poll_status(ls1c_i2c_info_t *i2c_info_p, unsigned long bit)
{
    int loop_cntr = 20000;

    do {
        delay_us(1);
    } while ((i2c_get_status(i2c_info_p) & bit) && (0 < --loop_cntr));

    return (0 < loop_cntr);
}


/*
 * 初始化指定i2c模块
 * @i2c_info_p 某个i2c模块的信息
 */
void i2c_init(ls1c_i2c_info_t *i2c_info_p)
{
    void *i2c_base = i2c_get_base(i2c_info_p->I2Cx);
    unsigned long i2c_clock = i2c_info_p->clock;
    unsigned char ctrl = reg_read_8(i2c_base + LS1C_I2C_CONTROL_OFFSET);
    unsigned long prescale = 0;

    /* make sure the device is disabled */
    ctrl = ctrl & ~(LS1C_I2C_CONTROL_EN | LS1C_I2C_CONTROL_IEN);
    reg_write_8(ctrl, i2c_base + LS1C_I2C_CONTROL_OFFSET);

    // 设置时钟
    i2c_clock = MIN(i2c_clock, LS1C_I2C_CLOCK_MAX);     // 限制在最大允许范围内
    prescale = clk_get_apb_rate();
    prescale = (prescale / (5 * i2c_clock)) - 1;
    reg_write_8(prescale & 0xff, i2c_base + LS1C_I2C_PRER_LOW_OFFSET);
    reg_write_8(prescale >> 8, i2c_base + LS1C_I2C_PRER_HIGH_OFFSET);
    
    // 使能
    i2c_cmd_iack(i2c_info_p);
    ctrl = ctrl | LS1C_I2C_CONTROL_EN;
    reg_write_8(ctrl, i2c_base + LS1C_I2C_CONTROL_OFFSET);

    return ;
}


/*
 * (再发送一个字节数据之后)接收从机发送的ACK信号
 * @i2c_info_p i2c模块信息
 * @ret LS1C_I2C_ACK or LS1C_I2C_NACK
 */
ls1c_i2c_ack_t i2c_receive_ack(ls1c_i2c_info_t *i2c_info_p)
{
    ls1c_i2c_ack_t ret = LS1C_I2C_NACK;
    
    if (LS1C_I2C_STATUS_NACK & i2c_get_status(i2c_info_p))
    {
        ret = LS1C_I2C_NACK;
    }
    else
    {
        ret = LS1C_I2C_ACK;
    }

    return ret;
}


/*
 * 接收数据
 * @i2c_info_p i2c模块信息
 * @buf 数据缓存
 * @len 待接收数据的长度
 */
ls1c_i2c_ret_t i2c_receive_data(ls1c_i2c_info_t *i2c_info_p, unsigned char *buf, int len)
{
    void *i2c_base = i2c_get_base(i2c_info_p->I2Cx);
    int i = 0;

    for (i=0; i<len; i++)
    {
        // 开始接收
        if (i != (len - 1))
            i2c_cmd_read_ack(i2c_info_p);
        else 
            i2c_cmd_read_nack(i2c_info_p);

        // 等待,直到接收完成
        if (!i2c_poll_status(i2c_info_p, LS1C_I2C_STATUS_TIP))
            return LS1C_I2C_RET_TIMEOUT;

        // 读取数据,并保存
        *buf++ = reg_read_8(i2c_base + LS1C_I2C_DATA_OFFSET);
    }

    return LS1C_I2C_RET_OK;
}


/*
 * 发送START信号和地址
 * @i2c_info_p i2c模块信息
 * @slave_addr 从机地址
 * @direction 数据传输方向(读、写)
 */
ls1c_i2c_ret_t i2c_send_start_and_addr(ls1c_i2c_info_t *i2c_info_p, 
                                       unsigned char slave_addr,
                                       ls1c_i2c_direction_t direction)
{
    void *i2c_base = i2c_get_base(i2c_info_p->I2Cx);
    unsigned char data = 0;
    
    // 等待i2c总线空闲
    if (!i2c_poll_status(i2c_info_p, LS1C_I2C_STATUS_BUSY))
        return LS1C_I2C_RET_TIMEOUT;

    // 填充地址到数据寄存器
    data = (slave_addr << 1) | ((LS1C_I2C_DIRECTION_READ == direction) ? 1 : 0);
    reg_write_8(data , i2c_base + LS1C_I2C_DATA_OFFSET);

    // 开始发送
    i2c_cmd_start(i2c_info_p);

    // 等待,直到发送完成
    if (!i2c_poll_status(i2c_info_p, LS1C_I2C_STATUS_TIP))
        return LS1C_I2C_RET_TIMEOUT;

    return LS1C_I2C_RET_OK;
}


/*
 * 发送数据
 * @i2c_info_p i2c模块信息
 * @data 待发送的数据
 * @len 待发送数据的长度
 */
ls1c_i2c_ret_t i2c_send_data(ls1c_i2c_info_t *i2c_info_p, unsigned char *data, int len)
{
    void *i2c_base = i2c_get_base(i2c_info_p->I2Cx);
    int i = 0;

    for (i=0; i<len; i++)
    {
        // 将一个字节数据写入数据寄存器
        reg_write_8(*data++, i2c_base + LS1C_I2C_DATA_OFFSET);

        // 开始发送
        reg_write_8(LS1C_I2C_CMD_WRITE, i2c_base + LS1C_I2C_CMD_OFFSET);

        // 等待,直到发送完成
        if (!i2c_poll_status(i2c_info_p, LS1C_I2C_STATUS_TIP))
            return LS1C_I2C_RET_TIMEOUT;

        // 读取应答信号
        if (LS1C_I2C_ACK != i2c_receive_ack(i2c_info_p))
            return len;
    }

    return LS1C_I2C_RET_OK;
}



/*
 * 发送STOP信号
 * @i2c_info_p i2c模块信息
 */
void i2c_send_stop(ls1c_i2c_info_t *i2c_info_p)
{
    i2c_cmd_stop(i2c_info_p);
    return ;
}

这里只把主要的源文件贴上来了,更多更完整的代码,请移步到http://git.oschina.net/caogos/OpenLoongsonLib1c


Linux中的I2C源码(仅供对比参考)

drivers\i2c\busses\i2c-ls1x.c

/*
 *  Copyright (c) 2013 Tang, Haifeng <[email protected]>
 *
 *  Based partly on i2c-at91.c
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
*/

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/io.h>

#define I2C_CLOCK	CONFIG_LS1X_I2C_CLOCK		/* Hz. max 400 Kbits/sec */

/* registers */
#define OCI2C_PRELOW		0
#define OCI2C_PREHIGH		1
#define OCI2C_CONTROL		2
#define OCI2C_DATA		3
#define OCI2C_CMD		4 /* write only */
#define OCI2C_STATUS		4 /* read only, same address as OCI2C_CMD */

#define OCI2C_CTRL_IEN		0x40
#define OCI2C_CTRL_EN		0x80

#define OCI2C_CMD_START		0x90
#define OCI2C_CMD_STOP		0x40
#define OCI2C_CMD_READ		0x20
#define OCI2C_CMD_WRITE		0x10
#define OCI2C_CMD_READ_ACK	0x20
#define OCI2C_CMD_READ_NACK	0x28
#define OCI2C_CMD_IACK		0x00

#define OCI2C_STAT_IF		0x01
#define OCI2C_STAT_TIP		0x02
#define OCI2C_STAT_ARBLOST	0x20
#define OCI2C_STAT_BUSY		0x40
#define OCI2C_STAT_NACK		0x80


struct ls1x_i2c {
	void __iomem *base;
	struct i2c_adapter adap;
};

static inline void i2c_writeb(struct ls1x_i2c *i2c, int reg, u8 value)
{
	writeb(value, i2c->base + reg);
}

static inline u8 i2c_readb(struct ls1x_i2c *i2c, int reg)
{
	return readb(i2c->base + reg);
}

/*
 * Poll the i2c status register until the specified bit is set.
 * Returns 0 if timed out (100 msec).
 */
static short ls1x_poll_status(struct ls1x_i2c *i2c, unsigned long bit)
{
	int loop_cntr = 20000;

	do {
		udelay(1);
	} while ((i2c_readb(i2c, OCI2C_STATUS) & bit) && (--loop_cntr > 0));

	return (loop_cntr > 0);
}

static int ls1x_xfer_read(struct ls1x_i2c *i2c, unsigned char *buf, int length) 
{
	int x;

	for (x=0; x<length; x++) {
		/* send ACK last not send ACK */
		if (x != (length -1)) 
			i2c_writeb(i2c, OCI2C_CMD, OCI2C_CMD_READ_ACK);
		else
			i2c_writeb(i2c, OCI2C_CMD, OCI2C_CMD_READ_NACK);

		if (!ls1x_poll_status(i2c, OCI2C_STAT_TIP)) {
			dev_dbg(&i2c->adap.dev, "READ timeout\n");
			return -ETIMEDOUT;
		}
		*buf++ = i2c_readb(i2c, OCI2C_DATA);
	}
	i2c_writeb(i2c,OCI2C_CMD, OCI2C_CMD_STOP);
		
	return 0;
}

static int ls1x_xfer_write(struct ls1x_i2c *i2c, unsigned char *buf, int length)
{
	int x;

	for (x=0; x<length; x++) {
		i2c_writeb(i2c, OCI2C_DATA, *buf++);
		i2c_writeb(i2c, OCI2C_CMD, OCI2C_CMD_WRITE);
		if (!ls1x_poll_status(i2c, OCI2C_STAT_TIP)) {
			dev_dbg(&i2c->adap.dev, "WRITE timeout\n");
			return -ETIMEDOUT;
		}
		if (i2c_readb(i2c, OCI2C_STATUS) & OCI2C_STAT_NACK) {
			i2c_writeb(i2c, OCI2C_CMD, OCI2C_CMD_STOP);
			return length;
		}
	}
	i2c_writeb(i2c, OCI2C_CMD, OCI2C_CMD_STOP);

	return 0;
}

static int ls1x_xfer(struct i2c_adapter *adap, struct i2c_msg *pmsg, int num)
{
	struct ls1x_i2c *i2c = (struct ls1x_i2c *)adap->algo_data;
	int i, ret;

//	dev_dbg(&adap->dev, "ls1x_xfer: processing %d messages:\n", num);

	for (i = 0; i < num; i++) {
/*		dev_dbg(&adap->dev, " #%d: %sing %d byte%s %s 0x%02x\n", i,
			pmsg->flags & I2C_M_RD ? "read" : "writ",
			pmsg->len, pmsg->len > 1 ? "s" : "",
			pmsg->flags & I2C_M_RD ? "from" : "to",	pmsg->addr);*/

		if (!ls1x_poll_status(i2c, OCI2C_STAT_BUSY)) {
			return -ETIMEDOUT;
		}

		i2c_writeb(i2c, OCI2C_DATA, (pmsg->addr << 1)
			| ((pmsg->flags & I2C_M_RD) ? 1 : 0));
		i2c_writeb(i2c, OCI2C_CMD, OCI2C_CMD_START);

		/* Wait until transfer is finished */
		if (!ls1x_poll_status(i2c, OCI2C_STAT_TIP)) {
			dev_dbg(&adap->dev, "TXCOMP timeout\n");
			return -ETIMEDOUT;
		}

		if (i2c_readb(i2c, OCI2C_STATUS) & OCI2C_STAT_NACK) {
			dev_err(&adap->dev, "slave addr no ack !!\n");
			i2c_writeb(i2c, OCI2C_CMD, OCI2C_CMD_STOP);
			return 0;
		}

 		if (pmsg->flags & I2C_M_RD)
			ret = ls1x_xfer_read(i2c, pmsg->buf, pmsg->len);
  		else
			ret = ls1x_xfer_write(i2c, pmsg->buf, pmsg->len);

		if (ret)
			return ret;
//		dev_dbg(&adap->dev, "transfer complete\n");
		pmsg++;
	}
	return i;
}

static void ls1x_i2c_hwinit(struct ls1x_i2c *i2c)
{
	struct clk *clk;
	int prescale;
	u8 ctrl = i2c_readb(i2c, OCI2C_CONTROL);

	/* make sure the device is disabled */
	i2c_writeb(i2c, OCI2C_CONTROL, ctrl & ~(OCI2C_CTRL_EN | OCI2C_CTRL_IEN));

	clk = clk_get(NULL, "apb");
	prescale = clk_get_rate(clk);
	prescale = (prescale / (5*I2C_CLOCK)) - 1;
	i2c_writeb(i2c, OCI2C_PRELOW, prescale & 0xff);
	i2c_writeb(i2c, OCI2C_PREHIGH, prescale >> 8);

	/* Init the device */
	i2c_writeb(i2c, OCI2C_CMD, OCI2C_CMD_IACK);
	i2c_writeb(i2c, OCI2C_CONTROL, ctrl | OCI2C_CTRL_EN);
}

static u32 ls1x_functionality(struct i2c_adapter *adap)
{
	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
}

static const struct i2c_algorithm ls1x_algorithm = {
	.master_xfer	= ls1x_xfer,
	.functionality	= ls1x_functionality,
};

static int __devinit ls1x_i2c_probe(struct platform_device *pdev)
{
	struct ls1x_i2c *i2c;
	struct resource *res;
	int rc;

	res =  platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		dev_err(&pdev->dev, "cannot find IO resource\n");
		return -ENOENT;
	}

	if (!(i2c = kzalloc(sizeof(struct ls1x_i2c), GFP_KERNEL))) {
		dev_err(&pdev->dev, "can't allocate inteface!\n");
		rc = -ENOMEM;
		goto fail2;
	}
	
	if (!request_mem_region(res->start, resource_size(res), "ls1x-i2c"))
		return -EBUSY;

	i2c->base = ioremap(res->start, resource_size(res));
	if (!i2c->base) {
		dev_err(&pdev->dev, "i2c-ls1x - failed to map controller\n");
		rc = -ENOMEM;
		goto fail1;
	}

	strlcpy(i2c->adap.name, "loongson1", sizeof(i2c->adap.name));
	i2c->adap.owner   = THIS_MODULE;
	i2c->adap.algo    = &ls1x_algorithm;
	i2c->adap.class   = I2C_CLASS_HWMON;
	i2c->adap.algo_data = i2c;
	i2c->adap.dev.parent = &pdev->dev;
	i2c->adap.nr = pdev->id;

	ls1x_i2c_hwinit(i2c);

	platform_set_drvdata(pdev, i2c);

	rc = i2c_add_numbered_adapter(&i2c->adap);
	if (rc) {
		dev_err(&pdev->dev, "Adapter %s registration failed\n",
				i2c->adap.name);
		goto fail0;
	}

	dev_info(&pdev->dev, "Loongson1 i2c bus driver.\n");
	return 0;

fail0:
	platform_set_drvdata(pdev, NULL);
fail1:
	iounmap(i2c->base);
	release_mem_region(res->start, resource_size(res));
fail2:
	kfree(i2c);

	return rc;
}

static int __devexit ls1x_i2c_remove(struct platform_device *pdev)
{
	struct ls1x_i2c *i2c = platform_get_drvdata(pdev);
	struct resource *res;
	int rc;

	rc = i2c_del_adapter(&i2c->adap);
	platform_set_drvdata(pdev, NULL);

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	iounmap(i2c->base);
	release_mem_region(res->start, resource_size(res));

	return rc;
}

#ifdef CONFIG_PM
static int ls1x_i2c_suspend(struct platform_device *pdev, pm_message_t mesg)
{
	return 0;
}

static int ls1x_i2c_resume(struct platform_device *pdev)
{
	return 0;
}

#else
#define ls1x_i2c_suspend	NULL
#define ls1x_i2c_resume	NULL
#endif

static struct platform_driver ls1x_i2c_driver = {
	.probe		= ls1x_i2c_probe,
	.remove		= __devexit_p(ls1x_i2c_remove),
	.suspend	= ls1x_i2c_suspend,
	.resume		= ls1x_i2c_resume,
	.driver		= {
		.owner	= THIS_MODULE,
		.name	= "ls1x-i2c",
	},
};

static int __init ls1x_i2c_init(void)
{
	return platform_driver_register(&ls1x_i2c_driver);
}

static void __exit ls1x_i2c_exit(void)
{
	platform_driver_unregister(&ls1x_i2c_driver);
}

arch_initcall(ls1x_i2c_init);
module_exit(ls1x_i2c_exit);

MODULE_AUTHOR("TangHaifeng <[email protected]>");
MODULE_DESCRIPTION("I2C driver for Loongson 1A/1B");
MODULE_LICENSE("GPL");


感谢耐心看到这里!


猜你喜欢

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