现在的位置: 首页 > 综合 > 正文

linux下CPU注册i2c控制器(adapter)过程

2013年12月09日 ⁄ 综合 ⁄ 共 8189字 ⁄ 字号 评论关闭

无论是三星的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().  

这样, 在用户层的一个i2c访问操作就会体现在octeon处理器层的i2c实现方法上.


/*
注 : 关于: i2c_algorithm                   
                        
static struct i2c_adapter octeon_i2c_ops = {
    .owner = THIS_MODULE,
    .name = "OCTEON adapter",
    .algo = &octeon_i2c_algo,
};                      
    


static const struct i2c_algorithm octeon_i2c_algo = {
    .master_xfer = octeon_i2c_xfer,                                                                                                          
    .functionality = octeon_i2c_functionality,
}; 

octeon 特定的 master_xfer 的实现就是octeon_i2c_xfer()函数, 在此有操作寄存器的方法.

*/

下面介绍关于linux提供的i2c服务, linux 把一切deivce当作文件来处理, 其区别就是device 的属性和driver.

I2C层提供的两个(可看作一个)重要的函数:  i2c_add_adapter 和 i2c_add_numbered_adapter

这两个函数是i2c-core提供的注册i2c_adapter 的两个重要的接口函数, 本质是一样的, 最终都调用了i2c_register_adapter()函数. 

两者的区别从命名上可以看出来:    

i2c_add_numbered_adapter() 增加了一个 numbered(有指定号码) 的 adapter. 可以理解为静态的总线号, 如果总线号被占用或非法等, 那么函数会返回相应的错误值.


而i2c_add_adapter ()函数是由系统自动分配总线号,即使用动态总线号,   如果注册成功, 得到的总线号保存在adapter->nr 成员中. 

CPU通过调用i2c_add_adapter 或 i2c_add_numbered_adapter来实现向内核文件系统注册自己的adapter. 比如上面的 octeon_i2c_probe()就使用了i2c_add_adapter, s3c2410最新版本使用的是i2c_add_numbered_adapter.

I2c_add_adapter定义如下:


inti2c_add_adapter(struct i2c_adapter *adapter)

{   
    int id, res = 0;
    
retry:     
    if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)
        return -ENOMEM;
    
    mutex_lock(&core_lock);
    /* "above" here means "above or equal to", sigh */
    res = idr_get_new_above(&i2c_adapter_idr, adapter,
                __i2c_first_dynamic_bus_num, &id);
    mutex_unlock(&core_lock);
    
    if (res < 0) {
        if (res == -EAGAIN)
            goto retry;
        return res;
    }
           
    adapter->nr = id;
    return i2c_register_adapter(adapter);

}
   EXPORT_SYMBOL(i2c_add_adapter);


static inti2c_register_adapter(struct i2c_adapter *adap)

{                     
...
                      
    mutex_init(&adap->bus_lock);
                      
    /* Set default timeout to 1 second if not already set */
    if (adap->timeout == 0)
        adap->timeout = HZ;
                      
    dev_set_name(&adap->dev, "i2c-%d", adap->nr);
    adap->dev.bus = &i2c_bus_type;
    adap->dev.type = &i2c_adapter_type;
 
/*
使用device_register注册该adapter设备
*/
   res = device_register(&adap->dev);
   
....
}

在i2c_register_adapter中使用了两个重要的结构体, 

i2c_bus_type 和 i2c_adapter_type,  

linux将 adapter作为一个模拟为一个总线设备, 用 i2c_bus_type描述其总线类型, 用i2c_adapter_type描述其设备类型.		

struct device_type i2c_adapter_type = {        
    .groups     = i2c_adapter_attr_groups,     
    .release    = i2c_adapter_dev_release,     
};                                             
EXPORT_SYMBOL_GPL(i2c_adapter_type);   


static const struct attribute_group *i2c_adapter_attr_groups[] = {                                                                           
    &i2c_adapter_attr_group,                   
    NULL                                       
};                                             
                                               
      
static struct attribute_group i2c_adapter_attr_group = {
    .attrs      = i2c_adapter_attrs,           
};                                             
         


staticstruct attribute *i2c_adapter_attrs[] = {

    &dev_attr_name.attr,
    &dev_attr_new_device.attr, 
    &dev_attr_delete_device.attr,
    NULL
};    
                                      





对于dev_attr_name, dev_attr_new_device, dev_attr_delete_device使用宏函数 DEVICE_ATTR, 将具体读写操作方法函数进行绑定, 请参考 DEVICE_ATTR宏的用法.

I2c-core中:


static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
static DEVICE_ATTR(new_device, S_IWUSR, NULL, i2c_sysfs_new_device);                                                                         
static DEVICE_ATTR(delete_device, S_IWUSR, NULL, i2c_sysfs_delete_device);


定义了show_name() 为结构体dev_attr_name 的show方法.
定义了i2c_sysfs_new_device() 为 结构体 dev_attr_new_device的store方法.
定义了i2c_sysfs_delete_device() 为 结构体dev_attr_delete_device的 store 方法.


i2c_sysfs_new_device:


staticssize_t

i2c_sysfs_new_device(struct device *dev, struct device_attribute *attr,
             const char *buf, size_t count)
{   

/*
获取adapter 数据结构
*/
  struct i2c_adapter *adap = to_i2c_adapter(dev);

    struct i2c_board_info info;
    struct i2c_client *client;
....
 client = i2c_new_device(adap, &info);

...

}
 

i2c_sysfs_new_device主要功能是: 获取adapter, 调用 i2c_new_device() 

struct i2c_client *

i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{   
    struct i2c_client   *client;
    int         status;
...
	
client->adapter = adap;
    client->flags = info->flags;
    client->addr = info->addr;
    client->irq = info->irq;
         
..

    client->dev.parent = &client->adapter->dev;
    client->dev.bus = &i2c_bus_type;
    client->dev.type = &i2c_client_type;
    client->dev.of_node = info->of_node;
status = device_register(&client->dev);
..

}


i2c_new_device()  主要功能是: 将adapter, irq, i2c_bus_type ,i2c_adapter_type等信息填充到一个 i2c_client中, 最后将i2c_client中的device注册到系统内核中. 

上面使用到的另一个结构体 i2c_client_type :


staticstruct device_type i2c_client_type;

static struct device_type i2c_client_type = {
    .groups     = i2c_dev_attr_groups,
    .uevent     = i2c_device_uevent,
    .release    = i2c_client_dev_release,
};                 
            

static const struct attribute_group *i2c_dev_attr_groups[] = {
    &i2c_dev_attr_group,
    NULL             
};                   
   
                    
static struct attribute_group i2c_dev_attr_group = {
    .attrs      = i2c_dev_attrs,                                                                                                             
};                  
     
    

staticstruct attribute *i2c_dev_attrs[] = {
&dev_attr_name.attr,
/* modalias helpscoldplug: modprobe $(cat .../modalias) */
&dev_attr_modalias.attr,
NULL
};

两个属性结构体的定义在:

staticDEVICE_ATTR(name, S_IRUGO, show_name, NULL);

staticDEVICE_ATTR(modalias, S_IRUGO, show_modalias, NULL);                                                                                   

这个属性的show_name定义为:

static ssize_t               
show_name(struct device *dev, struct device_attribute *attr, char *buf)
{                            
    return sprintf(buf, "%s\n", dev->type == &i2c_client_type ?                                                                              
               to_i2c_client(dev)->name : to_i2c_adapter(dev)->name);
}                            
   
举一个实际的例子:

在系统中执行:

root@juson:~# cat /sys/bus/i2c/devices/i2c-0/name
OCTEON adapter


显示:  OCTEON adapter.  这个名字就是在  struct i2c_adapter octeon_i2c_ops 结构体中定义的:


static struct i2c_adapter octeon_i2c_ops = {
    .owner = THIS_MODULE,
    .name = "OCTEON adapter",                                                                                                                
    .algo = &octeon_i2c_algo,
};   
   

抱歉!评论已关闭.