Linux操作系統的I2C驅動


 一、Linux的I2C體系結構

  Linux I2C體系結構分為3個組成部分

(1)Linux核心

  I2C核心提供了I2C總線驅動和設備驅動的注冊、注銷方法,I2C通信方法(algorithm)上層的,與具體適配器無關的代碼以及探測設備、檢測設備地址的上層代碼等。

(2)I2C總線驅動

  I2C總線驅動是對I2C硬件體系結構中適配器端的實現,適配器可由CPU控制,甚至可以直接集成在CPU內部。I2C總線驅動主要包含了I2C適配器數據結構i2c_adapter、I2C適配器的algorithm數據結構i2c_algorithm和控制I2C適配器產生通信信號的函數。經由I2C總線驅動的代碼,我們可以控制I2C適配器以主控方式產生開始位、停止位、讀寫周期,以及以從設備方式被讀寫、產生ACK等。

(3)I2C設備驅動

  I2C設備驅動是對I2C硬件體系結構中設備端的實現,設備一般掛接在首CPU控制的I2C適配器上,通過I2C適配器與CPU交換數據。I2C設備驅動主要包含了數據結構i2c_driver和i2c_client,我們需要根據具體設備實現其中的成員函數。

  在Linux內核源代碼中的drivers目錄下包含一個i2c目錄,而在i2c目錄下又包含如下文件和文件夾:

  • i2c_core.c

  這個文件實現了I2C核心的功能以及/proc/bus/i2c*接口

  • i2c_dev.c

  實現了I2C適配器設備文件的功能,每一個I2C適配器都被分配為一個設備。通過適配器訪問設備是的主設備號都為89,次設備號為0~255.應用程序通過“i2c-%d”(i2c-0,i2c-1,i2c-2,...i2c-10,...)文件名並使用文件操作結構open()/write()/read()/ioctl()和close()等來訪問這個設備。

  i2c-dev.c並沒有針對特定的設備而設計,只是提供了通用read()等接口,應用程序可以借用這些接口訪問適配器上的I2C設別的存儲空間或寄存器,並控制I2C設別的工作方式。

  • chips文件夾

  這個目錄中包含了一些特定的I2C設備驅動。在具體的I2C設別驅動中,調用的都是I2C核心提供的API,因此,這使得具體的I2C設備驅動不依賴於CPU類型和I2C適配器的硬件特性。

  • buses文件夾

  這個文件中包含了一些I2C總線的驅動。如針對S3C2410/S3C2440等處理器的I2C控制器驅動。

  • algos文件夾

   實現了一些I2C總線適配器的algorithm。

  此外,內核中的i2c.h這個頭文件對i2c_driver/i2c_client/i2c_adapter和i2c_algotithm這4個數據結構進行了定義。理解這4個數據結構作用有利於后續的理解。

i2c_adapter結構體

struct i2c_adapter {
  struct module *owner;      /* 所屬模塊 */
  unsigned int id;         /* algorithm的類型,定義於i2c-id.h,以I2C_ALGO_開始 */
  unsinged int class;
  struct i2c_algorithm *algo;   /* 總線通信方法結構體指針 */
  void *algo_data;         /* algorithm 數據 */
  int (*client_register) (struct i2c_client *);  /* client注冊時調用 */
  int (*client_unregister) (struct i2c_client *); /* client注銷時調用 */
  u8 level;
  struct semphore bus_lock;
  struct semphore clist_lock;
  int timeout;
  int retries;            /* 重試次數 */
  struct device dev;         /* 適配器設備 */
  struct class_device class_dev;  /* 類設備 */
  int nr;
  struct list_head client;
  struct list_head list;
  char name[48];           /* 適配器名稱 */
  struct completion dev_released; /* 用於同步 */
};

i2c_algoritm結構體

struct i2c_algorithm {
  int (*master_xfer) (struct i2c_adapter, struct i2c_msg *msgs, int num);  /* i2c 傳輸函數指針 */
  int (*subus_xfer) (struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write,
             u8 command, int size, union i2c_smbus_data *data);
  u32 (*functionality) (struct i2c_adapter *adap);
};
 
        

i2c_driver結構體

struct i2c_driver {
  int id;
  unsigned int class;
  int (*attach_adapter) (struct i2c_adapter *);  /* 依附i2c_adapter 函數指針 */
  int (*detach_adapter) (struct i2c_adapter *);  /* 脫離i2c_adapter 函數指針 */
  int (*detach_client) (struct i2c_client *);   /* 脫離i2c_client 函數指針 */
  int (*probe) (struct i2c_client *, const struct i2c_device_id *);
  int (*remove) (struct i2c_client *);
  void (*shutdown) (struct i2c_client *);
  int (*suspend) (struct i2c_client *, pm_message_t mesg);
  int (*resume) (struct i2c_client *);
  int (*command) (struct i2c_client *,unsigned int cmd, void *arg);
  struct device_driver driver;
  const struct i2c_device_id *id_table;
  int (*detect) (struct i2c_client *,int kind, struct i2c_board_info *);
  const struct i2c_client_address_data *address_data;
  struct list_head clients;
};

i2c_client結構體

struct i2c_client {
  unsigned int flags;
  unsigned short addr;  /* 低7位芯片地址 */
  char name[I2C_NAMD_SIZE];  /* 設備名稱 */
  struct i2c_adapter *adapter;
  struct i2c_driver *driver;
  struct device dev;
  int irq;
  struct list_head list;
  struct completion released;
};

(1)i2c_adapter和i2c_algorithm

  i2c_adapter對應於物理上的一個適配器,而i2c_algorithm對應一套通信方法。一個I2C適配器需要i2c_algorithm中提供的通信函數來控制適配器上產生特定的訪問周期。缺少i2c_algorithm的i2c_adapter什么也做不了,因此i2c_adapter中包含其使用的i2c_algorithm的指針。

  i2c_algorithm中的關鍵函數master_xfer()用於產生I2C訪問周期需要的信號,以i2c_msg為單位,i2c_msg結構體也非常關鍵。

i2c_msg結構體

struct i2c_msg {
  __u16 addr;  /* 設備地址 */
  __u16 flags;  /* 標志 */
  __u16 len;   /* 消息長度 */
  __u8 *buf;   /* 消息數據 */
};

(2)i2c_driver和i2c_client

  i2c_driver對應一套驅動方法,其主要成員函數probe()、remove()等,另外id_table是該驅動所支持的I2C設備的ID表。i2c_client對應於真實的物理設備,每個I2C設備都需要一個i2c_client來描述。i2c_driver與i2c_client的關系是一對多,一個i2c_dirver上可以支持多個同等類型的client。

  i2c_client信息通常在BSP的板文件中通過i2c_board_info填充,如下面代碼就定義了一個i2c設備ID為“ad7142_joystick”、地址為0x2C、中斷號為IRQ_PF5的i2c_client:

  在I2C總線驅動i2c_bus_type的match()函數i2c_device_match()中,會調用i2c_match_id()函數匹配板文件的ID和i2c_driver所支持的ID表。

(3)i2c_adapter與i2c_client

 

   雖然I2C硬件體系結構比較簡單,但是I2C體系結構在Linux中的實現卻相當復雜。當工程師拿到實際的電路板,面對復雜的Linux I2C子系統,應該如何下手寫驅動呢?究竟有哪些是需要親自做的,哪些是內核已經提供的呢?理解這個問題非常有意義,可以使我們面對具體問題時迅速地抓住重點。

  一方面,適配器驅動可能是Linux內核本身還不包含的;另一方面,掛接在適配器上的具體設備驅動可能也是Linux內核還不包含的,因此,功能是要實現的主要工作如下:

  • 提供I2C適配器的硬件驅動,探測、初始化I2C適配器(如申請I2C的I/O地址和中斷號)、驅動CPU控制I2C適配器從硬件上產生各種信號以及處理I2C中斷等。
  • 驅動I2C適配器的algorithm,用具體適配器的xxx)xfer()函數填充i2c_algorithm的master_xfer指針,並把i2c_algorithm指針復制給i2c_adapter的algo指針。
  • 實現I2C設備驅動中的i2c_driver接口,用具體設備的yyy_probe()、yyy_remove()、yyy_suspend()、yyy_resume()函數指針和i2c_device_id設備的ID表復制給i2c_driver的probe/remove/suspend/resume.
  • 實現I2C設備所對應類型的具體驅動,i2c_driver只是實現設備與總線的連接,而掛機啊在總線上的設備則是千差萬別的,例如,如果是字符設備,就實現文件操作接口,即實現具體設備yyy的yyy_read()、yyy_write和yyy_ioctl函數等;如果是聲卡,就實現ALSA驅動。

  上述工作中前兩個屬於I2C總線驅動,后兩個屬於I2C設備驅動,做完這些工作,系統會增加兩個內核模塊。

 

二、Linux I2C核心

  I2C核心(drivers/i2c/i2c-core.c)提供了一組不依賴於平台的接口函數。這個文件一般不需要被工程師修改,但是理解其中的主要函數非常關鍵,因為I2C總線驅動和設備驅動之間依賴於I2C核心作為組帶,I2C和心中的主要函數如下:

(1)增加/刪除i2c_adapter:

int i2c_add_adapter(struct i2c_adapter *adapter);
int i2c_del_adapter(struct i2c_adapter *adapter);

(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);

   當一個具體的client被偵測到並被關聯的時候,設備和sysfs文件將被注冊。相反地,在client被取消關聯的時候,sysfs文件盒設備也被注銷。

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

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *mags, 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);
 
        

  i2c_transfer()函數用於進行I2C適配器和I2C設備之間的一組消息交互,i2c_master_send()函數和i2c_master_recv()函數內部會調用i2c_transfer函數分別完成一條寫消息和一條讀消息。

代碼如下:

int i2c_master_send(struct i2c_client *client, const char *buf, int count)
{
  int ret;
  struct i2c_adapter *adap = client->adapter;
}

  i2c_transfer函數本身不具備驅動適配器物理硬件完成消息交互的能力,他只是尋找到i2c_adapter對應的i2c_algorithm,並使用i2c_algorithm的master_xfer()函數真正驅動硬件流程。

 

三、Linux I2C總線驅動

1 I2C適配器驅動加載和卸載

I2C總線驅動模塊的加載函數要完成兩個工作:

  • 初始化I2C適配器所有的硬件資源,如申請I/O地址、中斷號。
  • 通過i2c_add_adapter()添加i2c_adapter的數據結構,當然這個數據結構的成員已經被xxx適配器的響應函數指針所初始化。

I2C總線驅動模塊的卸載函數要完成的工作與加載函數相反。

  • 釋放I2C適配器所使用的硬件資源,如釋放I/O地址,中斷號等。
  • 通過i2c_del_adapter刪除i2c_adapter的數據結構。

I2C總線驅動的模塊加載和卸載函數:

static int __init i2c_adapter_xxx_init(void)
{
  xxx_adapter_hw_init();
  i2c_add_adapter(&xxx_adapter);
}

static int __exit i2c_adapter_xxx_exit(void)
{
  xxx_adapter_hw_freee();
  i2c_del_adapter(&xxx_adapter);
}

  上述代碼中xxx_adapter_hw_init和xxx_adapter_hw_free函數的實現都與具體的CPU和I2C適配器硬件相關。

2 I2C總線通信方法

  我們需要為特定的I2C適配器實現其通信方法,主要實現i2c_algorithm的master_xfer()函數和functionality()函數。functionality()函數非常簡單,用於返回algorithm所支持的通信協議,如I2C_FUNC_I2C、I2C_FUNC_10BIT_ADDR、I2C_FUNC_SMBUS_BYTE、I2C_FUNC_SMBUS_WRITE_BYTE等。master_xfer()函數在I2C適配器上完成傳遞給他的i2c_msg數組中的每個I2C消息,代碼清單如下:

static

  上述代碼實際上給出了一個master_xfer()函數處理I2C消息數組的流程,對於數組中的每個消息,判斷消息類型,若為都消息,則賦從設備地址為(msg->addr<<1)|1,否則為msg->addr<<1,對每個消息產生一個開始位,緊接着傳送從設備地址,然后開始數據的發送或接收,對最后的消息還需產生一個停止位。如圖所示

 

  master_xfer函數模板中的i2c_adapter_xxx_start()、i2c_adapter_xxx_setaddr()、i2c_adapter_xxx_wait_ack()、i2c_adapter_xxx_readbytes()、i2c_adapter_xxx_writebytes()和i2c_adapter_xxx_stop()函數用於完成適配器的底層硬件操作,與I2C適配器和CPU具體硬件直接相關,需要由驅動工程師根據芯片的數據手冊來實現。

 

四、Linux I2C設備驅動

  I2C設備驅動要使用i2c_driver和i2c_client數據結構並填充i2c_driver中的成員函數,i2c_client一般被包含在設備的私有信息結構體yyy_data中,而i2c_driver則適合被定義為全局變量並初始化,代碼如下:

static struct i2c_driver yyy_driver = {
  .driver = {
    .name = "yyy",
  },
  .probe  = yyy_probe,
  .remove  = yyy_remove,
  .id_table = yyy_id,
};

1 Linux I2C設備驅動的模塊加載與卸載

  I2C設備驅動的模塊加載函數通用方法是在I2C設備驅動的模塊加載函數進行,通過I2C核心的i2c_add_driver()函數添加i2c_driver的工作,而在模塊卸載函數中需要做相反的工作;通過I2C狠心的i2c_del_driver()函數刪除i2c_drivdr,I2C設備驅動的加載工作與寫在功函數模板如下:

static int __init yyy_init(void)
{
  return i2c_add_adapter(&yyy_driver);
}

static void __exit yyy_exit(void)
{
  return i2c_del_adapter(&yyy_driver);
}

2 Linux I2C設備驅動的數據傳輸

  在I2C設備上讀寫數據的時序和數據通常通過i2c_msg數組組織,最后通過i2c_transfer()函數完成,代碼清單所示為一個都去指定偏移offs寄存器的例子。

struct i2c_msg msg[2];
/* 第一條消息是寫消息 */
msg[0].addr = client->addr;
msg[0].flags = 0;
msg[0].len = 1;
msg[0].buf = &offs;

/* 第二條消息是讀消息 */
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD;
msg[1].len = sizeof(buf);
msg[1].buf = &buf[0];

i2c_transfer(client->adapter,msg,2);

3 Linux的i2c_dev.c文件分析

  i2c-dev.c文件完全可以被看作一個I2C設備驅動。不過,它實現的一個i2c_client是虛擬的、臨時的。鎖着設備文件的打開而產生,並隨設備文件的關閉而撤銷,並沒有被添加到i2c_adapter的client鏈表中。i2c-dev.c針對每個I2C適配器生成一個主設備號為89的設備文件,實現了i2c_driver的成員函數以及文件操作接口,所以i2c_dev.c的主題是“i2c_driver成員函數 + 字符設備驅動”。

  i2c-dev.c中提供i2ccdev_read()/i2cdev_write()函數來對應用戶空間要使用的read()/write()文件操作接口,這兩個函數分別調用i2c_master_recv()和i2c_master_send()函數來構造一條I2C消息並印發適配器algorithm通信函數的調用。但是,很遺憾,大多數稍微復雜一點I2C設備的讀寫流程並不對應於一條消息,往往需要兩條甚至更多的消息來進行一次讀寫周期,這種情況下,在應用層仍然調用read()、write()文件的API來讀寫I2C設備,將不能正確地讀寫。許多工程師碰到類似的問題,往往經過相當長時間的調試都沒法解決I2C設備的讀寫,連錯誤的原因也無法找到,顯然是對i2cdev_read()和i2cdec_write()函數的作用有所誤解。

  鑒於上述原因,i2c-dev.c中i2cdev_read()和i2cdev_write()函數不具備太強的通用性,沒有踢啊大的使用價值。只能適用於非RepStart模式的情況。對於兩條以上的消息組成的讀寫,在用戶空間需要重新組織i2c-msg消息並調用I2C_RDWR_IOCTL命令。

 

五、S3C2410 I2C總線驅動實例

1 S3C2410 I2C控制器硬件描述

  S3C2410處理器內部集成了一個I2C控制器,通常4個寄存器就可方便的對其進行控制,這4個寄存器如下:

  • IICCON:I2C控制寄存器
  • IICSTAT:I2C狀態寄存器
  • IICDS:I2C收發數據移位寄存器
  • IICADD:I2C地址寄存器

  S3C2410處理器內部集成的I2C控制器可支持主、從兩種模式,我們主要使用其主模式。通過對IICCON、IICDS和IICADD寄存器的操作,可在I2C總線上產生開始位、停止位、數據和地址位,而傳輸的狀態則通過IICSTAT寄存器獲取。

2 S3C2410 I2C總線驅動總體分析

  S3C2410的I2C總線驅動drivers/i2c/busses/i2c-s3c2410.c支持S3C24xx、S3C64xx、S5PC1xx和S5P6xx處理器。它主要完成的工作有:

  • 設計對應於i2c_adapter_xxx_init()模板的S3C2410的模塊加載函數和對應於i2c_adapter_xxx_exit()函數模板的模塊卸載函數。

設計對應於i2c_adapter_xxx_xfer()模板的S3C2410適配器的通信方法函數。

  針對S3C24xx、S3C64xx、S5PC1xx和S5P64xx處理器、functionality()函數s3c24xx_i2c_func()只需簡單地返回I2C_FUNC_I2C|I2C_FUNC_SMBUS_EMUL|I2C_FUNC_PROTOCOL_MANGLING表明其支持的功能。

   

3 S3C2410 I2C適配器驅動的模塊加載和卸載

  I2C適配器驅動被作為一個單獨的模塊加載進內核,在模塊的加載和卸載函數中,只需注冊和注銷一個platform_driver結構體,如下所示:

static int __init i2c_adap_s3c_init(void)
{
  int ret;
  ret = platform_driver_register(&s3c24xx_i2c_driver);

  return ret;
}

static void __exit i2c_adap_s3c_exit(void)
{
  platform_driver_unregister(&s3c24xx_i2c_driver);
}

platform_driver結構體包含了具體適配器的probe()函數、romove()函數、resume()函數指針等信息,他需要被定義和賦值。代碼如下:

static struct platform_driver s3c24xx_i2c_driver = {
  .probe = s3c24xx_i2c_probe,
  .remove = s3c24xx_i2c_remove,
  .resume = s3c24xx_i2c_resume,
  .driver = {
    .name = THIS_MODULE,
    .name = "s3c24xx-i2c",
  },
};

當通過Linux內核源碼/drivers/base/platform.c文件中定義platform_driver_unregister()函數注冊platform_driver結構體時,其中probe指針指向的s3c24xx_i2c_probe()函數將被調用,已初始化適配器硬件。代碼如下

static int s3c24xx_i2c_probe(struct platform_device *pde)
{  
  struct s3c24xx_i2c *i2c;
struct s3c2410_platform_i2c *npd;
  struct resource *resource;
  int retval;
  
  npd = pdev->dev.platform_data;
  if(!npd)
  {
    dev_err(&pdev->dev,"no platform data\n");
    return -EINVAL;
  }

  i2c = kmalloc(sizeof(struct s3c24xx_i2c),GFP_KERNEL);
  memset(i2c,0,sizeof(struct s3c24xx_i2c));
  if(!i2c)
  {
    dev_err(&pdev->dev,"no memory for state\n");
    return -ENOMEM;
  }
  
  strlcpy(i2c->adap.name,"s3c24xx-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;

  spin_lock_init(&i2c->lock);
  init_waitqueue_head(&i2c->wait);
  i2c->dev = &pdev->dev;
  i2c->clk = clk_get(&pdev->dev,"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);  
  resource = platform_get_resource(pdev,IORESOURCE_MEM,0);
  i2c->ioarea = request_mem_region(resource->start,resource_size(resource),pdev->name);
  i2c->regs = ioremap(res->start,resource_size(resource));
  i2c->adap.algo_data = i2c;
  i2c->adap.dev.parent = &pdev->dev;
  ret = s3c24xx_i2c_init(i2c);
  i2c->irq = platform_get_irq(pdev,0);
  ret = request_irq(i2c->irq,s3c24xx_i2c_irq,IRQF_DISABLED,dev_name(&pdev->dev),i2c);
  ret = s3c24xx_i2c_register_cpufreq(i2c);
  i2c->adap.nr = pdata->bus_num;
  ret = i2c_add_numbered_adapter(&i2c->adap);
  platform_set_drvdata(pdev,i2c);
}
 
        

  上述代碼中的主體工作使能硬件並申請I2C適配器使用的I/O地址、中斷號等,在這些工作度完成無誤后,通過I2C核心提供的i2c_add_adapter()函數添加這個適配器。當處理器包含多個I2C控制器時,我們通過板文件定義的platform數據中的bus_num進行區分。 

  與s3c24xx_i2c_probe()函數完成相反功能的函數是s3c24xx_i2c_remove()函數,他在適配器模塊卸載函數調用platform_driver_unregister()函數是通過platform_driver的remove指針方式被調用。xxx_i2c_remove()的設計模塊如下:

static int s3c24xx_i2c_remove(struct platform_device *pdev)
{
  struct s3c24xx_i2c *i2c = platform_get_drvdata(pdev);
  s3c24xx_i2c_derefister_cpufreq(i2c);
  
  i2c_del_adapter(&i2c_adap);
  free_irq(i2c->irq,i2c);
  
  clk_disable(i2c->clk);
  clk_put(i2c->clk);
  iounmap(i2c->regs);
  release_resource(i2c->ioarea);
  kfree(i2c->ioarea);
  kfree(i2c);

  return 0;
}

代碼清單的s3c24xx_i2c結構體進行適配器所有信息的封裝。其結構體定義如下:

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;

  unsigned int		tx_setup;
  unsigned int		irq;

  enum s3c24xx_i2c_state	state;
  unsigned long		clkrate;

  void __iomem		*regs;
  struct clk		*clk;
  struct device		*dev;
  struct resource		*ioarea;
  struct i2c_adapter	adap;

#ifdef CONFIG_CPU_FREQ
  struct notifier_block	freq_transition;
#endif
};

4 S3C2410 I2C總線通信方法

  由代碼清單可以看出,I2C配置適配器對應的i2c_algorithm結構體實例為s3c24xx_i2c_algorithm。

static struct i2c_algorithm s3c24xx_i2c_algorithm = {
  .master_xfer = s3c24xx_i2c_xfer,
  .functionality = s3c24xx_i2c_func,
};

  上述代碼第一行指定了S3C2410 I2C總線通信傳輸函數s3c24xx_i2c_xfer(),這個函數非常關鍵,所有的I2C總線上對設備的訪問最周應該由他來完成,代碼清單所示這個重要函數以及其依賴的s3c24xx_i2c_doxfer()函數和s3c24xx_i2c_message_start()函數的源代碼。

/* s3c24xx_i2c_xfer
 *
 * first port of call from the i2c bus code when an message needs
 * transferring across the i2c bus.
*/

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);     if (ret != -EAGAIN)       return ret;      
    dev_dbg(i2c->dev, "Retrying transmission (%d)\n", retry);     udelay(100);   }   return -EREMOTEIO; }
/* s3c24xx_i2c_doxfer
 *
 * this starts an i2c transfer
*/

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);   ret = i2c->msg_idx;   /* having these next two as dev_err() makes life very    * noisy when doing an i2cdetect */   if (timeout == 0)     dev_dbg(i2c->dev, "timeout\n");   else if (ret != num)     dev_dbg(i2c->dev, "incomplete xfer (%d)\n", ret);   /* ensure the stop has been through the bus */   msleep(1); out:   return ret; }
/* s3c24xx_i2c_message_start
 *
 * put the start of a message onto the bus
*/
static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c, struct i2c_msg *msg)
{
  unsigned int addr = (msg->addr & 0x7f) << 1;
  unsigned long stat;
  unsigned long iiccon;

  stat = 0;
  stat |=  S3C2410_IICSTAT_TXRXEN;

  if (msg->flags & I2C_M_RD) 
  {     stat |= S3C2410_IICSTAT_MASTER_RX;     addr |= 1;   }
  else
  {     stat |= S3C2410_IICSTAT_MASTER_TX;
  }   if (msg->flags & I2C_M_REV_DIR_ADDR)
  {     addr ^= 1;
  }   /* todo - check for wether ack wanted or not */   s3c24xx_i2c_enable_ack(i2c);   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);   /* delay here to ensure the data byte has gotten onto the bus    * before the transaction is started */   ndelay(i2c->tx_setup);   dev_dbg(i2c->dev, "iiccon, %08lx\n", iiccon);   writel(iiccon, i2c->regs + S3C2410_IICCON);   stat |= S3C2410_IICSTAT_START;   writel(stat, i2c->regs + S3C2410_IICSTAT); } 

  s3c24xx_i2c_xfer()函數調用s3c24xx_i2c_doxfer函數傳輸I2C消息。s3c24xx_i2c_doxfer()首先將s3c2410的I2C適配器設置為I2C主設備,氣候初始化s3c24xx_i2c結構體,使能I2C中斷,並調用s3c24xx_i2c_message_start()函數啟動I2C消息的傳輸。s3c24xx_i2c_message_start()函數寫s3c2410適配器對應的控制寄存器,向I2C從設備傳遞開始位和從設備地址。

  上述代碼只是啟動了I2C消息數組的傳輸周期,並沒有完整實現圖給出的algorithm master_xfer流程。這個流程的完整實現需要借助I2C適配器上的中斷來步步推進。下述代碼所示為S3C2410 I2C適配器中斷處理函數以及其依賴的i2c_s3c_irq_nextbyte()函數的源代碼。

/* s3c24xx_i2c_irq
 *
 * top level IRQ servicing routine
 */

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)
  {     /* deal with arbitration loss */     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;   }   /* pretty much this leaves us with the fact that we've    * transmitted or received whatever byte we last sent */   i2s_s3c_irq_nextbyte(i2c, status); out:   return IRQ_HANDLED; }
/* i2s_s3c_irq_nextbyte
 *
 * process an interrupt and work out what to do
 */

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:
    dev_err(i2c->dev, "%s: called in STATE_STOP\n", __func__);
    s3c24xx_i2c_disable_irq(i2c);
    goto out_ack;

  case STATE_START:
    /* last thing we did was send a start condition on the
     * bus, or started a new i2c message
     */

    if (iicstat & S3C2410_IICSTAT_LASTBIT &&!(i2c->msg->flags & I2C_M_IGNORE_NAK)) 
    {       /* ack was not received... */       dev_dbg(i2c->dev, "ack was not received\n");       s3c24xx_i2c_stop(i2c, -ENXIO);       goto out_ack;     }     if (i2c->msg->flags & I2C_M_RD)       i2c->state = STATE_READ;     else       i2c->state = STATE_WRITE;     /* terminate the transfer if there is nothing to do      * as this is used by the i2c probe to find devices. */     if (is_lastmsg(i2c) && i2c->msg->len == 0)
    {       s3c24xx_i2c_stop(i2c, 0);       goto out_ack;     }     if (i2c->state == STATE_READ)       goto prepare_read;   /* fall through to the write state, as we will need to    * send a byte as well */   case STATE_WRITE:     /* we are writing data to the device... check for the      * end of the message, and if so, work out what to do      */     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:     if (!is_msgend(i2c))
    {       byte = i2c->msg->buf[i2c->msg_ptr++];       writeb(byte, i2c->regs + S3C2410_IICDS);       /* delay after writing the byte to allow the        * data setup time on the bus, as writing the        * data to the register causes the first bit        * to appear on SDA, and SCL will change as        * soon as the interrupt is acknowledged */       ndelay(i2c->tx_setup);     }
     else if (!is_lastmsg(i2c))
    {       /* we need to go to the next i2c message */       dev_dbg(i2c->dev, "WRITE: Next Message\n");       i2c->msg_ptr = 0;       i2c->msg_idx++;       i2c->msg++;       /* check to see if we need to do another message */       if (i2c->msg->flags & I2C_M_NOSTART)
      {         if (i2c->msg->flags & I2C_M_RD)
        {           /* cannot do this, the controller            * forces us to send a new START            * when we change direction */           s3c24xx_i2c_stop(i2c, -EINVAL);         }         goto retry_write;       }
      else
      {         /* send the new start */         s3c24xx_i2c_message_start(i2c, i2c->msg);         i2c->state = STATE_START;       }     }
    else
    {       /* send stop */       s3c24xx_i2c_stop(i2c, 0);     }     break;   case STATE_READ:     /* we have a byte of data in the data register, do      * something with it, and then work out wether we are      * going to do any more read/write      */     byte = readb(i2c->regs + S3C2410_IICDS);     i2c->msg->buf[i2c->msg_ptr++] = byte; prepare_read:     if (is_msglast(i2c))
    {       /* last byte of buffer */       if (is_lastmsg(i2c))         s3c24xx_i2c_disable_ack(i2c);     }
    else if (is_msgend(i2c))
    {       /* ok, we've read the entire buffer, see if there        * is anything else we need to do */       if (is_lastmsg(i2c))
      {         /* last message, send stop and complete */         dev_dbg(i2c->dev, "READ: Send Stop\n");         s3c24xx_i2c_stop(i2c, 0);       }
      else
      {         /* go to the next transfer */         dev_dbg(i2c->dev, "READ: Next Transfer\n");         i2c->msg_ptr = 0;         i2c->msg_idx++;         i2c->msg++;       }     }     break;   }   /* acknowlegde the IRQ and get back on with the work */ out_ack:   tmp = readl(i2c->regs + S3C2410_IICCON);   tmp &= ~S3C2410_IICCON_IRQPEND;   writel(tmp, i2c->regs + S3C2410_IICCON); out:   return ret; }

  中斷處理函數s3c24xx_i2c_irq()主要通過調用i2c_s3c_irq_nextbyte()函數進行傳輸工作的進一步推進。i2s_s3c)irq_nexbyte函數通過switch(i2c->state)的不同狀態進行處理,在每種狀態下,先檢查i2c_state的狀態和硬件寄存器應該處於的狀態是否一致,如果不一致,則證明有誤,直接返回。當I2C處於讀狀態STATE_READ或寫狀態STATE_WRITE時,通過is_lastmsg()函數判斷是否傳輸的是最后一條I2C消息。如果是,則產生停止位,否則通過i2c_msg_idx++、i2c->msg++推進到下一條消息。

 

六、總結

  Linux I2C驅動體系結構有相當的復雜度,它主要由3部分組成,即I2C核心,I2C總線驅動和I2C設備驅動。I2C核心是I2C總線驅動和I2C設備驅動的中間樞紐,它以通用的、與平台無關的接口實現了I2C中設備與適配器的溝通。I2C總線驅動填充了i2c_adapter和i2c_algorithm結構體,I2C設備驅動填充i2c_driver結構體並實現其本身所對應的設備類型的驅動。

  另外,系統中的i2c-dev.c文件定義的主設備號為89的設備可以方便的給應用程序提供讀寫I2C設備寄存器的能力,是的工程師大多數時候並不需要為具體的I2C設備驅動定義文件操作接口。  

 

  

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM