天天看點

Linux下的I2C總線驅動

版權所有,轉載請說明轉自  http://my.csdn.net/weiqing1981127

                                                                    原創作者:南京郵電大學  通信與資訊系統專業 研二 魏清

一.系統理論

1. I2C驅動體系概述

 Linux I2C驅動體系結構主要由3部分組成,即I2C核心、I2C總線驅動和I2C裝置驅動。I2C核心是I2C總線驅動和I2C裝置驅動的中間樞紐,它以通用的、與平台無關的接口實作了I2C中裝置與擴充卡的溝通。I2C總線驅動填充i2c_adapter和i2c_algorithm結構體。I2C裝置驅動填充i2c_driver和i2c_client結構體。

2. 驅動工程師需要做的事

2.1總線層:根據核心闆的晶片手冊,編寫總線層驅動,例如根據S3C2440中文手冊中I2C總線接口一章實作總線層驅動的編寫。總線層主要向核心注冊一個擴充卡,并填充擴充卡的支援類型和方法,核心加載時,會生成一個總線驅動子產品。

2.2裝置層:裝置驅動層主要是針對不同的I2C硬體裝置編寫驅動程式并為使用者提供接口,核心加載時,會生成一個裝置驅動子產品。I2C子系統下裝置驅動的編寫有兩種模式:一種是使用者模式裝置驅動這種驅動依賴I2C子系統中的i2c-dev這個驅動,我們需要在應用程式去封裝資料,這需要應用程式的開發人員具備相當的硬體基礎,另外一種是普通的裝置驅動。

3.  *****

二.核心代碼

1.      核心/drivers/i2c目錄下檔案分析

(1)  i2c-core.c

這個檔案實作了I2C核心的功能以及/proc/bus/i2c*接口。

(2)i2c-dev.c

實作了I2C擴充卡裝置檔案的功能,每一個I2C擴充卡都被配置設定一個裝置。通過擴充卡通路裝置時的主裝置号都為89,次裝置号為0~255。應用程式通過 “i2c-%d” (i2c-0, i2c-1, ..., i2c-10, ...)檔案名并使用檔案操作接口open()、write()、read()、ioctl()和close()等來通路這個裝置。i2c-dev.c并沒有針對特定的裝置而設計,隻是提供了通用的read()、write()和ioctl()等接口,應用層可以借用這些接口通路挂接在擴充卡上的I2C裝置的存儲空間或寄存器并控制I2C裝置的工作方式。

(3)chips檔案夾

這個目錄中包含了一些特定的I2C裝置驅動,如Dallas公司的DS1337實時鐘晶片、EPSON公司的RTC8564實時鐘晶片和I2C接口的EEPROM驅動等。

(4)busses檔案夾

這個檔案中包含了一些I2C總線的驅動,如S3C2410的I2C控制器驅動為i2c-s3c2410.c

(5)algos檔案夾

實作了一些I2C總線擴充卡的algorithm。

2.      I2C核心

  (1)增加/删除i2c_adapter

int i2c_add_adapter(struct i2c_adapter *adap);

int i2c_del_adapter(struct i2c_adapter *adap);

(2)增加/删除i2c_driver

int i2c_register_driver(struct module *owner, struct i2c_driver *driver);

int i2c_del_driver(struct i2c_driver *driver);

inline int i2c_add_driver(struct i2c_driver *driver);

(3)i2c_client依附/脫離

int i2c_attach_client(struct i2c_client *client);

int i2c_detach_client(struct i2c_client *client);

(4)i2c傳輸、發送和接收

int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num);

int i2c_master_send(struct i2c_client *client,const char *buf ,int count);

int i2c_master_recv(struct i2c_client *client, char *buf ,int count);

3.      I2C總線驅動

我們根據S3C2440核心闆I2C總線接口手冊編寫I2C總線驅動。

3.1硬體部分

在此隻提供mini2440的I2C接口通信協定,S3C2440的I2C控制器主要由4個寄存器完成所有的I2C操作的,這4個寄存器是IICON、IICSTAT、IICADD、IICCDS。(請參見Mini2440手冊) 

3.2軟體部分

首先我們要明白總線層驅動編寫好是放在/drivers/i2c/buses目錄下的。那下面讓我們一起分析下I2c_s3c2410.c這個總線驅動吧。

我們在前面說過,I2C總線驅動層主要是填充i2c_adapter和i2c_algorithm結構體,那我們先來看看這兩個結構體到底是啥玩意兒把!

struct i2c_adapter {

struct module *owner;

unsigned int id;

unsigned int class;        

const struct i2c_algorithm *algo;  //指向擴充卡的驅動程式

void *algo_data;    //指向擴充卡的私有資料

u8 level;               

struct mutex bus_lock;

int timeout;     //逾時時間設定

int retries;    //重試次數

struct device dev;         

int nr;      //IDR機制中ID号,有時也作次裝置号。        

char name[48];

struct completion dev_released;

};

struct i2c_algorithm {

int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,

                  int num);  //指向 I2C總線通信協定的函數

int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,

                  unsigned short flags, char read_write,

                  u8 command, int size, union i2c_smbus_data *data);

            //實作SMBUS總線通信協定的函數,一般置為NULL

u32 (*functionality) (struct i2c_adapter *); //确定擴充卡支援的類型

};

對于特定類型的擴充卡,我們需要在i2c_adapter的基礎上進行擴充,S3C2440對應的擴充卡結構體如下。

struct s3c24xx_i2c {

spinlock_t              lock;

wait_queue_head_t wait;

unsigned int           suspended:1;

struct i2c_msg              *msg;   //擴充卡到裝置真正傳輸資料的結構體

unsigned int           msg_num;  //消息的個數

unsigned int           msg_idx;  //完成了幾個消息

unsigned int           msg_ptr;  //指向目前傳輸的下一個位元組,即在i2c_msg.buf中的

                                         //偏移位置

unsigned int           tx_setup;  //寫一個寄存器的時間,這裡為50ms

unsigned int           irq;

enum s3c24xx_i2c_state       state;  //擴充卡的狀态,包括空閑、開始、讀、寫、停止

unsigned long         clkrate;

void __iomem        *regs;   //I2C      裝置寄存器位址

struct clk        *clk;

struct device          *dev;

struct resource              *ioarea;  //指向擴充卡使用的資源

struct i2c_adapter   adap;   //擴充卡的主體結構體

#ifdef CONFIG_CPU_FREQ

struct notifier_block      freq_transition;

#endif

};

struct i2c_msg {

__u16 addr;    //從機的位址

__u16 flags;  //消息類型标志

__u16 len;      //消息位元組長度

__u8 *buf;      //指向消息資料的緩沖區

};

前面說過,編寫I2C總線驅動層主要是填充i2c_adapter和i2c_algorithm結構體,那麼可以開始了,讓我們先來填充i2c_algorithm結構體吧,代碼如下

static const struct i2c_algorithm s3c24xx_i2c_algorithm = {

.master_xfer          = s3c24xx_i2c_xfer,

.functionality          = s3c24xx_i2c_func,

};

我們先看i2c_algorithm中的master_xfer成員,剛才說過,s3c24xx_i2c_xfer是用來确定擴充卡支援的類型,用于傳回總線支援的協定,具體到代碼如下

static u32 s3c24xx_i2c_func(struct i2c_adapter *adap)

{

return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING;

}

好了,接下來我們把重點放在i2c_algorithm中的functionality成員上,s3c24xx_i2c_func函數用于實作I2C通信協定,将i2c_msg消息傳給I2C裝置。

static int s3c24xx_i2c_xfer(struct i2c_adapter *adap,

               struct i2c_msg *msgs, int num)

{

struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap->algo_data;

int retry;

int ret;

struct s3c2410_platform_i2c *pdata = i2c->dev->platform_data;

if (pdata->cfg_gpio)

        pdata->cfg_gpio(to_platform_device(i2c->dev));

for (retry = 0; retry < adap->retries; retry++) {

        ret = s3c24xx_i2c_doxfer(i2c, msgs, num);   //傳輸到I2C裝置的具體函數

        if (ret != -EAGAIN)

               return ret;

        dev_dbg(i2c->dev, "Retrying transmission (%d)\n", retry);

        udelay(100);

}

return -EREMOTEIO;

}

我們可以發現s3c24xx_i2c_xfer其實主要就是調用s3c24xx_i2c_doxfer完成具體資料的傳輸任務。那我們接着看看s3c24xx_i2c_doxfer做了哪些事情?

static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c,

                     struct i2c_msg *msgs, int num)

{

unsigned long timeout;  //傳輸逾時

int ret;               //傳回傳輸的消息個數

if (i2c->suspended)    //如果擴充卡處于挂起狀态,則傳回

        return -EIO;

ret = s3c24xx_i2c_set_master(i2c);  //将擴充卡設定為主機發送狀态

if (ret != 0) {

        dev_err(i2c->dev, "cannot get bus (error %d)\n", ret);

        ret = -EAGAIN;

        goto out;

}

spin_lock_irq(&i2c->lock);

i2c->msg     = msgs;

i2c->msg_num = num;

i2c->msg_ptr = 0;

i2c->msg_idx = 0;

i2c->state   = STATE_START;    

s3c24xx_i2c_enable_irq(i2c);  //啟動擴充卡的中斷号,允許擴充卡發出中斷

s3c24xx_i2c_message_start(i2c, msgs);  //啟動擴充卡的消息傳輸

spin_unlock_irq(&i2c->lock);

timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);

 //設定等待隊列,直到i2c->msg_num == 0為真或5ms到來才被喚醒

ret = i2c->msg_idx;

if (timeout == 0)

        dev_dbg(i2c->dev, "timeout\n");

else if (ret != num)

        dev_dbg(i2c->dev, "incomplete xfer (%d)\n", ret);

msleep(1);

 out:

return ret;

}

好了,讓我們總結總結,我們可以發現s3c24xx_i2c_doxfer主要做了如下幾件事:第一,将擴充卡設定為主機發送狀态。第二,設定為中斷傳輸方式。第三,發送啟動信号,傳輸第一個位元組。第四,等待逾時或者其他函數在i2c->msg_num == 0時喚醒這裡的等待隊列。

到此為止我們會帶來幾個疑問:第一,s3c24xx_i2c_enable_irq和s3c24xx_i2c_message_start具體怎麼實作的?第二,等待隊列在何時被喚醒呢?

首先我們先來研究第一個問題,s3c24xx_i2c_enable_irq實作開中斷如下

static inline void s3c24xx_i2c_enable_irq(struct s3c24xx_i2c *i2c)

{

unsigned long tmp;

tmp = readl(i2c->regs + S3C2410_IICCON);

writel(tmp | S3C2410_IICCON_IRQEN, i2c->regs + S3C2410_IICCON);

//将IICCON的D5位置1表示總線在接收或發送一個位元組資料後會産生一個中斷。

}

下面看看s3c24xx_i2c_message_start實作啟動擴充卡的消息傳輸的實作吧。

static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c,

                            struct i2c_msg *msg)

{

unsigned int addr = (msg->addr & 0x7f) << 1;  //取從裝置位址低7位,并前移1位

unsigned long stat;   //緩存IICSTAT

unsigned long iiccon;  //緩存IICCON

stat = 0;

stat |=  S3C2410_IICSTAT_TXRXEN; //使能發送接收功能,為寫位址到IICDS

if (msg->flags & I2C_M_RD) {  //如果讀,則主機接收,位址位D0=1

        stat |= S3C2410_IICSTAT_MASTER_RX;

        addr |= 1;

} else                        //如果寫,則主機發送,位址位D0=0

        stat |= S3C2410_IICSTAT_MASTER_TX;

if (msg->flags & I2C_M_REV_DIR_ADDR)

        addr ^= 1;

s3c24xx_i2c_enable_ack(i2c);   //使能ACK響應信号

iiccon = readl(i2c->regs + S3C2410_IICCON);

writel(stat, i2c->regs + S3C2410_IICSTAT);

dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS\n", stat, addr);

writeb(addr, i2c->regs + S3C2410_IICDS);  //寫位址到IICDS寄存器

ndelay(i2c->tx_setup);

dev_dbg(i2c->dev, "iiccon, %08lx\n", iiccon);

writel(iiccon, i2c->regs + S3C2410_IICCON);  //

stat |= S3C2410_IICSTAT_START;  //發送S信号,IICDS寄存器中資料自動發出

writel(stat, i2c->regs + S3C2410_IICSTAT);

}

總結下我們這個3c24xx_i2c_message_start函數吧,這個函數主要做了兩件事,第一使能ACK信号。第二,将從機位址和讀寫方式控制字寫入待IICDS中,由IICSTAT發送S信号,啟動發送從機位址。

嗯,到現在為止我們已經把前面提出的第一個問題解決了,該輪到解決第二個問題了,s3c24xx_i2c_doxfer中的等待隊列何時被喚醒呢?其實分析到現在我們發現s3c24xx_i2c_doxfer調用3c24xx_i2c_message_start隻是發送了一個從機的位址。真正的資料傳輸在哪裡呢?其實真正的資料傳輸我們放在了中斷處理函數中實作的。當資料準備好發送時,将産生中斷,并調用事先注冊的中斷處理函數s3c24xx_i2c_irq進行資料傳輸。中斷的産生其實有3種情況,第一,總線仲裁失敗。第二,當總線還處于空閑狀态,因為一些錯誤操作等原因,意外進入了中斷處理函數。第三,收發完一個位元組的資料,或者當收發到的I2C裝置位址資訊吻合。行,那我們先來看看s3c24xx_i2c_irq到底怎麼來傳輸資料的吧。

static irqreturn_t s3c24xx_i2c_irq(int irqno, void *dev_id)

{

struct s3c24xx_i2c *i2c = dev_id;

unsigned long status;

unsigned long tmp;

status = readl(i2c->regs + S3C2410_IICSTAT);

if (status & S3C2410_IICSTAT_ARBITR) { //仲裁失敗下的處理

        dev_err(i2c->dev, "deal with arbitration loss\n");

}

//當總線為空閑狀态,突然進入中斷,我們需要清除中斷信号,繼續傳輸資料

if (i2c->state == STATE_IDLE) {

        dev_dbg(i2c->dev, "IRQ: error i2c->state == IDLE\n");

        tmp = readl(i2c->regs + S3C2410_IICCON);

        tmp &= ~S3C2410_IICCON_IRQPEND;  //清除中斷信号,繼續傳輸資料

        writel(tmp, i2c->regs +  S3C2410_IICCON);

        goto out;

}

i2s_s3c_irq_nextbyte(i2c, status);   //傳輸或接收下一個位元組

 out:

return IRQ_HANDLED;

}

我們發現其實中斷處理函數s3c24xx_i2c_irq中真正傳輸資料的函數是 i2s_s3c_irq_nextbyte。走了這麼久,其實i2s_s3c_irq_nextbyte才是真正的傳輸資料的核心函數,那我們趕緊來看看  i2s_s3c_irq_nextbyte吧。

static int i2s_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat)

{

unsigned long tmp;

unsigned char byte;

int ret = 0;

switch (i2c->state) {

case STATE_IDLE:       //總線上沒有資料傳輸,則立即傳回

        dev_err(i2c->dev, "%s: called in STATE_IDLE\n", __func__);

        goto out;

        break;

case STATE_STOP:             //發出停止信号P

        dev_err(i2c->dev, "%s: called in STATE_STOP\n", __func__);

        s3c24xx_i2c_disable_irq(i2c);   //關中斷

        goto out_ack;

case STATE_START:            //發出開始信号S

     //當沒有接收到ACK應答信号,說明I2C裝置不存在,應停止總線工作

        if (iicstat & S3C2410_IICSTAT_LASTBIT &&

            !(i2c->msg->flags & I2C_M_IGNORE_NAK)) {

               dev_dbg(i2c->dev, "ack was not received\n");

               s3c24xx_i2c_stop(i2c, -ENXIO); //完成發送P信号,喚醒,關中斷三個事情

               goto out_ack;

        }

        if (i2c->msg->flags & I2C_M_RD)

               i2c->state = STATE_READ;     //一個讀消息

        else

               i2c->state = STATE_WRITE;    //一個寫消息

     // is_lastmsg()判斷是目前處理的消息是否是最後一個消息,如果是傳回1

        if (is_lastmsg(i2c) && i2c->msg->len == 0) {

               s3c24xx_i2c_stop(i2c, 0);

               goto out_ack;

        }

        if (i2c->state == STATE_READ)   //如果是讀那進行跳轉,注此case無break!

               goto prepare_read;

case STATE_WRITE:

  //當沒有接收到ACK應答信号,說明I2C裝置不存在,應停止總線工作

        if (!(i2c->msg->flags & I2C_M_IGNORE_NAK)) {

               if (iicstat & S3C2410_IICSTAT_LASTBIT) {

                      dev_dbg(i2c->dev, "WRITE: No Ack\n");

                      s3c24xx_i2c_stop(i2c, -ECONNREFUSED);

                      goto out_ack;

               }

        }

 retry_write:

// is_msgend(0判斷目前消息是否已經傳輸完所有位元組,如果是傳回1

        if (!is_msgend(i2c)) {

               byte = i2c->msg->buf[i2c->msg_ptr++]; //讀取待傳輸資料

               writeb(byte, i2c->regs + S3C2410_IICDS); //将待傳輸資料寫入IICDS

               ndelay(i2c->tx_setup);        //延時50ms,等待發送到總線上

// is_lastmsg()判斷是目前處理的消息是否是最後一個消息,如果是傳回1

        } else if (!is_lastmsg(i2c)) {  //目前資訊傳輸完,還有資訊要傳輸情況下

               dev_dbg(i2c->dev, "WRITE: Next Message\n");

               i2c->msg_ptr = 0;  //下一條消息字元串的首位址置0

               i2c->msg_idx++;  //表示已經傳輸完1條消息

               i2c->msg++;  //表示準備傳輸下一條消息

               if (i2c->msg->flags & I2C_M_NOSTART) { //不處理此新類型消息,停止

                      if (i2c->msg->flags & I2C_M_RD) {

                             s3c24xx_i2c_stop(i2c, -EINVAL);

                      }

               goto retry_write; //當本消息因類型不被處理則繼續檢視下面是否有消息

               } else { //開始傳輸消息,将IICDS裡的資料發送到總線上

                      s3c24xx_i2c_message_start(i2c, i2c->msg);

                      i2c->state = STATE_START;

               }

        } else { //目前資訊傳輸完,沒有資訊要傳輸情況下,停止總線工作

               s3c24xx_i2c_stop(i2c, 0);

        }

        break;

case STATE_READ:

        byte = readb(i2c->regs + S3C2410_IICDS);  //從IICDS讀取資料

//将讀取到的資料放入緩存區, msg_ptr++直到目前消息傳輸完畢

        i2c->msg->buf[i2c->msg_ptr++] = byte; 

 prepare_read:

     // is_msglast()判斷如果是消息的最後一個位元組,如果是傳回1

        if (is_msglast(i2c)) {

    // is_lastmsg()判斷是目前處理的消息是否是最後一個消息,如果是傳回1

               if (is_lastmsg(i2c))

                      s3c24xx_i2c_disable_ack(i2c); //關閉ACK應答信号

        // is_msgend(0判斷目前消息是否已經傳輸完所有位元組,如果是傳回1

        } else if (is_msgend(i2c)) {

        // is_lastmsg()判斷是目前處理的消息是否是最後一個消息,如果是傳回1

               if (is_lastmsg(i2c)) {

                            dev_dbg(i2c->dev, "READ: Send Stop\n");

                      s3c24xx_i2c_stop(i2c, 0);     //發P信号,喚醒等待隊列

               } else { 

        //目前消息傳輸完畢,但還有其他消息,則将相關指針指向下一條消息

                            dev_dbg(i2c->dev, "READ: Next Transfer\n");

                      i2c->msg_ptr = 0;  //下一條消息字元串的首位址置0

                      i2c->msg_idx++;   //表示已經傳輸完1條消息

                      i2c->msg++;   //表示準備傳輸下一條消息

               }

        }

        break;

}

 out_ack:

tmp = readl(i2c->regs + S3C2410_IICCON);

tmp &= ~S3C2410_IICCON_IRQPEND; //清除中斷,否則會重複執行中斷處理函數

writel(tmp, i2c->regs + S3C2410_IICCON);

 out:

return ret;

}

我們終于把這個龐大的i2s_s3c_irq_nextbyte搞定了,在這裡需要說明幾點,第一,消息分為第一條消息,第二條消息,第三條消息等,總共有msg_num條消息,每發送完一個消息,會msg_idx++。每條消息發送完還需要調用s3c24xx_i2c_message_start進行發送新的起始信号S。第二,第i條消息是一個字元串,按照一個位元組一個位元組的形式發送,由一個指針msg_ptr指向這個字元串的待發送位元組的位址。

        在i2s_s3c_irq_nextbyte這個函數中,我們發現有很多s3c24xx_i2c_stop終止函數,

        那麼讓我們來看看這個函數到底怎麼終止的吧。

static inline void s3c24xx_i2c_stop(struct s3c24xx_i2c *i2c, int ret)

{

unsigned long iicstat = readl(i2c->regs + S3C2410_IICSTAT);

dev_dbg(i2c->dev, "STOP\n");

iicstat &= ~S3C2410_IICSTAT_START;      //發送P信号

writel(iicstat, i2c->regs + S3C2410_IICSTAT);

i2c->state = STATE_STOP;      //設定擴充卡狀态為停止

s3c24xx_i2c_master_complete(i2c, ret);  //喚醒傳輸等待隊列中的程序

s3c24xx_i2c_disable_irq(i2c);   //禁止中斷

}

這個s3c24xx_i2c_stop函數還是很簡單的,但裡面調用了s3c24xx_i2c_master_complete這個函數來喚醒傳輸等待隊列中的程序,那我們就來看看s3c24xx_i2c_master_complete啦。

static inline void s3c24xx_i2c_master_complete(struct s3c24xx_i2c *i2c, int ret)

{

dev_dbg(i2c->dev, "master_complete %d\n", ret);

i2c->msg_ptr = 0;     //下一條消息字元串的首位址置0

i2c->msg = NULL;    //表示沒有可傳輸的消息

i2c->msg_idx++;   //表示已經傳輸完1條消息

i2c->msg_num = 0;  //表示沒有可傳輸的消息

if (ret)

        i2c->msg_idx = ret;    //記錄已經傳輸完的資訊個數

wake_up(&i2c->wait);   //喚醒等待隊列中的程序

}

到此為止,我們已經完成了在I2C總線驅動層填充了i2c_adapter和i2c_algorithm結構體,剩下來我們需要把這兩個結構體外包一下,來注冊這個擴充卡,這怎麼實作呢?當然我們在上面已經分析了中斷處理函數s3c24xx_i2c_irq,那麼這個函數什麼時候被注冊的呢?帶着這兩個問題我們需要繼續往下走,go!

下面兩個函數就完成了I2C總線層驅動子產品的注冊和登出。

static int __init i2c_adap_s3c_init(void)

{ //注冊平台裝置

return platform_driver_register(&s3c24xx_i2c_driver);

}

static void __exit i2c_adap_s3c_exit(void)

{ //登出平台裝置

platform_driver_unregister(&s3c24xx_i2c_driver);

}

那我們來看看這個平台裝置吧。

static struct platform_driver s3c24xx_i2c_driver = {

.probe            = s3c24xx_i2c_probe,  //探測函數

.remove          = s3c24xx_i2c_remove,

.id_table  = s3c24xx_driver_ids,

.driver            = {

        .owner    = THIS_MODULE,

        .name      = "s3c-i2c",

        .pm  = S3C24XX_DEV_PM_OPS,

},

};

恩恩,有一定平台驅動基礎的朋友應該就明白了,平台驅動的這個探測函數s3c24xx_i2c_probe應該就完成了整個擴充卡的注冊和中斷處理函數的注冊工作了。别發呆了,那我們就來看看這個s3c24xx_i2c_probe函數吧。

static int s3c24xx_i2c_probe(struct platform_device *pdev)

{

struct s3c24xx_i2c *i2c;   //擴充卡指針

struct s3c2410_platform_i2c *pdata;   //平台資料

struct resource *res;         //指向資源

int ret;

pdata = pdev->dev.platform_data;   //擷取平台資料

if (!pdata) {

        dev_err(&pdev->dev, "no platform data\n");

        return -EINVAL;

}

i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL);  //配置設定擴充卡空間

if (!i2c) {

        dev_err(&pdev->dev, "no memory for state\n");

        return -ENOMEM;

}

//給擴充卡賦予名字s3c2410-i2c,這個名字會由cat /sys/class/i2c_dev/0/name看到。

strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));

i2c->adap.owner   = THIS_MODULE;

i2c->adap.algo    = &s3c24xx_i2c_algorithm;   //給擴充卡一個通信方法!

i2c->adap.retries = 2;   //兩次總線仲裁嘗試

i2c->adap.class   = I2C_CLASS_HWMON | I2C_CLASS_SPD;

i2c->tx_setup     = 50;    //資料從擴充卡到總線的時間為50ms

spin_lock_init(&i2c->lock);

init_waitqueue_head(&i2c->wait);   //初始化等待隊列

i2c->dev = &pdev->dev; 

i2c->clk = clk_get(&pdev->dev, "i2c");   //擷取i2c時鐘

if (IS_ERR(i2c->clk)) {

        dev_err(&pdev->dev, "cannot get clock\n");

        ret = -ENOENT;

        goto err_noclk;

}

dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);

clk_enable(i2c->clk);              //使能i2c時鐘

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//擷取擴充卡寄存器資源

if (res == NULL) {

        dev_err(&pdev->dev, "cannot find IO resource\n");

        ret = -ENOENT;

        goto err_clk;

}

i2c->ioarea = request_mem_region(res->start, resource_size(res),  //申請I/O記憶體

                              pdev->name);

if (i2c->ioarea == NULL) {

        dev_err(&pdev->dev, "cannot request IO\n");

        ret = -ENXIO;

        goto err_clk;

}

i2c->regs = ioremap(res->start, resource_size(res));  //将記憶體位址映射到虛拟位址

if (i2c->regs == NULL) {

        dev_err(&pdev->dev, "cannot map IO\n");

        ret = -ENXIO;

        goto err_ioarea;

}

dev_dbg(&pdev->dev, "registers %p (%p, %p)\n",

        i2c->regs, i2c->ioarea, res);

i2c->adap.algo_data = i2c;               //将私有資料指向擴充卡結構體

i2c->adap.dev.parent = &pdev->dev;       //組織擴充卡的裝置模型

ret = s3c24xx_i2c_init(i2c);           //初始化I2C控制器

if (ret != 0)

        goto err_iomap;

i2c->irq = ret = platform_get_irq(pdev, 0);   //擷取平台裝置的中斷号

if (ret <= 0) {

        dev_err(&pdev->dev, "cannot find IRQ\n");

        goto err_iomap;

}

ret = request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED,

                 dev_name(&pdev->dev), i2c);       //注冊中斷處理函數

if (ret != 0) {

        dev_err(&pdev->dev, "cannot claim IRQ %d\n", i2c->irq);

        goto err_iomap;

}

ret = s3c24xx_i2c_register_cpufreq(i2c);   //在核心中注冊一個擴充卡使用的時鐘

if (ret < 0) {

        dev_err(&pdev->dev, "failed to register cpufreq notifier\n");

        goto err_irq;

}

i2c->adap.nr = pdata->bus_num; 

ret = i2c_add_numbered_adapter(&i2c->adap);  //向核心中添加擴充卡

if (ret < 0) {

        dev_err(&pdev->dev, "failed to add bus to i2c core\n");

        goto err_cpufreq;

}

platform_set_drvdata(pdev, i2c);  //将I2C擴充卡設定為平台裝置的私有資料

dev_info(&pdev->dev, "%s: S3C I2C adapter\n", dev_name(&i2c->adap.dev));

return 0;

 err_cpufreq:

s3c24xx_i2c_deregister_cpufreq(i2c);

 err_irq:

free_irq(i2c->irq, i2c);

 err_iomap:

iounmap(i2c->regs);

 err_ioarea:

release_resource(i2c->ioarea);

kfree(i2c->ioarea);

 err_clk:

clk_disable(i2c->clk);

clk_put(i2c->clk);

 err_noclk:

kfree(i2c);

return ret;

}

我們來回顧下這個探測函數s3c24xx_i2c_probe吧,這個函數主要幹了六件事。第一,申請一個I2C擴充卡結構體,并對其指派。第二,擷取I2C時鐘資源,并注冊時鐘。第三,擷取資源并最終映射到實體位址。第四,申請中斷處理函數。第五,初始化I2C控制器。第六,将I2C擴充卡添加到核心中。對于resume函數由于做的是跟探測函數相反的操作,在此就無需浪費時間了。

接下來,我們來看看上面第五步初始化I2C控制器所使用的函數s3c24xx_i2c_init。

static int s3c24xx_i2c_init(struct s3c24xx_i2c *i2c)

{

unsigned long iicon = S3C2410_IICCON_IRQEN | S3C2410_IICCON_ACKEN;

struct s3c2410_platform_i2c *pdata;

unsigned int freq;

pdata = i2c->dev->platform_data;

if (pdata->cfg_gpio)  //初始化GPIO口

       pdata->cfg_gpio(to_platform_device(i2c->dev));

writeb(pdata->slave_addr, i2c->regs + S3C2410_IICADD); //寫入從機位址

dev_info(i2c->dev, "slave address 0x%02x\n", pdata->slave_addr);

writel(iicon, i2c->regs + S3C2410_IICCON); //開中斷,ACK信号使能

if (s3c24xx_i2c_clockrate(i2c, &freq) != 0) { //設定時鐘源和時鐘頻率

        writel(0, i2c->regs + S3C2410_IICCON); //失敗則設定為0

        dev_err(i2c->dev, "cannot meet bus frequency required\n");

        return -EINVAL;

}

dev_info(i2c->dev, "bus frequency set to %d KHz\n", freq);

dev_dbg(i2c->dev, "S3C2410_IICCON=0x%02lx\n", iicon);

return 0;

}

在s3c24xx_i2c_init中,我們調用s3c24xx_i2c_clockrate設定了時鐘源和時鐘頻率,繼續看下去。

static int s3c24xx_i2c_clockrate(struct s3c24xx_i2c *i2c, unsigned int *got)

{

struct s3c2410_platform_i2c *pdata = i2c->dev->platform_data;

unsigned long clkin = clk_get_rate(i2c->clk);  //擷取PLCK時鐘,機關為HZ

unsigned int divs, div1;

unsigned long target_frequency;

u32 iiccon;

int freq;

i2c->clkrate = clkin;

clkin /= 1000;        //時鐘頻率機關轉為KHZ

dev_dbg(i2c->dev, "pdata desired frequency %lu\n", pdata->frequency);

target_frequency = pdata->frequency ? pdata->frequency : 100000;

target_frequency /= 1000;   //目标頻率,機關KHZ

freq = s3c24xx_i2c_calcdivisor(clkin, target_frequency, &div1, &divs);  //擷取分頻值

if (freq > target_frequency) {

        dev_err(i2c->dev,

               "Unable to achieve desired frequency %luKHz."   \

               " Lowest achievable %dKHz\n", target_frequency, freq);

        return -EINVAL;

}

*got = freq;

//将分頻值寫入IICCON相應位

iiccon = readl(i2c->regs + S3C2410_IICCON);

iiccon &= ~(S3C2410_IICCON_SCALEMASK | S3C2410_IICCON_TXDIV_512);

iiccon |= (divs-1);

if (div1 == 512)

        iiccon |= S3C2410_IICCON_TXDIV_512;

writel(iiccon, i2c->regs + S3C2410_IICCON);

//如果裝置是2440,則執行下面代碼處理

if (s3c24xx_i2c_is2440(i2c)) {

        unsigned long sda_delay;

        if (pdata->sda_delay) {

               sda_delay = (freq / 1000) * pdata->sda_delay;

               sda_delay /= 1000000;

               sda_delay = DIV_ROUND_UP(sda_delay, 5);

               if (sda_delay > 3)

                      sda_delay = 3;

               sda_delay |= S3C2410_IICLC_FILTER_ON;

        } else

               sda_delay = 0;

        dev_dbg(i2c->dev, "IICLC=%08lx\n", sda_delay);

        writel(sda_delay, i2c->regs + S3C2440_IICLC);

}

return 0;

}

在s3c24xx_i2c_clockratet中,我們調用s3c24xx_i2c_calcdivisor根據已知PCLK和目标頻率,擷取了兩個分頻系數,我們繼續看下去。

static int s3c24xx_i2c_calcdivisor(unsigned long clkin, unsigned int wanted,

                         unsigned int *div1, unsigned int *divs)

{

unsigned int calc_divs = clkin / wanted;

unsigned int calc_div1;

if (calc_divs > (16*16)) 

        calc_div1 = 512;    //IICLK=PCLK/512

else

        calc_div1 = 16;     //IICLK=PCLK/16

calc_divs += calc_div1-1;

calc_divs /= calc_div1;

if (calc_divs == 0)   //控制分頻量程範圍

        calc_divs = 1;

if (calc_divs > 17)   //控制分頻量程範圍

        calc_divs = 17;

*divs = calc_divs;  //分頻系數

*div1 = calc_div1;  //時鐘源的選擇

return clkin / (calc_divs * calc_div1);

}

好了,到現在為止,我們的I2C總線層驅動就已經全部搞定了,我們總結下吧!在基于mini2440的I2C總線層驅動中,我們首先加載了一個平台裝置,在平台裝置的探測函數中,我們主要注冊了擴充卡和中斷處理函數。擴充卡結構體主要是實作通信方法的函數s3c24xx_i2c_xfer,我們在這裡是使用的中斷方式進行通信的,這也是大多數的情況下我們的選擇,當然我們也可以采用查詢的方式進行編寫s3c24xx_i2c_xfer函數,隻需要判斷是讀還是寫操作就可以了。I2C總線層驅動子產品加載後會在sys檔案系統下産生一個擴充卡節點,可以供I2C裝置驅動層來進行探測比對。

4.      I2C裝置驅動

前面已經說過I2C裝置層驅動有兩種實作方式,我們選擇使用者模式裝置驅動方式,這種驅動依賴I2C子系統中的i2c-dev這個驅動。I2C裝置驅動主要填充i2c_driver和i2c_client結構體,同時提供read,write,ioctl等API供應用層使用。在分析裝置驅動層的時候,我們要留意裝置驅動層怎麼找到總線驅動層相應的擴充卡的。

讓我們先一起來填充一下i2c_driver吧

static struct i2c_driver i2cdev_driver = {

.driver = {

        .name      = "dev_driver",

},

.attach_adapter       = i2cdev_attach_adapter,  //探測擴充卡函數

.detach_adapter      = i2cdev_detach_adapter,

};

那下面就需要看看這個結構體怎麼被注冊到核心中的了。

static int __init i2c_dev_init(void)

{

int res;

printk(KERN_INFO "i2c /dev entries driver\n");

res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);   //注冊字元裝置提供API

if (res)

        goto out;

i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");  //注冊類

if (IS_ERR(i2c_dev_class)) {

        res = PTR_ERR(i2c_dev_class);

        goto out_unreg_chrdev;

}

res = i2c_add_driver(&i2cdev_driver);   //調用核心層函數,注冊i2c_driver結構體

if (res)

        goto out_unreg_class;

return 0;

out_unreg_class:

class_destroy(i2c_dev_class);

out_unreg_chrdev:

unregister_chrdev(I2C_MAJOR, "i2c");

out:

printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);

return res;

}

這樣,我們的這個I2C裝置層驅動就被作為子產品加載到核心中了。好了。我們繼續看i2c_driver結構體中的i2cdev_attach_adapter怎麼來探測擴充卡的吧。

static int i2cdev_attach_adapter(struct i2c_adapter *adap)

{

struct i2c_dev *i2c_dev;

int res;

//檢查核心中是否注冊過了擴充卡,如果沒注冊直接傳回,注冊了擴充卡那麼就返

//回一個指向該擴充卡的i2c_dev結構體

i2c_dev = get_free_i2c_dev(adap);  

if (IS_ERR(i2c_dev))

        return PTR_ERR(i2c_dev);

//注冊這個I2C裝置到核心層

i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,

                           MKDEV(I2C_MAJOR, adap->nr), NULL,

                           "i2c-%d", adap->nr);

if (IS_ERR(i2c_dev->dev)) {

        res = PTR_ERR(i2c_dev->dev);

        goto error;

}

res = device_create_file(i2c_dev->dev, &dev_attr_name);  //添加裝置屬性

if (res)

        goto error_destroy;

pr_debug("i2c-dev: adapter [%s] registered as minor %d\n",

         adap->name, adap->nr);

return 0;

error_destroy:

device_destroy(i2c_dev_class, MKDEV(I2C_MAJOR, adap->nr));

error:

return_i2c_dev(i2c_dev);

return res;

}

這樣就完成了擴充卡的探測,至此,我們填充i2c_driver就分析至此了。接下來我們該去填充i2c_client了。此時,我們發現上面提到在i2c_dev_init裡注冊了一個字元裝置,為我們提供了API,那我們來看看這些API裡是否能找到我們填充了的i2c_client結構體呢?

static const struct file_operations i2cdev_fops = {

.owner           = THIS_MODULE,

.llseek            = no_llseek,

.read              = i2cdev_read,

.write             = i2cdev_write,

.unlocked_ioctl       = i2cdev_ioctl,

.open             = i2cdev_open,

.release    = i2cdev_release,

};

有字元裝置常識的朋友,對上面代碼應該很熟悉了。那我們一個個分析這些API吧。

static int i2cdev_open(struct inode *inode, struct file *file)

{

unsigned int minor = iminor(inode);  //由struct inode節點擷取次裝置号

struct i2c_client *client;

struct i2c_adapter *adap;

struct i2c_dev *i2c_dev;

int ret = 0;

lock_kernel();  //此處代碼其實什麼也沒做

//将這個次裝置号當做IDR機制中的ID檢視是否有滿足ID=minor的擴充卡

i2c_dev = i2c_dev_get_by_minor(minor);

if (!i2c_dev) {

        ret = -ENODEV;

        goto out;

}

adap = i2c_get_adapter(i2c_dev->adap->nr);   //擷取比對的擴充卡

if (!adap) {

        ret = -ENODEV;

        goto out;

}

client = kzalloc(sizeof(*client), GFP_KERNEL);   //為i2c_client配置設定空間

if (!client) {

        i2c_put_adapter(adap);

        ret = -ENOMEM;

        goto out;

}

snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);

client->driver = &i2cdev_driver;    //設定i2c_client的驅動

client->adapter = adap;                   //設定i2c_client的擴充卡      

file->private_data = client;        //将i2c_client作為檔案的私有資料

out:

unlock_kernel();

return ret;

}

通過上面分析,在i2cdev_open中,我們發現i2c_client的配置設定并且找到了i2c_client與擴充卡和裝置層驅動的聯系。為了給使用者提供API,字元裝置還有read,write,ioctl。我們主要分析一下i2cdev_ioctl這個函數。

static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)

{

struct i2c_client *client = (struct i2c_client *)file->private_data;

unsigned long funcs;

dev_dbg(&client->adapter->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n",

        cmd, arg);

switch ( cmd ) {

case I2C_SLAVE:

case I2C_SLAVE_FORCE:

        if ((arg > 0x3ff) ||

            (((client->flags & I2C_M_TEN) == 0) && arg > 0x7f))

               return -EINVAL;

        if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg))

               return -EBUSY;

        client->addr = arg;   //設定從機位址

        return 0;

case I2C_TENBIT:   //是否有十位位址晶片的設定

        if (arg)

               client->flags |= I2C_M_TEN;

        else

               client->flags &= ~I2C_M_TEN;

        return 0;

case I2C_PEC:         

        if (arg)

               client->flags |= I2C_CLIENT_PEC;

        else

               client->flags &= ~I2C_CLIENT_PEC;

        return 0;

case I2C_FUNCS:  //擴充卡支援項設定

        funcs = i2c_get_functionality(client->adapter);

        return put_user(funcs, (unsigned long __user *)arg);

case I2C_RDWR:  //讀寫控制

        return i2cdev_ioctl_rdrw(client, arg);

case I2C_SMBUS:  //SMBUS總線通信協定設定

        return i2cdev_ioctl_smbus(client, arg);

case I2C_RETRIES:    //重試次數設定

        client->adapter->retries = arg;

        break;

case I2C_TIMEOUT:   //逾時時間設定

        client->adapter->timeout = msecs_to_jiffies(arg * 10);

        break;

default:

        return -ENOTTY;

}

return 0;

}

在i2cdev_ioctl函數中,我們常用I2C_RETRIES、I2C_TIMEOUT和I2C_RDWR标簽,對于I2C_RDWR标簽,我們可以發現在此調用了i2cdev_ioctl_rdrw函數,為了清楚怎麼進行讀寫控制,我們繼續看看i2cdev_ioctl_rdrw函數吧。

static noinline int i2cdev_ioctl_rdrw(struct i2c_client *client,

        unsigned long arg)

{

struct i2c_rdwr_ioctl_data rdwr_arg;

struct i2c_msg *rdwr_pa;

u8 __user **data_ptrs;

int i, res;

if (copy_from_user(&rdwr_arg,

                  (struct i2c_rdwr_ioctl_data __user *)arg,

                  sizeof(rdwr_arg)))   //複制使用者空間的i2c_rdwr_ioctl_data到核心空間

        return -EFAULT;

if (rdwr_arg.nmsgs > I2C_RDRW_IOCTL_MAX_MSGS)  //限制消息個數

        return -EINVAL;

rdwr_pa = (struct i2c_msg *)               //配置設定t i2c_msg結構體

        kmalloc(rdwr_arg.nmsgs * sizeof(struct i2c_msg),

        GFP_KERNEL);

if (!rdwr_pa)

        return -ENOMEM;

if (copy_from_user(rdwr_pa, rdwr_arg.msgs,

                  rdwr_arg.nmsgs * sizeof(struct i2c_msg))) { //拷貝擷取 i2c_msg内容

        kfree(rdwr_pa);

        return -EFAULT;

}

data_ptrs = kmalloc(rdwr_arg.nmsgs * sizeof(u8 __user *), GFP_KERNEL);

if (data_ptrs == NULL) {   //配置設定消息緩存區失敗

        kfree(rdwr_pa);

       return -ENOMEM;

}

res = 0;

for (i = 0; i < rdwr_arg.nmsgs; i++) {

        if ((rdwr_pa[i].len > 8192) ||

            (rdwr_pa[i].flags & I2C_M_RECV_LEN)) { //限制消息長度

               res = -EINVAL;

               break;

        }

        data_ptrs[i] = (u8 __user *)rdwr_pa[i].buf; //将消息字元串給中間變量

        rdwr_pa[i].buf = kmalloc(rdwr_pa[i].len, GFP_KERNEL);

        if (rdwr_pa[i].buf == NULL) {

               res = -ENOMEM;

               break;

        }

        if (copy_from_user(rdwr_pa[i].buf, data_ptrs[i],

                         rdwr_pa[i].len)) {  //将中間變量傳給核心中的i2c_msg結構體

                      ++i;

                      res = -EFAULT;

               break;

        }

}

if (res < 0) {   //配置設定空間失敗時需呀釋放空間資源

        int j;

        for (j = 0; j < i; ++j)

               kfree(rdwr_pa[j].buf);

        kfree(data_ptrs);

        kfree(rdwr_pa);

        return res;

}

//調用核心層i2c_transfer函數完成資料的傳輸

res = i2c_transfer(client->adapter, rdwr_pa, rdwr_arg.nmsgs);

while (i-- > 0) {

 //如果傳輸的資料後期需要讀取,則将傳輸的資料放在中間變量的儲存

        if (res >= 0 && (rdwr_pa[i].flags & I2C_M_RD)) {

               if (copy_to_user(data_ptrs[i], rdwr_pa[i].buf,

                              rdwr_pa[i].len))

                      res = -EFAULT;

        }

        kfree(rdwr_pa[i].buf);  //釋放資源

}

kfree(data_ptrs);

kfree(rdwr_pa);

return res;

}

我們再來看看i2cdev_ioctl_rdrw這個函數,其實這個函數隻是對使用者空間傳過來的資料進行截取分類存放,然後調用i2c_transfer進行資料傳輸,我們繼續跟蹤下去。

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

{

unsigned long orig_jiffies;

int ret, try;

if (adap->algo->master_xfer) {

#ifdef DEBUG

        for (ret = 0; ret < num; ret++) {

               dev_dbg(&adap->dev, "master_xfer[%d] %c, addr=0x%02x, "

                      "len=%d%s\n", ret, (msgs[ret].flags & I2C_M_RD)

                      ? 'R' : 'W', msgs[ret].addr, msgs[ret].len,

                      (msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : "");

        }

#endif

        if (in_atomic() || irqs_disabled()) {

               ret = mutex_trylock(&adap->bus_lock);

               if (!ret)

                      return -EAGAIN;

        } else {

               mutex_lock_nested(&adap->bus_lock, adap->level);

        }

        orig_jiffies = jiffies;

        for (ret = 0, try = 0; try <= adap->retries; try++) {

               ret = adap->algo->master_xfer(adap, msgs, num);  //真正的資料傳輸

               if (ret != -EAGAIN)

                      break;

               if (time_after(jiffies, orig_jiffies + adap->timeout))

                      break;

        }

        mutex_unlock(&adap->bus_lock);

        return ret;

} else {

        dev_dbg(&adap->dev, "I2C level transfers not supported\n");

        return -EOPNOTSUPP;

}

}

通過上面的跟蹤,我們很快發現i2c_transfer其實是調用了master_xfer函數進行的資料傳輸。至于master_xfer幹什麼的,我們再I2C總線層驅動分析中已經講的很清楚了,master_xfer就是我們擴充卡的I2C通信協定函數。

好了,I2C的裝置驅動層我們也講完了,回顧一下,I2C的裝置驅動層主要是填充了i2c_driver和i2c_client結構體,然後注冊了i2c_driver,在i2c_driver中,我們探測了擴充卡。同時為了給使用者提供API,我們還注冊了一個字元裝置,在字元裝置中的open函數中,我們完成了i2c_client結構體的填充,并擷取了比對的擴充卡。最後我們講了ioctl函數,重點分析了I2C_RDWR标簽下最終調用擴充卡的I2C通信協定函數master_xfer完成資料傳輸。

5.      *****

三.測試代碼

我們已經分析了mini2440的I2C總線層驅動已經核心為我們提供的i2c-dev.c檔案。系統中i2c-dev.c檔案定義的主裝置号為89的裝置可以友善地給應用程式提供讀寫I2C裝置寄存器的能力,使得工程師大多數時候并不需要為具體的I2C裝置驅動定義檔案操作接口。在此我們就使用i2c-dev.c檔案為使用者提供API。我們利用mini2440外接一個256byte的E2PROM晶片AT24C08進行I2C驅動的測試。根據I2C裝置位址的設計規則,要求D7位為讀寫位,D6-D3位為器件類型,D2-D0為自定義位址。我們可以通過檢視mini2440外圍電路發現AT24C08的A0-A2均接地,表明AT24C08自定義位址D2-D0=000,而AT24C08器件類型編号為1010,是以我們的從機裝置AT24C08位址為0X50。

實驗環境:核心linux2.6.32.2,arm-linux-gcc交叉編譯器,mini2440開發闆。

核心配置:選中核心層i2c_core.c、裝置驅動層i2c-dev.c和總線驅動層I2c_s3c2410.c

為了使用者空間與核心空間的資訊互動,我們還需要在使用者空間引入核心空間的兩個結構體i2c_msg和i2c_rdwr_ioctl_data。具體測試代碼如下

#include <stdio.h>

#include <linux/types.h>

#include <stdlib.h>

#include <fcntl.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/ioctl.h>

#include <errno.h>

#define I2C_RETRIES 0x0701

#define I2C_TIMEOUT 0x0702

#define I2C_RDWR 0x0707

struct i2c_msg

{

unsigned short addr;   //從機的位址

unsigned short flags;  //設定讀寫

#define I2C_M_TEN 0x0010

#define I2C_M_RD 0x0001

unsigned short len;  //一個消息字元串的位元組數

unsigned char *buf;  //消息字元串

};

struct i2c_rdwr_ioctl_data

{

 struct i2c_msg *msgs; 

 int nmsgs;    //傳輸消息的個數

 };

int main()

{

 int fd,ret;

 struct i2c_rdwr_ioctl_data e2prom_data;

 fd=open("/dev/i2c/0",O_RDWR);

//dev/i2c/0是在注冊i2c-dev.c後産生的,代表一個可操作的擴充卡。如果不使用i2c-dev.c

//的方式,就沒有,也不需要這個節點

 if(fd<0){

              perror("open error");

              }

  e2prom_data.nmsgs=2;   //傳輸的資訊個數

  e2prom_data.msgs=(struct i2c_msg*)malloc(e2prom_data.nmsgs*sizeof(struct i2c_msg));

  if(!e2prom_data.msgs){

         perror("malloc error");

               exit(1);

         }

 ioctl(fd,I2C_TIMEOUT,100);   //逾時時間100ms

 //ioctl(fd,I2C_M_TEN,0);

 ioctl(fd,I2C_RETRIES,2);     //重試次數2次

 e2prom_data.nmsgs=1;       //傳輸1條資訊

 (e2prom_data.msgs[0]).len=2;   //資訊長度為2個位元組

 (e2prom_data.msgs[0]).addr=0x50;//從機的位址

 (e2prom_data.msgs[0]).flags=0; //  flag=0表示寫

 (e2prom_data.msgs[0]).buf=(unsigned char*)malloc(2);  //申請空間

 (e2prom_data.msgs[0]).buf[0]=0x01;//第一個傳輸的資料是AT24C08存儲空間的位址

 (e2prom_data.msgs[0]).buf[1]=0x74;//第二個傳輸的資料是向AT24C08位址空間寫的數

 ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);  //調用ioctl

  if(ret<0){

  perror("ioctl error1");

         }

   sleep(1);

 e2prom_data.nmsgs=2;     //傳輸2條資訊

 (e2prom_data.msgs[0]).len=1; //資訊的長度為1個位元組

 (e2prom_data.msgs[0]).addr=0x50; //從機的位址

 (e2prom_data.msgs[0]).flags=0;// flag=0表示寫

 (e2prom_data.msgs[0]).buf[0]=0x01;//第一個傳輸的資料是AT24C08存儲空間的位址

 (e2prom_data.msgs[1]).len=1;//資訊的長度為1個位元組

 (e2prom_data.msgs[1]).addr=0x50;//從機的位址

 (e2prom_data.msgs[1]).flags=I2C_M_RD;// flag= I2C_M_RD表示讀

 (e2prom_data.msgs[1]).buf=(unsigned char*)malloc(1); //申請空間

 (e2prom_data.msgs[1]).buf[0]=0;  //清空

 ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data); //調用ioctl

 if(ret<0){

      perror("ioctl error2");

        }

  printf("buff[0]=%x\n",(e2prom_data.msgs[1]).buf[0]);  //列印讀到的資料,應該是0x74

  close(fd);

  return 0;

}

測試結果:

虛拟機下編譯arm-linux-gcc  i2c.c  -o i2c

在超級終端下運作:./i2c

可以見到:buff[0]=74