1. 程式人生 > >LINUX的IIC驅動從這開始(四)

LINUX的IIC驅動從這開始(四)

轉載地址:http://blog.csdn.net/xie0812/article/details/24291153

首先這篇文章主要介紹IIC匯流排的實現過程,怎麼說了,本人也是一個小菜鳥,可能有許多錯誤在裡面,如有大神發現,請指出來,多謝多謝!

注意:平臺還是和前面的一樣,所以分析三星的iic匯流排實現,當然這部分,可能不需要咱們驅動工程師實現,但本人認為好好研究這部分內容有助於提高水平,也能更好的理解linux的裝置模型,不過今天僅僅討論三星iic匯流排驅動具體是怎麼操作s5pv210上的iic模組的,至於涉及到的platform匯流排還有裝置模型會在接下來的文章中討論,希望能給像我一樣的菜鳥一點幫助!

1、首先說一下主要的討論內容,主要是通過解釋下面幾個問題為指引展開的

(1)怎麼把s5pv210上的iic模組註冊到linux中的?

(2)iic模組是怎麼獲得時鐘的,以及怎麼樣才能調節時鐘的快慢?

(3)iic的匯流排驅動是怎麼註冊到iic-core當中的?

(4)具體怎麼把一個字元通過片上iic模組傳送出去?

好吧,咱們就按問題來吧。三星的iic匯流排驅動的位置在:linux-3.0.8/drivers/i2c/busses/i2c-s3c2410.c就是這個檔案了,整個程式也就是1000行吧。那先從init和exit看起吧,這是看驅動的出發點嘛,為方便起見,我就直接附上程式碼了

  1. staticint __init i2c_adap_s3c_init(
    void)  
  2. {  
  3.     return platform_driver_register(&s3c24xx_i2c_driver);  
  4. }  
  5. subsys_initcall(i2c_adap_s3c_init);  
  6. staticvoid __exit i2c_adap_s3c_exit(void)  
  7. {  
  8.     platform_driver_unregister(&s3c24xx_i2c_driver);  
  9. }  
  10. module_exit(i2c_adap_s3c_exit);  
可以很清楚的看到,它使用platform匯流排以驅動的形式註冊到核心裡面的。這裡注意一下,匯流排也是裝置,當然可能你早已經知道了。是裝置嘛,肯定就需要驅動了,要不然linux的應用層就沒法用這個iic模組了,那這是驅動,那對應的裝置的資訊在什麼地方了?說明一下,我用的是友善的開發板,所以就直接用友善提供的linux原始碼包了,你可以在linux-3.0.8/arch/arm/mach-s5pv210/mach-mini210.c這個檔案中看到如下的函式:
  1. static
    struct platform_device *mini210_devices[] __initdata = {  
  2.     ...  
  3.     <span style="color:#ff0000;">&s3c_device_i2c0,  
  4.     &s3c_device_i2c1,  
  5.     &s3c_device_i2c2,</span>}  
看到了吧,用platform_device結構體定義的這個指標陣列中,有上面的三項,這是三項就是s5pv210上面的iic模組的資訊,你可以點進去看看。當然,這僅僅是資訊,還沒有註冊到platform總線上了,還是在這個檔案中,下面的程式碼就是註冊裝置了。

static void __init mini210_machine_init(void)
{
......
platform_add_devices(mini210_devices, ARRAY_SIZE(mini210_devices));
......
}
上面的這個函式就是把剛才那個指標陣列中定義的裝置資訊註冊到platform總線上了。至於當給platform總線上註冊一個驅動後核心如何進行操作,這裡就不討論了,等那天再寫一篇吧。當註冊到platform總線上的驅動的名字和已經註冊到platform上的裝置的名字匹配後,就會呼叫probe函式,其實許多事都是在probe函式做的,我下面直接把程式碼附上,直接在程式碼裡做解釋吧。

  1. staticint s3c24xx_i2c_probe(struct platform_device *pdev)  
  2. {  
  3.     struct s3c24xx_i2c *i2c;  
  4.     struct s3c2410_platform_i2c *pdata;  
  5.     struct resource *res;  
  6.     int ret;  
  7.     pdata = pdev->dev.platform_data;  //通過這裡獲得了,iic模組的一些附加資訊,包括匯流排裝置號、iic的時鐘頻率等
  8.     if (!pdata) {  
  9.         dev_err(&pdev->dev, "no platform data\n");  
  10.         return -EINVAL;  
  11.     }  
  12.     i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL);  
  13.     if (!i2c) {  
  14.         dev_err(&pdev->dev, "no memory for state\n");  
  15.         return -ENOMEM;  
  16.     }  
  17.         //下面的這幾句很關鍵,你應該還記得iic中的adapter這個結構體吧,它就代表一個iic介面卡
  18.     strlcpy(i2c->adap.name, "s3c2410-i2c"sizeof(i2c->adap.name));//給匯流排取名字
  19.     i2c->adap.owner   = THIS_MODULE;  
  20.     i2c->adap.algo    = <span style="color:#ff0000;">&s3c24xx_i2c_algorithm</span>;//這個是最重要的,是iic介面卡對應的通訊方法,也就是具體怎麼把資料通過iic模組傳輸出去的方法,這也是我們的主要問題之一,所以會在後面好好討論一下
  21.     i2c->adap.retries = 2;  
  22.     i2c->adap.class   = I2C_CLASS_HWMON | I2C_CLASS_SPD;  
  23.     i2c->tx_setup     = 50;//這個在通訊方法裡面可以看到,就是每次傳送資料後的要延時的ns數
  24.     spin_lock_init(&i2c->lock);  
  25.     init_waitqueue_head(&i2c->wait);  
  26.     /* find the clock and enable it */
  27. <span style="white-space:pre">  </span>  
  28.     i2c->dev = &pdev->dev;  
  29.     i2c->clk = clk_get(&pdev->dev, "i2c");//獲取iic介面卡的時鐘,至於具體通過怎麼用名字匹配獲取等,這個這裡就不說了
  30.     if (IS_ERR(i2c->clk)) {  
  31.         dev_err(&pdev->dev, "cannot get clock\n");  
  32.         ret = -ENOENT;  
  33.         goto err_noclk;  
  34.     }  
  35.     dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);  
  36.     clk_enable(i2c->clk);//這裡就把iic裝置的時鐘給使能了,僅僅是使能,通訊的頻率不是這裡調節的啊,具體怎麼調節通訊的頻率高低我會詳細介紹
  37.     /* map the registers */
  38. <span style="white-space:pre">  </span>//下面就是通過呼叫相應的函式獲取我們在mach-mini210.c檔案裡註冊到platform總線上的iic介面卡的資訊了,下面是獲取記憶體資源
  39.     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);  
  40.     if (res == NULL) {  
  41.         dev_err(&pdev->dev, "cannot find IO resource\n");  
  42.         ret = -ENOENT;  
  43.         goto err_clk;  
  44.     }  
  45. <span style="white-space:pre">  </span>//這裡根據獲取到的地址和大小,在記憶體中申請這樣一塊大小的記憶體區
  46.     i2c->ioarea = request_mem_region(res->start, resource_size(res),  
  47.                      pdev->name);  
  48.     if (i2c->ioarea == NULL) {  
  49.         dev_err(&pdev->dev, "cannot request IO\n");  
  50.         ret = -ENXIO;  
  51.         goto err_clk;  
  52.     }  
  53. <span style="white-space:pre">  </span>//這裡就是用申請到的記憶體做實際的映射了
  54.     i2c->regs = ioremap(res->start, resource_size(res));  
  55.     if (i2c->regs == NULL) {  
  56.         dev_err(&pdev->dev, "cannot map IO\n");  
  57.         ret = -ENXIO;  
  58.         goto err_ioarea;  
  59.     }  
  60.     dev_dbg(&pdev->dev, "registers %p (%p, %p)\n",  
  61.         i2c->regs, i2c->ioarea, res);  
  62.     /* setup info block for the i2c core */
  63.     i2c->adap.algo_data = i2c;  
  64.     i2c->adap.dev.parent = &pdev->dev;  
  65.     /* initialise the i2c controller */
  66. <span style="white-space:pre">  </span>//這個函式裡,主要就是通過配置iic介面卡的幾個暫存器,來完成實際的介面卡的初始化,上面的過程主要是獲得介面卡的資源,比如說地址了,暫存器基地址什麼的
  67.     ret = s3c24xx_i2c_init(i2c);  
  68.     if (ret != 0)  
  69.         goto err_iomap;  
  70.     /* find the IRQ for this unit (note, this relies on the init call to 
  71.      * ensure no current IRQs pending 
  72.      */
  73. <span style="white-space:pre">  </span>//下面這句話就是獲取iic介面卡的中斷資源,其實iic介面卡實際傳輸資料時用中斷的方式完成的,所以作用是可想而知了
  74.     i2c->irq = ret = platform_get_irq(pdev, 0);  
  75.     if (ret <= 0) {  
  76.         dev_err(&pdev->dev, "cannot find IRQ\n");  
  77.         goto err_iomap;  
  78.     }  
  79. <span style="white-space:pre">  </span>//這就是把中斷註冊到核心中的函數了,注意這裡的s3c24xx_i2c_irq這個中斷服務函式,其實在這裡真真完成資料傳輸
  80.     ret = request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED,  
  81.               dev_name(&pdev->dev), i2c);  
  82.     if (ret != 0) {  
  83.         dev_err(&pdev->dev, "cannot claim IRQ %d\n", i2c->irq);  
  84.         goto err_iomap;  
  85.     }  
  86.     ret = s3c24xx_i2c_register_cpufreq(i2c);  
  87.     if (ret < 0) {  
  88.         dev_err(&pdev->dev, "failed to register cpufreq notifier\n");  
  89.         goto err_irq;  
  90.     }  
  91.     /* Note, previous versions of the driver used i2c_add_adapter() 
  92.      * to add the bus at any number. We now pass the bus number via 
  93.      * the platform data, so if unset it will now default to always 
  94.      * being bus 0. 
  95.      */
  96.     i2c->adap.nr = pdata->bus_num;  
  97. <span style="white-space:pre">  </span>//下面這個函式式iic-core裡面的函式,這個函式的作用就是把上面獲取的iic介面卡的註冊為iic匯流排裝置,具體的實現可以看看iic-core
  98.     ret = i2c_add_numbered_adapter(&i2c->adap);  
  99.     if (ret < 0) {  
  100.         dev_err(&pdev->dev, "failed to add bus to i2c core\n");  
  101.         goto err_cpufreq;  
  102.     }  
  103.     platform_set_drvdata(pdev, i2c);  
  104.     dev_info(&pdev->dev, "%s: S3C I2C adapter\n", dev_name(&i2c->adap.dev));  
  105.     clk_disable(i2c->clk);  
  106.     return 0;  
  107.  err_cpufreq:  
  108.     s3c24xx_i2c_deregister_cpufreq(i2c);  
  109.  err_irq:  
  110.     free_irq(i2c->irq, i2c);  
  111.  err_iomap:  
  112.     iounmap(i2c->regs);  
  113.  err_ioarea:  
  114.     release_resource(i2c->ioarea);  
  115.     kfree(i2c->ioarea);  
  116.  err_clk:  
  117.     clk_disable(i2c->clk);  
  118.     clk_put(i2c->clk);  
  119.  err_noclk:  
  120.     kfree(i2c);  
  121.     return ret;  
  122. }  
看到上面的用紅顏色標出來的s3c24xx_algorithm這個結構裡吧,這個結構體實現了iic介面卡具體的通訊方法,具體如下:
  1. staticconststruct i2c_algorithm s3c24xx_i2c_algorithm = {  
  2.     .master_xfer        = s3c24xx_i2c_xfer, //這個函式是具體的通訊方法
  3.     .functionality      = s3c24xx_i2c_func, //這個函式描述的iic介面卡的功能特性
  4. };  
由於s3c24xx_i2c_xfer是真真的通訊方法,那咱們就再看看它的具體實現吧,看這神祕的通訊方法。
  1. staticint s3c24xx_i2c_xfer(struct i2c_adapter *adap,  
  2.             struct i2c_msg *msgs, int num)  
  3. {  
  4.     struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap->algo_data;  
  5.     int retry;  
  6.     int ret;  
  7.     clk_enable(i2c->clk); //使能時鐘嘛
  8.     for (retry = 0; retry < adap->retries; retry++) {  //這個迴圈來完成實際的資料傳輸,迴圈的次數可以自定義了,這是為了保證資料傳輸的可靠性嘛
  9.         ret = s3c24xx_i2c_doxfer(i2c, msgs, num);//還記得iic裡面重要的那個幾個資料結構嘛,其中一個就是msg吧,在linux中,把要通過iic傳輸的具體資料會封裝成msg結構的一個包,它裡面包含的要傳輸資料的長度,裝置的地址,要傳輸的資料。看出來吧,還沒到到真真傳輸資料了,核心會呼叫這個函式
  10.         if (ret != -EAGAIN) { //你看,只要以傳輸成功,馬上就返回了
  11.             clk_disable(i2c->clk);  
  12.             return ret;  
  13.         }  
  14.         dev_dbg(i2c->dev, "Retrying transmission (%d)\n", retry);  
  15.         udelay(100);  
  16.     }  
  17.     clk_disable(i2c->clk);  
  18.     return -EREMOTEIO;  
  19. }  
好吧,還沒到真真傳輸資料的函式,這黃金一層又一層的,但這樣寫的確是有道理的,就比如說通過上面的for迴圈能加強資料傳輸的可靠性。好吧,咱們就硬著堅硬的頭繼續吧。
  1. staticint s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c,  
  2.                   struct i2c_msg *msgs, int num)  
  3. {  
  4.     unsigned long timeout;  
  5.     int ret;  
  6.     if (i2c->suspended)  
  7.         return -EIO;  
  8.     ret = s3c24xx_i2c_set_master(i2c); //這個函式就是看iic介面卡是否在忙,如果在忙就等待,直到把得到這個iic介面卡的使用權
  9.     if (ret != 0) {  
  10.         dev_err(i2c->dev, "cannot get bus (error %d)\n", ret);  
  11.         ret = -EAGAIN;  
  12.         goto out;  
  13.     }  
  14.     spin_lock_irq(&i2c->lock);  
  15.     i2c->msg     = msgs;    
  16.     i2c->msg_num = num;  
  17.     i2c->msg_ptr = 0;  
  18.     i2c->msg_idx = 0;  
  19.     i2c->state   = STATE_START;  //上面這幾句就是把msg結構體賦值給在前面全域性定義的iic結構體,因為還要包含其他資訊嘛
  20.     s3c24xx_i2c_enable_irq(i2c);//終於使能了中斷,看來的確快到實際傳輸的函數了
  21.     s3c24xx_i2c_message_start(i2c, msgs);//看這個函式的名字就知道,應該開始傳輸了
  22.     spin_unlock_irq(&i2c->lock);  
  23.     timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);  
  24.     ret = i2c->msg_idx;  
  25.     /* having these next two as dev_err() makes life very 
  26.      * noisy when doing an i2cdetect */
  27.     if (timeout == 0)  
  28.         dev_dbg(i2c->dev, "timeout\n");  
  29.     elseif (ret != num)  
  30.         dev_dbg(i2c->dev, "incomplete xfer (%d)\n", ret);  
  31.     /* ensure the stop has been through the bus */
  32.     udelay(10);  
  33.  out:  
  34.     return ret;  
  35. }  
哎,還是沒有到真真要傳輸的函式啊,那為什麼要寫這個函式啊,因為在有三個iic模組嘛,當然多少模組都行,這樣寫可以讓三個模組或更多的模組公用這個函式,減少程式碼重複量嘛,膜拜一下這些高手吧,好吧,生活還得繼續,那咱們也不能不前進啊,看看s3c24xx_i2c_message_irq這個函式吧
  1. staticvoid s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c,  
  2.                       struct i2c_msg *msg)  
  3. {  
  4.     unsigned int addr = (msg->addr & 0x7f) << 1;  
  5.     unsigned long stat;  
  6.     unsigned long iiccon;  
  7.     stat = 0;  
  8.     stat |=  S3C2410_IICSTAT_TXRXEN;  
  9.     if (msg->flags & I2C_M_RD) {  //看到了吧,這就是msg結構體裡面的flag起的作用,判斷是否是接收資料,也就是讀資料了
  10.         stat |= S3C2410_IICSTAT_MASTER_RX;  
  11.         addr |= 1;  
  12.     } else
  13.         stat |= S3C2410_IICSTAT_MASTER_TX;//這當然就是傳送資料了
  14.     if (msg->flags & I2C_M_REV_DIR_ADDR)  
  15.         addr ^= 1;  
  16.     /* todo - check for wether ack wanted or not */
  17.     s3c24xx_i2c_enable_ack(i2c);  
  18.     iiccon = readl(i2c->regs + S3C2410_IICCON);  
  19.     writel(stat, i2c->regs + S3C2410_IICSTAT);  
  20.     dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS\n", stat, addr);  
  21.     writeb(addr, i2c->regs + S3C2410_IICDS);  
  22.     /* delay here to ensure the data byte has gotten onto the bus 
  23.      * before the transaction is started */
  24.     ndelay(i2c->tx_setup);  
  25.     dev_dbg(i2c->dev, "iiccon, %08lx\n", iiccon);  
  26.     writel(iiccon, i2c->regs + S3C2410_IICCON);  
  27.     stat |= S3C2410_IICSTAT_START;  
  28.     writel(stat, i2c->regs + S3C2410_IICSTAT);  //上面這些通過readl和writel來讀取和設定了一些iic介面卡的暫存器,最後邊的這句話,是最關鍵的,它設定iic介面卡為開始狀態,這樣就能觸發中斷,來完成實際資料的傳輸,用中斷傳輸的好處我就不說了,想來大家都會知道啊。
  29. }  
這傢伙終於啟動了中斷,要完成資料的傳輸了。中斷具體怎麼傳輸的,這個大家可以看看,做過微控制器的人肯定還是挺親切的。發現說了這麼多,其實才把剛開始提出的問題1和問題4討論一下,2和3還沒有詳細的討論,那咱們就在下一篇繼續討論吧。