歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

Linux下CPU注冊i2c控制器(adapter)過程

無論是三星的s3c2410, 還是cavium 的octeon, AMD的amd8111等等,  任何處理器在linux下添加自己的adapter都是大致的方法, 都是實現自己的driver,  最後調用i2c-core提供的API完成整個注冊.  廣泛地講,  linux將任何類型的設備, 任何類型的總線等都作為文件來處理,  只不過使用了不同的數據結構的driver和device.   

I2c的邏輯簡單實用. 在linux精妙的架構下, 代碼量非常小. 現在大部分的IC都有I2C接口. 至於spi, uart, can, usb, pci, stat等等各種各樣的, 雖然協議不同,  特點不用, 但本質上都是一樣的.

至於I2C具體的協議, 時序等請參考其他資料. 這裡只做軟件上的架構分析.

下面以octeon處理器為例,  重點介紹下octeon_i2c_probe()中部分重要的代碼, 在其他處理器的xxx_i2c_probe() 函數中, 無論是獲取設備資源, 獲取中斷, CPU時鐘, 配置adapter 等等操作在任何處理器中的步驟大致都是相同的, 也都是不可或缺的.

                By 韓大衛@吉林師范大學

內核代碼drivers/i2c/busses/i2c-octeon.c

 

static int __init octeon_i2c_init(void)
{             
    int rv;    /*
i2c 控制器是被集成在CPU上的, 寄存器地址可以被CPU直接尋址到,  linux將這個adapter抽象為一個platfrom device , 其驅動使用數據結構: struct  platfrom_driver.  一般將usb host,  serial 控制器等也做同樣處理.
實現好driver後, 使用  platform_driver_register()函數將其注冊到linux內核的設備樹中. 
*/    rv = platform_driver_register(&octeon_i2c_driver);                                                                                     
    return rv; 

static struct platform_driver octeon_i2c_driver = {
    .probe      = octeon_i2c_probe,

    .remove    = __devexit_p(octeon_i2c_remove),
    .driver    = {
        .owner  = THIS_MODULE,
        .name  = DRV_NAME,
        .of_match_table = octeon_i2c_match,
    },         
};             
 
 
#define DRV_NAME "i2c-octeon" 


static int __devinit octeon_i2c_probe(struct platform_device *pdev)
{         
    int irq, result = 0;
    struct octeon_i2c *i2c;
    struct resource *res_mem;
    const __be32 *data;
    int len;

/*
獲取設備的中斷號
*/
 /* All adaptors have an irq.  */                                                                                                       
    irq = platform_get_irq(pdev, 0);


/*
為其數據結構分配內存
*/
    i2c = devm_kzalloc(&pdev->dev, sizeof(*i2c), GFP_KERNEL);
    if (!i2c) { 
        dev_err(&pdev->dev, "kzalloc failed\n");
        result = -ENOMEM;
        goto out;
    }           
    i2c->dev = &pdev->dev;
                 
/* platform_driver_register() 注冊時會對所有已注冊的所有 platform_device 中的 name 和當前注冊的 platform_driver 的driver.name 進行比較,只有找到相同的名稱的 platfomr_device 才能注冊成功,當注冊成功時會調用 platform_driver 結構元素 probe 函數指針,這裡就是octeon_i2c_probe(), 當進入 probe 函數後,需要獲取設備的資源信息,常用獲取資源的函數主要是:structresource * platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num);

根據參數 type 所指定類型,例如 IORESOURCE_MEM ,來獲取指定的資源,即獲取設備的IO資源地址.

*/
 res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); 

    if (res_mem == NULL) {
        dev_err(i2c->dev, "found no memory resource\n");
        result = -ENXIO;
        goto fail_region;
    }           

    i2c->twsi_phys = res_mem->start;
    i2c->regsize = resource_size(res_mem);
     
    data = of_get_property(pdev->dev.of_node, "clock-rate", &len);
    if (data && len == sizeof(*data)) {
/*
設置I2C時鐘頻率
*/
        i2c->twsi_freq = be32_to_cpup(data);
    } else {     
        dev_err(i2c->dev, "no I2C 'clock-rate' property\n");
        result = -ENXIO;
        goto fail_region;
    }           
       
             
/*
設置I2C系統IO時鐘頻率
*/
    i2c->sys_freq = octeon_get_io_clock_rate();

/*
申請IO域
*/
 if (!devm_request_mem_region(&pdev->dev, i2c->twsi_phys, i2c->regsize,
                    res_mem->name)) {
        dev_err(i2c->dev, "request_mem_region failed\n");
        goto fail_region;
    }

/*
申請成功後將其映射到內核空間.
*/
i2c->twsi_base = ioremap(i2c->twsi_phys, i2c->regsize);

/*
初始化I2C的等待隊列
*/
    init_waitqueue_head(&i2c->queue);

                 
    i2c->irq = irq;
                 
/*
注冊I2C的中斷號
*/
    result = request_irq(i2c->irq, octeon_i2c_isr, 0, DRV_NAME, i2c);
    if (result < 0) {
        dev_err(i2c->dev, "failed to attach interrupt\n");
        goto fail_irq;
    }           
               
/*
初始化octeon I2C 控制器
*/
    result = octeon_i2c_initlowlevel(i2c);

    if (result) {
        dev_err(i2c->dev, "init low level failed\n");
        goto  fail_add;
    }           
               
/*
設置octeon I2C 時鐘
*/ 
    result = octeon_i2c_setclock(i2c);
 

/*
添加octeon I2C 的寄存器read/write實現方法
*/
 i2c->adap = octeon_i2c_ops;
    i2c->adap.timeout = msecs_to_jiffies(50);
    i2c->adap.dev.parent = &pdev->dev;
    i2c->adap.dev.of_node = pdev->dev.of_node;
    i2c_set_adapdata(&i2c->adap, i2c);
    platform_set_drvdata(pdev, i2c);
           
/*
調用 i2c-core提供的注冊adapter接口API.
*/
    result = i2c_add_adapter(&i2c->adap);
  if (result < 0) {
        dev_err(i2c->dev, "failed to add adapter\n");
        goto fail_add;
    }             
/*
注冊adapter成功, 打印出當前版本號
*/
      dev_info(i2c->dev, "version %s\n", DRV_VERSION);

/*
of_i2c_register_devices最終調用的是i2c-core提供的i2c_new_device()函數, 建立一個的i2c adapter.
*/
of_i2c_register_devices(&i2c->adap);                                                                                                   
..
}             


在此,  octeon處理器先將定義好其特定的adapter數據結構,  將針對octeon處理器的 i2c 操作(i2c_algorithm)實現方法填充到此adapter結構體中,  最後, 使用 i2c-core提供的adapter注冊函數 i2c_add_adapter(). 

Copyright © Linux教程網 All Rights Reserved