I2C Subsystem -2- (Core)

<kernel v4.14>

I2C subsystem 준비

i2c_init()

i2c-core-base.c

static int __init i2c_init(void)
{
        int retval;

        retval = of_alias_get_highest_id("i2c");

        down_write(&__i2c_board_lock);
        if (retval >= __i2c_first_dynamic_bus_num)
                __i2c_first_dynamic_bus_num = retval + 1;
        up_write(&__i2c_board_lock);

        retval = bus_register(&i2c_bus_type);
        if (retval)
                return retval;

        is_registered = true;

#ifdef CONFIG_I2C_COMPAT
        i2c_adapter_compat_class = class_compat_register("i2c-adapter");
        if (!i2c_adapter_compat_class) {
                retval = -ENOMEM;
                goto bus_err;
        }
#endif
        retval = i2c_add_driver(&dummy_driver);
        if (retval)
                goto class_err;

        if (IS_ENABLED(CONFIG_OF_DYNAMIC))
                WARN_ON(of_reconfig_notifier_register(&i2c_of_notifier));
        if (IS_ENABLED(CONFIG_ACPI))
                WARN_ON(acpi_reconfig_notifier_register(&i2c_acpi_notifier));

        return 0;

class_err:
#ifdef CONFIG_I2C_COMPAT
        class_compat_unregister(i2c_adapter_compat_class);
bus_err:
#endif
        is_registered = false;
        bus_unregister(&i2c_bus_type);
        return retval;
}
postcore_initcall(i2c_init);

i2c 버스를 사용할 수 있도록 준비한다.

  • 코드 라인 5에서 “i2c”로 등록된 alias 들 중 가장 큰 alias id를 알아온다.
  • 코드 라인 7~10에서 알아온 alias id가 __i2c_first_dynamic_bus_num보다 큰 경우 __i2c_first_dynamic_bus_num 값을 알아온 alias id+1 값으로 갱신한다. 즉 i2c 버스가 추가될 때 사용될 번호를 갱신한다.
  • 코드 라인 12~16에서 i2c 버스 타입을 등록한다.
  • 코드 라인 18~24에서 CONFIG_I2C_COMPAT 커널 옵션을 사용하는 경우 legacy 호환을 위해 i2c-adapter 클래스를 등록한다.
  • 코드 라인 25~27에서 i2c 버스에 일단 dummy_driver를 등록한다.
  • 코드 라인 29~30에서 디바이스 트리를 사용하는 커널인 경우 i2c bus에 등록/해제되는 디바이스 및 드라이버마다 of_i2c_notify() 함수를 호출하도록 notifier 블럭을 등록한다.
  • 코드 라인 31~32에서 ACPI를 사용하는 커널인 경우 i2c bus에 등록/해제되는 디바이스 및 드라이버마다 i2c_acpi_notify() 함수를 호출하도록 notifier 블럭을 등록한다.

 

i2c 코어 서브시스템

i2c 버스에 디바이스와 드라이버가 등록될 때 동작하는 i2c 코어를 먼저 설명하고 i2c 호스트 컨트롤러의 등록은 잠시 후에 다루기로 한다.

i2c 클라이언트 디바이스 및 드라이버 등록

i2c 클라이언트 디바이스 및 i2c 드라이버의 등록방법은 다음과 같다.

i2c 디바이스 등록
  • i2c_new_device() 함수를 사용하여 등록
    • 드라이버에서 지정한 주소 리스트를 사용하는 경우 HW detect하여 등록할 수 있다.
    • 커널의 특정 기능을 로드할 때(주로 i2c host controller) 이미 알고 있는 i2c 디바이스를 등록한다.
  • 디바이스 트리를 파싱하여 등록을 한다.
    • of_i2c_register_device()
  • ACPI 펌웨어를 통해 등록된다.
    • i2c_acpi_register_device()
  • user space 디바이스 인터페이스를 통해 i2c 디바이스를 등록한다.
    • echo <device-name> <i2c-addr> /sys/bus/i2c/i2c-N/new_device
      • 예) echo foo_lcd 0x3f > /sys/bus/i2c-0/new_device

 

i2c 드라이버 등록

다음 API를 통해 등록한다.

  • i2c_add_driver()
  • i2c_register_driver()
  • module_i2c_driver()

 

i2c 버스 타입

drivers/i2c/i2c-core-base.c

struct bus_type i2c_bus_type = {
        .name           = "i2c",
        .match          = i2c_device_match,
        .probe          = i2c_device_probe,
        .remove         = i2c_device_remove,
        .shutdown       = i2c_device_shutdown,
};
EXPORT_SYMBOL_GPL(i2c_bus_type)

i2c 버스에 등록되는 디바이스 및 드라이버가 있을 때마다 i2c_device_match() 함수가 호출된다. 이의 결과가 성공(1)인 경우 i2c_device_probe() 함수가 호출된다.

 

i2c 디바이스 매치

i2c 버스에 디바이스나 드라이버가 등록될 때마다 버스에 등록되어 있는 모든 디바이스와 드라이버들이 서로 매치될 수 있는지 이 함수를 호출한다.

예를 들어 i2c 버스에 다음 디바이스 및 드라이버들이 등록되어 있다고 가정하자.

+ i2c 버스
      |
      +---- 디바이스 리스트
      |            +---------- A-device
      |            +---------- B-device
      +---- 드라이버 리스트
                   +---------- C-driver
                   +---------- D-driver
                   +---------- E-driver

 

위와 같은 경우 매치 여부를 테스트하기 위해 A디바이스를 C,D,E 드라이버와 비교해야 하고, B 디바이스도 역시 C, D, E 드라이버와 비교해야 한다. 즉 등록된 디바이스들과 드라이버들의 조합으로 디바이스 및 드라이버 인자를 가지고 i2c_device_match() 함수가 호출된다.

 

매치 테이블

다음과 같이 3가지 방식을 사용하여 아래 순서대로 매치 여부를 결정한다.

  • of_match_table
    • 드라이버에 있는 이 테이블에 등록된 명칭을 디바이스 트리의 노드명 또는 compatible 속성명과 비교한다.
  • ACPI
    • 드라이버명을 ACPI 펌웨어에 내장된 ACPI 테이블의 디바이스명과 비교한다.
  • id_table
    • 드라이버에 있는 이 테이블에 등록된 명칭을 디바이스명과 비교한다.

 

i2c_device_match()

drivers/i2c/i2c-core-base.c

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
        struct i2c_client       *client = i2c_verify_client(dev);
        struct i2c_driver       *driver;

        /* Attempt an OF style match */
        if (i2c_of_match_device(drv->of_match_table, client))
                return 1;

        /* Then ACPI style match */
        if (acpi_driver_match_device(dev, drv))
                return 1;

        driver = to_i2c_driver(drv);

        /* Finally an I2C match */
        if (i2c_match_id(driver->id_table, client))
                return 1;

        return 0;
}

디바이스와 드라이버가 매치되는 경우 성공(1)을 반환한다.

  • 코드 라인 7~8에서 드라이버의 of_match_table에 등록된 드라이버 이름과 디바이스 트리의 이름 또는 compatible 명이 같은 경우 성공(1)을 반환한다.
    • 예) drv->of_match_table에 등록된 “company,foo”와 디바이스 트리의 compatible=”company,foo”가 동일
  • 코드 라인 11~12에서 ACPI 테이블에서 읽어온 디바이스 명과 드라이버명이 동일한 경우 성공(1)을 반환한다.
  • 코드 라인 17~18에서 마지막으로 드라이버에 등록된 id_table의 id 이름과 디바이스 이름이 동일한 경우 성공(1)을 반환한다.

 

i2c_device_probe()

i2c 버스에 등록된 디바이스와 드라이버들 중 하나가 매치되는 경우 이 함수가 호출된다.

  • 물론 매치 여부와 관련 없이 매뉴얼 probe 하는 경우도 있다.

drivers/i2c/i2c-core-base.c

static int i2c_device_probe(struct device *dev)                                 
{                                                                               
        struct i2c_client       *client = i2c_verify_client(dev);               
        struct i2c_driver       *driver;                                        
        int status;                                                             
                                                                                
        if (!client)                                                            
                return 0;                                                       
                                                                                
        driver = to_i2c_driver(dev->driver);                                    
                                                                                
        if (!client->irq && !driver->disable_i2c_core_irq_mapping) {            
                int irq = -ENOENT;                                              
                                                                                
                if (client->flags & I2C_CLIENT_HOST_NOTIFY) {                   
                        dev_dbg(dev, "Using Host Notify IRQ\n");                
                        irq = i2c_smbus_host_notify_to_irq(client);             
                } else if (dev->of_node) {                                      
                        irq = of_irq_get_byname(dev->of_node, "irq");           
                        if (irq == -EINVAL || irq == -ENODATA)                  
                                irq = of_irq_get(dev->of_node, 0);              
                } else if (ACPI_COMPANION(dev)) {                               
                        irq = acpi_dev_gpio_irq_get(ACPI_COMPANION(dev), 0);    
                }                                                               
                if (irq == -EPROBE_DEFER)                                       
                        return irq;                                             
                                                                                
                if (irq < 0)                                                    
                        irq = 0;                                                
                                                                                
                client->irq = irq;                                              
        }                                                                       
                                                                                
        /*                                                                      
         * An I2C ID table is not mandatory, if and only if, a suitable OF      
         * or ACPI ID table is supplied for the probing device.                 
         */                                                                     
        if (!driver->id_table &&                                                
            !i2c_acpi_match_device(dev->driver->acpi_match_table, client) &&    
            !i2c_of_match_device(dev->driver->of_match_table, client))          
                return -ENODEV;                                                 
                                                                                
        if (client->flags & I2C_CLIENT_WAKE) {                                  
                int wakeirq = -ENOENT;                                          
                                                                                
                if (dev->of_node) {                                             
                        wakeirq = of_irq_get_byname(dev->of_node, "wakeup");    
                        if (wakeirq == -EPROBE_DEFER)                           
                                return wakeirq;                                 
                }                                                               
                                                                                
                device_init_wakeup(&client->dev, true);                         
                                                                                
                if (wakeirq > 0 && wakeirq != client->irq)                      
                        status = dev_pm_set_dedicated_wake_irq(dev, wakeirq);   
                else if (client->irq > 0)                                       
                        status = dev_pm_set_wake_irq(dev, client->irq);         
                else                                                            
                        status = 0;                                             
                                                                                
                if (status)                                                     
                        dev_warn(&client->dev, "failed to set up wakeup irq\n");
        }

디바이스에 바인딩된 드라이버의 (*probe_new) 후크 함수를 호출한다. 후크 함수가 없는 경우 (*probe) 후크 함수를 호출한다. 이 과정에서 사용할 irq를 클라이언트 디바이스에 지정하고 클럭 설정을 한다. 그리고  디바이스에 절전 기능이 있는 경우 pm 도메인을 어태치한다.

  • 코드 라인 12~32에서 i2c 클라이언트 디바이스에 irq가 지정되지 않았고, i2c 코어 시스템에서 irq 매핑을 허용한 경우 다음과 같은 방법으로 irq를 알아온다. 알아오지 못한 경우 0으로 한다.
    • i2c 어댑터에 등록된 irq 도메인에서 client 디바이스의 i2c 주소에 배정된 irq를 알아와 매핑한다.
    • 디바이스 트리 노드의 “interrupt-names” 속성값에서 “irq” 문자열이 포함된 항목의 인덱스 번호에 해당하는 irq를 알아온다.
    • acpi 디바이스의 경우 지정된 irq를 알아온다.
  • 코드 라인 38~41에서 드라이버에 id_table이 지정되지 않은 경우 acpi 또는 디바이스 트리를 통해 배치된 디바이스가 없는 경우 -ENODEV 에러를 반환한다.
  • 코드 라인 43~63에서 절전 기능이 있어 I2C_CLIENT_WAKE 플래그가 사용된 디바이스의 경우 wakeup 처리를 수행한다. 디바이스 트리에서 “wakeup” 속성으로 wakeirq가 지정된 경우 디바이스에 wakeup irq를 지정한다.

 

        dev_dbg(dev, "probe\n");                                                
                                                                                
        status = of_clk_set_defaults(dev->of_node, false);                      
        if (status < 0)                                                         
                goto err_clear_wakeup_irq;                                      
                                                                                
        status = dev_pm_domain_attach(&client->dev, true);                      
        if (status == -EPROBE_DEFER)                                            
                goto err_clear_wakeup_irq;                                      
                                                                                
        /*                                                                      
         * When there are no more users of probe(),                             
         * rename probe_new to probe.                                           
         */                                                                     
        if (driver->probe_new)                                                  
                status = driver->probe_new(client);                             
        else if (driver->probe)                                                 
                status = driver->probe(client,                                  
                                       i2c_match_id(driver->id_table, client)); 
        else                                                                    
                status = -EINVAL;                                               
                                                                                
        if (status)                                                             
                goto err_detach_pm_domain;                                      
                                                                                
        return 0;                                                               
                                                                                
err_detach_pm_domain:                                                           
        dev_pm_domain_detach(&client->dev, true);                               
err_clear_wakeup_irq:                                                           
        dev_pm_clear_wake_irq(&client->dev);                                    
        device_init_wakeup(&client->dev, false);                                
        return status;                                                          
}
  • 코드 라인 3~5에서 i2c 디바이스의 디바이스 트리 노드에서 클럭 설정이 있는 경우 이 값으로 클럭을 설정한다.
    • 클럭 값으로 “assigned-clock-rates” 속성값을 읽어온다. 그런 후 “assigned-clocks” phandle 노드가 가리키는 클럭에 읽어온 클럭 값으로 설정한다.
  • 코드 라인 7~9에서 절전을 위해 acpi pm 도메인을 디바이스에 어태치하거나 디바이스 트리의  “power-domains” phandle 노드가 가리키는 파워 도메인에서 지정된 pm 도메인을 디바이스에 어태치한다.
  • 코드 라인 15~24에서 드라이버에 (*probe_new) 후크 함수를 호출한다. 후크 함수가 없는 경우 (*probe) 후크 함수를 호출한다.

 

i2c 호스트 컨트롤러 드라이버 등록

 

버스별 드라이버 등록

i2c 호스트 컨트롤러가 어떠한 버스에 연결되는지에 따라 모듈의 드라이버 등록부분의 매크 함수가 바뀐다.

  • platform 버스
    • module_platform_driver() 또는 platform_driver_register()
  • pci 버스:
    • module_pci_driver() 또는 pci_register_driver()
  • usb 버스:
    • module_usb_driver() 또는 usb_register()
  • isa 버스:
    • module_isa_driver() 또는 isa_register_driver()
  • amba 버스
    • module_amba_driver() 또는 amba_driver_register()
  • parallel 포트 버스
    • parport_register_driver()

 

참고로 i2c 호스트 컨트롤러는 대부분이 플랫폼 버스에 연결되어 사용된다. 그리고 나머지는 pci 버스에 연결되어 사용된다. usb, isa 및 amba 버스에 연결되어 사용되는 i2c 컨트롤러 드라이버는 몇 개 없다.

usb 버스용 i2c 호스트 컨트롤러 드라이버
  • drivers/i2c/busses/i2c-robotfuzz-osif.c
  • drivers/i2c/busses/i2c-tiny-usb.c
  • drivers/i2c/busses/i2c-diolan-u2c.c

 

isa 버스용 i2c 호스트 컨트롤러 드라이버
  • drivers/i2c/busses/i2c-elektor.c
  • drivers/i2c/busses/i2c-pca-isa.c

 

amba 버스용 i2c 호스트 컨트롤러 드라이버
  • drivers/i2c/busses/i2c-nomadik.c

 

parallel port 버스용 i2c 호스트 컨트롤러 드라이버
  • drivers/i2c/busses/i2c-paraport.c

 

이제 버스별로 드라이버를 등록하는 과정을 살펴보자.

플랫폼 버스

i2c 호스트 컨트롤러를 플랫폼 버스에 붙이는 방법이다.

  • id_table을 사용하거나 of_match_table을 사용하여 디바이스 매치여부를 판단하게 할 수 있다.
  • 아래 소스는 id 테이블이 아닌 디바이스 트리 방식으로 매치 테이블을 구성한 예이다.
static const struct of_device_id foo_of_match[] = { 
        {.compatible = "company,foo-i2c", }, 
        {}, 
}; 
MODULE_DEVICE_TABLE(of, foo_of_match); 

static struct platform_driver foo_i2c_driver = {                          
        .driver = {                                                             
                .name = "foo-i2c",                                        
                .of_match_table = foo_i2c_of_match,                       
        },                                                                      
        .probe = foo_i2c_probe,                                           
        .remove = foo_i2c_remove,                                         
};                                                                              
module_platform_driver(foo_i2c_driver);

 

플랫폼 디바이스 정보들은 디바이스 트리를 사용하는 시스템에서는 대부분 디바이스 트리에서 플랫폼 리소스 정보와 드라이버 명이 지정된다. PC 서버와 같이 ACPI를 사용하는 시스템에서는 ACPI 펌웨어로부터 디바이스에 대한 정보를 알아올 수 있다. 다음은 디바이스 트리를 사용하여 플랫폼 디바이스가 등록된 샘플이다.

i2c0: i2c@66080000 {
        compatible = "company,foo-i2c";
        reg = <0x66080000 0x100>;
        #address-cells = <1>;
        #size-cells = <0>;
        interrupts = <GIC_SPI 394 IRQ_TYPE_NONE>;
        clock-frequency = <100000>;
};

 

pci 버스

i2c 호스트 컨트롤러를 pci 버스에 붙이는 방법이다.  pci 버스에서는 pci 벤더 id와 pci 디바이스 id를 사용하여 매치 테이블을 구성한다.

#define PCI_VENDOR_ID_FOO 0x1234
#define PCI_DEVICE_ID_FOO 0x5678

static const struct pci_device_id foo_ids[] = { 
        { PCI_DEVICE(PCI_VENDOR_ID_FOO, PCI_DEVICE_ID_FOO) }, 
        { 0, } 
}; 
MODULE_DEVICE_TABLE (pci, hydra_ids);

static struct pci_driver foo_driver = {                                       
        .name           = "foo",                                        
        .id_table       = foo_ids,                                            
        .probe          = foo_probe,                                          
        .remove         = foo_remove,                                         
};                                                                              
module_pci_driver(hydra_driver);

 

usb  버스

i2c 호스트 컨트롤러를 usb 버스에 붙이는 방법이다. usb 버스에서는 벤더 id와 제품 id를 사용하여 매치 테이블을 구성한다.

static const struct usb_device_id foo_table[] = { 
        { USB_DEVICE(0x1234, 0x5678) },
        { }
};

static struct usb_driver foo_driver = {                                  
        .name = "foo-i2c",                                                    
        .probe = foo_probe,                                              
        .disconnect = foo_disconnect,                                    
        .id_table = foo_table,                                           
};                                                                                
module_usb_driver(foo_driver);

 

probe 후크

probe 함수가 호출되면 먼저 디바이스 트리를 통해 플랫폼 디바이스에 등록되어 있던 플랫폼 리소스 정보를 알아와서 아래 구조체에 등록하고 HW 설정등을 수행한다.

struct foo_i2c_dev {                                                      
        struct device *device;                                                  

        struct clk *clk;             /* ic2 클럭이 필요한 경우에만 */
        u32 bus_clk_rate;          

        void __iomem *base;          /* 매핑하여 사용할 i2c 레지스터 */                                            

        int irq;                     /* 인터럽트가 사용되는 경우에만 */                                             
                                                                                
        struct i2c_adapter adapter;  /* 등록할 i2c adapter */                                           
        unsigned int bus_speed;      /* 디바이스 트리에서 읽어온 버스 속도 */

        ...                                                 
};

 

플랫폼 드라이버에서의 probe 함수는 다음과 같은 형식으로 작성된다.

static int foo_probe(struct platform_device *pdev)                    
{                                                                               
        int irq, ret = 0;                                                       
        struct foo_i2c_dev *foo_i2c;                                    
        struct i2c_adapter *adap;                                               
        struct resource *res;                                                   
                                                                                
        foo_i2c = devm_kzalloc(&pdev->dev, sizeof(*foo_i2c),                
                                 GFP_KERNEL);                                   
        if (!foo_i2c)                                                         
                return -ENOMEM;                                                 
                                                                                
        platform_set_drvdata(pdev, foo_i2c);                                  
        foo_i2c->device = &pdev->dev;
                                                                                
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);                   
        foo_i2c->base = devm_ioremap_resource(foo_i2c->device, res);        
        if (IS_ERR(foo_i2c->base))                                            
                return PTR_ERR(foo_i2c->base);                                
                                                                                
        ret = foo_i2c_init(foo_i2c);   /* HW 초기화 설정이 필요한 경우 */
        if (ret)                                                                
                return ret;                                                     
                                                                                
        ret = foo_i2c_cfg_speed(foo_i2c);   /* 버스 속도 HW 설정 */                            
        if (ret)                                                                
                return ret;                                                     
                                                                                
        irq = platform_get_irq(pdev, 0);                                        
        if (irq <= 0) {                                                         
                dev_err(foo_i2c->device, "no irq resource\n");                
                return irq;                                                     
        }                                                                       
        foo_i2c->irq = irq;                                                   
                                                                                
        ret = devm_request_irq(foo_i2c->device, irq, foo_i2c_isr, 0,    
                               pdev->name, foo_i2c);                          
        if (ret < 0) {                                                          
                dev_err(foo_i2c->device, "unable to request irq %i\n", irq);  
                return ret;                                                     
        }                                                                       
                                                                                
        foo_i2c_enable(foo_i2c);      /* i2c enable HW 설정 */
                                                                                
        adap = &foo_i2c->adapter;                                             
        i2c_set_adapdata(adap, foo_i2c);                                      
        strlcpy(adap->name, "foo i2c adapter", sizeof(adap->name));  
        adap->algo = &foo_algo;                                           
        adap->quirks = &foo_quirks;                                   
        adap->dev.parent = &pdev->dev;                                          
        adap->dev.of_node = pdev->dev.of_node;                                  
                                                                                
        return i2c_add_adapter(adap);                                           
} 
  • 코드 라인 8~11에서 i2c 호스트 컨트롤러 드라이버 데이터를 담을 foo_i2c_dev 구조체를 할당받는다.
  • 코드 라인 13~14에서 플랫폼 디바이스에 위에서 할당한 i2c 호스트 컨트롤러 드라이버의 데이터를 지정하고, 반대로 드라이버가 플랫폼 디바이스도 지정하여 서로 바인드한다.
  • 코드 라인 16~19에서 플랫폼 디바이스에 저장된 리소스 정보 중 i2c 레지스터 주소가 담긴 정보를 가져와서 io 매핑한다. 매핑하여 사용할 수 있는 가상주소가 base 멤버에 저장되므로 이제 이 멤버를 통해 i2c 레지스터에 접근할 수 있게되었다.
  • 코드 라인 21~23에서 i2c HW 초기화 설정이 필요한 경우 관련 함수를 하나 만들어 호출한다. 보통 여기에서 i2c 호스트 컨트롤러를 리셋하고 송/수신 큐들에 대한 flush와 pending 인터럽트 등을 클리어하는 등 HW의 초기화가 필요한 모든 것들을 처리한다.
  • 코드 라인 25~27에서 i2c 버스 스피드 HW 설정을 한다.
    • 플랫폼 드라이버의 경우 i2c 버스 스피드를 설정할 때 디바이스 트리 또는 ACPI 테이블로부터 정보를 알아온다.
  • 코드 라인 29~41에서 다시 플랫폼 디바이스에 저장된 리소스 정보 중 이번에는 irq 번호를 가져와서 irq 요청을 해야 한다. irq 처리를 위한 핸들러도 같이 등록해준다. 또한 irq 번호에 해당하는 irq chip controller의 구현과 irq domain 및 irq descriptor가 모두 사전에 할당되어 구성되어 있어야한다.
    • 대부분의 경우 irq chip 컨트롤러에서 irq domain 및 irq descriptor등이 사전에 구성되어 있다.
  • 코드 라인 43에서 i2c enable을 하기 위한 HW 조작이 필요한 경우 관련 작업을 수행하는 함수를 작성하여 호출한다.
  • 코드 라인 45~53에서 adapter를 구성하여 등록한다. 이 adapter에는 다음과 같은 항목을 다룬다.
    • *algo
      • i2c 전송 알고리즘 오퍼레이션을 지정한다.
    • *algo_data
      • i2c 전송 알고리즘의 후크 함수들의 인자로 전달된 데이터이다. 일반적으로 i2c 디바이스 정보를 전달한다. (예: foo_i2c_dev)
      • *alog가 지정되지 않는 경우 scl과 sda 라인을 직접 비트 조작할 수 있는 오퍼레이션을 지정할 수 있다.
    • *quirks
      • 옵션으로 메시지 전송 제한을 할 수 있는 quirks 오퍼레이션을 지정한다.

 

i2c 전송 구현(algorithm)

i2c adapter에서 i2c 전송에 대한 구현은 i2c chip마다 모두 다르다. 가장 많이 사용하는 방법으로 i2c_algorithm 구조체에 i2c 및 smbus 프로토콜 전송 함수를 구현하여 등록하고 이를 adapter->algo에서 지정하면 된다.

 

i2c_algorithm 구조체

include/linux/i2c.h

/**                                                                             
 * struct i2c_algorithm - represent I2C transfer method                         
 * @master_xfer: Issue a set of i2c transactions to the given I2C adapter       
 *   defined by the msgs array, with num messages available to transfer via     
 *   the adapter specified by adap.                                             
 * @smbus_xfer: Issue smbus transactions to the given I2C adapter. If this      
 *   is not present, then the bus layer will try and convert the SMBus calls    
 *   into I2C transfers instead.                                                
 * @functionality: Return the flags that this algorithm/adapter pair supports   
 *   from the I2C_FUNC_* flags.                                                 
 * @reg_slave: Register given client to I2C slave mode of this adapter          
 * @unreg_slave: Unregister given client from I2C slave mode of this adapter    
 *                                                                              
 * The following structs are for those who like to implement new bus drivers:   
 * i2c_algorithm is the interface to a class of hardware solutions which can    
 * be addressed using the same bus algorithms - i.e. bit-banging or the PCF8584 
 * to name two of the most common.                                              
 *                                                                              
 * The return codes from the @master_xfer field should indicate the type of     
 * error code that occurred during the transfer, as documented in the kernel    
 * Documentation file Documentation/i2c/fault-codes.                            
 */
struct i2c_algorithm {                                                          
        /* If an adapter algorithm can't do I2C-level access, set master_xfer   
           to NULL. If an adapter algorithm can do SMBus access, set            
           smbus_xfer. If set to NULL, the SMBus protocol is simulated          
           using common I2C messages */                                         
        /* master_xfer should return the number of messages successfully        
           processed, or a negative value on error */                           
        int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,      
                           int num);                                            
        int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,                  
                           unsigned short flags, char read_write,               
                           u8 command, int size, union i2c_smbus_data *data);   
                                                                                
        /* To determine what the adapter supports */                            
        u32 (*functionality) (struct i2c_adapter *);                            
                                                                                
#if IS_ENABLED(CONFIG_I2C_SLAVE)                                                
        int (*reg_slave)(struct i2c_client *client);                            
        int (*unreg_slave)(struct i2c_client *client);                          
#endif                                                                          
};
  • (*master_xfer)
    • 메시지 어레이에 대해 i2c 프로토콜로 전송을 구현해야 하는 후크 함수
    • i2c 프로토콜로 전송을 하지 않는 경우 null을 대입
  • (*smbus_xfer)
    • 메시지 어레이에 대해 smbus 프로토콜로 전송을 구현해야 하는 후크 함수
    • null인 경우 i2c 전송 프로토콜을 사용하여 smbus 전송 요청을 시뮬레이션한다.
    • (*master_xfer)와 (*smbus_xfer) 두 후크가 모두 구현되었고, 특별히 i2c 프로토콜 방식을 사용하라고 명시하지 않으면 (*smbus_xfer)를 사용한다.
  • (*functionality)
    • 이 adapter가 가지고 있는 기능을 비트 플래그로 표현
  • (*reg-slave)
    • clock streaching을 위해 slave 기능이 필요한 경우 사용되는 slave 등록 함수
    • eeprom에서 사용
  • (*unreg_slave)
    • slave 등록을 지원하는 경우 사용되는 slave 등록 해제 함수

 

내장 알고리즘 3가지

또 다른 방법으로는 다음과 같이 커널에 미리 준비된 3 가지 방법 알고리즘을 사용하는 방법도 있다.

  • bit 알고리즘 방식
    • i2c_algorithm 구조체는 bit 방식으로 임베드되어 있고 i2c_algo_bit_data 구조체만 사용자가 구성한 후 adapter->algo_data로 지정하여 사용한다.
    • i2c bus를 scl 및 sda 라인에 대해 비트 단위의 low-level로 직접 제어하기 위해 사용된다. 이러한 특징을 이용하여 gpio의 2핀을 scl 및 sda 라인으로 동작시켜 i2c 호스트 컨트롤러와 같은 동작을 하게 할 수 있다.
    • i2c adapter를 등록할 때 보통 i2c_add_adapter() 함수를 사용하지만, 이 방식에서는 i2c_bit_add_bus() 또는 i2c_bit_add_numbered_bus() 함수를 통해 내부에 임베드된 i2c_add_adapter() 함수를 호출하여 사용한다.
    • 이 방식을 사용하는 드라이버들
      • drivers/i2c/busses/i2c-gpio.c
        • 2개의 gpio 핀을 사용하여 i2c 호스트 컨트롤러로 동작시키는 방식으로 i2c_bit 알고리즘을 사용하는 대표적인 드라이버이다.
      • drivers/i2c/busses/i2c-versatile.c
        • 이 드라이버도 scl 및 sda 라인을 직접 제어할 수 있는 대표적인 드라이버이다.
      • drivers/i2c/busses/i2c-parport-light.c
      • drivers/i2c/busses/i2c-simtec.c
      • drivers/i2c/busses/i2c-via.c
      • drivers/i2c/busses/i2c-acorn.c
      • drivers/i2c/busses/i2c-parport.c
      • drivers/i2c/busses/i2c-hydra.c
  • pca 알고리즘 방식
    • i2c_algorithm 구조체는 pca 방식으로 임베드되어 있고 i2c_algo_pca_data 구조체를 사용자가 구성한 후 adapter->algo_data로 지정하여 사용한다.
    • isa 버스 및 platform 버스에 연결되어 사용하는 pca9564/9665 칩 전용 방식이다.
    • i2c adapter를 등록 시 i2c_pca_add_bus() 또는 i2c_pca_add_numbered_bus() 함수를 사용한다.
    • 이 방식을 사용하는 드라이버들
      • drivers/i2c/busses/i2c-pca-isa.c
      • drivers/i2c/busses/i2c-pca-platform.c
  • pcf 알고리즘 방식
    • i2c_algorithm 구조체는 pcf 방식으로 임베드되어 있고 i2c_algo_pcf_data 구조체를 사용자가 구성한 후 adapter->algo_data로 지정하여 사용한다.
    • isa 버스에 연결되어 사용하는 pcf8584 칩 전용 방식이다.
    • i2c adapter를 등록 시 i2c_pcf_add_bus() 또는 i2c_pcf_add_numbered_bus() 함수를 사용한다.
    • 이 방식을 사용하는 드라이버
      • drivers/i2c/busses/i2c-elektor.c

 

bit 알고리즘 구현

아래에 bit 알고리즘 방식으로 사용하는 구조체만 설명하기로 한다. gpio를 사용하여 i2c 호스트 컨트롤러를 구현해보고자 하는 경우 반드시 이해를 해야 한다.

i2c_algo_bit_data 구조체

include/linux/i2c-algo-bit.h

/* --- Defines for bit-adapters --------------------------------------- */
/*
 * This struct contains the hw-dependent functions of bit-style adapters to
 * manipulate the line states, and to init any hw-specific features. This is
 * only used if you have more than one hw-type of adapter running. 
 */
struct i2c_algo_bit_data {
        void *data;             /* private data for lowlevel routines */
        void (*setsda) (void *data, int state);
        void (*setscl) (void *data, int state);
        int  (*getsda) (void *data);
        int  (*getscl) (void *data); 
        int  (*pre_xfer)  (struct i2c_adapter *);
        void (*post_xfer) (struct i2c_adapter *);

        /* local settings */
        int udelay;             /* half clock cycle time in us,
                                   minimum 2 us for fast-mode I2C,
                                   minimum 5 us for standard-mode I2C and SMBus,
                                   maximum 50 us for SMBus */
        int timeout;            /* in jiffies */
};
  • *data
    • private data
  • (*setsda)
    • 시리얼 데이터(sda) 라인을 설정한다.
  • (*setscl)
    • 시리얼 클럭(scl) 라인을 설정한다.
  • (*getsda)
    • 시리얼 데이터(sda) 라인 상태를 알아온다.
  • (*getscl)
    • 시리얼 클럭(scl) 라인 상태를 알아온다.
  • (*pre_xfer)
  • (*post_xfer)
  • udelay
    • 클럭과 클럭 사이에 delay(us)를 할 때 필요한 시간으로 사이클의 절반 시간을 지정한다.
    • 400Khz 속도로 동작하는 fast-mode I2C의 경우 클럭 주기는 2.5us이다. 그 절반인 1.25가 필요하지만 올림 처리하여 2us가 필요하다.
    • 100Khz 속도로 동작하는 standard-mode I2C의 경우 클럭 주기는 10us이다. 따라서 최소한 그 절반인 5us가 필요하다.
    • 10Khz 저속 처리가 가능한 SMBus 방식의 I2C의 경우 클럭 주기는 100us이다. 최대 값으로 그 절반인 50us까지 허용된다.
  • timeout
    • 타임아웃(jiffies)을 지정한다.

 

2 개의 gpio 핀을 사용한 i2c 호스트 컨트롤러 구현 샘플

위의 구조체를 구현하여 동작시키는 gpio 드라이버를 작성하는 방법을 알아본다. (전체 소스를 만드는 것이 아니라 주요 부분만 설명하였다.)

struct i2c_gpio_platform_data {
        unsigned int    sda_pin;                 <- i2c sda 용도의 gpio 핀 번호
        unsigned int    scl_pin;                 <- i2c scl 용도의 gpio 핀 번호
        int             udelay;
        int             timeout;
        unsigned int    sda_is_open_drain:1;
        unsigned int    scl_is_open_drain:1;    
        unsigned int    scl_is_output_only:1;
};

위의 구조체에 들어갈 값들은 i2c 디바이스가 디바이스 트리를 통해 플랫폼 디바이스로 등록된 경우 아래와 같이 플랫폼 데이터로 전달받을 수 있다.

  • pdata = dev_get_platdata(&pdev->dev);

 

gpio를 사용하여 i2c 디바이스 트리를 만드는 경우의 디바이스 트리 노드 샘플

아래와 같이 gpio의 23번을 i2c sda 라인으로 사용하고, gpio 24번을 i2c scl 라인으로 사용하게 한다. 둘 다 gpio핀 상태를 open-drain으로 설정한다.

  • 서브 노드의 rv3029c2는 i2c 주소 0x56을 사용하는 i2c 클라이언트 디바이스이다. 여기에서는 설명하지 않는다.
i2c@0 {
        compatible = "i2c-gpio";
        gpios = <&pioA 23 0 /* sda */
                 &pioA 24 0 /* scl */>
        i2c-gpio,sda-open-drain;
        i2c-gpio,scl-open-drain;
        i2c-gpio,delay-us = <2>;        /* ~100 kHz */
        #address-cells = <1>;
        #size-cells = <0>;

        rv3029c2@56 {
                compatible = "rv3029c2";
                reg = <0x56>;
        };
};

 

만일 그렇지 않은 경우 직접 다음과 같은 방법으로 디바이스 트리 노드를 파싱하여 알아올 수 있다.

  • of_i2c_gpio_get_pins() 함수를 사용하여 다음 값들에 해당하는 gpio 핀 번호를 알아온다.
    • sda_pin
    • scl_pin
  • of_i2c_gpio_get_props() 함수를 사용하여 디바이스 트리 노드를 통해 다음 속성값들을 알아온다.
    • “i2c-gpio,delay-us”
    • “i2c-gpio,timeout-ms”
    • “i2c-gpio,sda-open-drain”
    • “i2c-gpio,scl-open-drain”
    • “i2c-gpio,scl-output-only”

 

그 후 bit 알고리즘 처리를 위한 후크 함수와 i2c_algo_bit_data 구조체를 채워 구현한다.

static void i2c_gpio_setsda_val(void *data, int state)
{
        struct i2c_gpio_platform_data *pdata = data;

        gpio_set_value(pdata->sda_pin, state);
}

static void i2c_gpio_setscl_val(void *data, int state)
{
        struct i2c_gpio_platform_data *pdata = data;

        gpio_set_value(pdata->scl_pin, state);
}

static int i2c_gpio_getsda(void *data)
{
        struct i2c_gpio_platform_data *pdata = data;

        return gpio_get_value(pdata->sda_pin);
}

static int i2c_gpio_getscl(void *data)
{
        struct i2c_gpio_platform_data *pdata = data;

        return gpio_get_value(pdata->scl_pin);
}

static struct i2c_algo_bit_data {

         .data = &pdata
         .setsda = i2c_gpio_setsda_val,
         .setscl = i2c_gpio_setscl_val,
         .getsda = i2c_gpio_getsda,
         .getscl = i2c_gpio_getscl,
         .udelay = 5,                 /* 디바이스 트리에서 읽은 값(us) */
         .timeout = 100,              /* 디바이스 트리에서 읽은 값(ms) */
} foo_bit;

 

위의 정보들을 모아 i2c_adapter 구조체를 만든 후 다음 api를 통해 adapter를 등록하는 것으로 호스트 컨트롤러 드라이버가 완성되다.

  • i2c_bit_add_numbered_bus(adap);

 

I2C Adapter

i2c 호스트 컨트롤러 드라이버가 상위 버스(플랫폼 버스나 pci 버스 등)에 등록된 후 그 아래에 위치한 i2c 버스를 control하는 칩과 연동하여 사용하기 위해 i2c adapter 디바이스를 구현하여 사용한다.

 

I2C Adapter 포지션

다음 그림은 pci 버스 아래에 i2c bus가 연결되어 있는 모습이다. i2c adapter는 상위 버스와 i2c 버스 중간에 위치한다.

 

include/linux/i2c.h

i2c_adapter 구조체

/*                                                                              
 * i2c_adapter is the structure used to identify a physical i2c bus along       
 * with the access algorithms necessary to access it.                           
 */                                                                             
struct i2c_adapter {                                                            
        struct module *owner;                                                   
        unsigned int class;               /* classes to allow probing for */    
        const struct i2c_algorithm *algo; /* the algorithm to access the bus */ 
        void *algo_data;                                                        
                                                                                
        /* data fields that are valid for all devices   */                      
        const struct i2c_lock_operations *lock_ops;                             
        struct rt_mutex bus_lock;                                               
        struct rt_mutex mux_lock;                                               
                                                                                
        int timeout;                    /* in jiffies */                        
        int retries;                                                            
        struct device dev;              /* the adapter device */                
                                                                                
        int nr;                                                                 
        char name[48];                                                          
        struct completion dev_released;                                         
                                                                                
        struct mutex userspace_clients_lock;                                    
        struct list_head userspace_clients;                                     
                                                                                
        struct i2c_bus_recovery_info *bus_recovery_info;                        
        const struct i2c_adapter_quirks *quirks;                                
                                                                                
        struct irq_domain *host_notify_domain;                                  
};
  • *owner
    • 모듈을 가리킨다.
  • class
    • 플래그 같이 사용한다.
      • I2C_CLASS_HWMON(0x01)
        • hwmon 클래스를 사용하는 디바이스들(lm 센서들)
      • I2C_CLASS_DDC(0x08)
        • 그래픽 아탑터위의 DDC 버스
      • I2C_CLASS_SPD(0x80)
        • 메모리 모듈
      • I2C_CLASS_DEPRECATED
        • 더 이상 클래스 타입을 지원하지 않는다.
      • I2C_CLIENT_END
        • lists의 터미네이션을 위한 내부 번호
  • *algo
    • i2c 버스에 접근할 알고리즘
  • *algo_data
    • 알고리즘 데이터
  • *lock_ops
    • i2c 버스에 접근하기 위해 사용되는 lock operation 후크
    • 지정하지 않는 경우 default i2c lock operation을 사용한다.
  • bus_lock
    • 버스에 대한 뮤텍스 락
  • mux_lock
    • i2c-mux에 대한 뮤텍스 락
  • timeout
    • 타임아웃(단위: jiffies)
  • retries
    • 반복 횟수
  • dev
    • adapter 디바이스
  • nr
    • adapter 번호
    • -1: auto
  • name
    • adapter 이름
  • dev_released
  • userspace_clients_lock
    • 유저스페이스 클라이언트 뮤텍스 락
  • userspace_clients
    • 유저 스페이스 클라이언트 리스트
  • *bus_recovery_info
    • i2c 버스 리커버리를 지원하는 경우 사용한다.
  • *quirks
    • i2c 전송 제한을 지원하는 경우 사용한다.
  • *host_notify_domain
    • smbus를 사용 시 irq 를 호스트에 전달하기 위한 irq domain이다.

 

Adapter 디바이스 등록

다음 그림은 adapter 디바이스를 등록하고 해당 i2c 버스에서 검출되는 i2c 디바이스들을 등록하는 과정을 보여준다.

 

i2c_add_adapter()

drivers/i2c/i2c-core-base.c

/**                                                                             
 * i2c_add_adapter - declare i2c adapter, use dynamic bus number                
 * @adapter: the adapter to add                                                 
 * Context: can sleep                                                           
 *                                                                              
 * This routine is used to declare an I2C adapter when its bus number           
 * doesn't matter or when its bus number is specified by an dt alias.           
 * Examples of bases when the bus number doesn't matter: I2C adapters           
 * dynamically added by USB links or PCI plugin cards.                          
 *                                                                              
 * When this returns zero, a new bus number was allocated and stored            
 * in adap->nr, and the specified adapter became available for clients.         
 * Otherwise, a negative errno value is returned.                               
 */
int i2c_add_adapter(struct i2c_adapter *adapter)                                
{                                                                               
        struct device *dev = &adapter->dev;                                     
        int id;                                                                 
                                                                                
        if (dev->of_node) {                                                     
                id = of_alias_get_id(dev->of_node, "i2c");                      
                if (id >= 0) {                                                  
                        adapter->nr = id;                                       
                        return __i2c_add_numbered_adapter(adapter);             
                }                                                               
        }                                                                       
                                                                                
        mutex_lock(&core_lock);                                                 
        id = idr_alloc(&i2c_adapter_idr, adapter,                               
                       __i2c_first_dynamic_bus_num, 0, GFP_KERNEL);             
        mutex_unlock(&core_lock);                                               
        if (WARN(id < 0, "couldn't get idr"))                                   
                return id;                                                      
                                                                                
        adapter->nr = id;                                                       
                                                                                
        return i2c_register_adapter(adapter);                                   
}                                                                               
EXPORT_SYMBOL(i2c_add_adapter);

i2c adapter 디바이스를 등록하고 해당 i2c 버스의 일부 디바이스들을 HW 디텍트 시도하여 등록한다. adapter id는 디바이스 트리에서 지정할 수도 있다. 결정된 i2c adapter id는 adapter->nr에 저장된다.

  • 코드 라인 6~12에서 디바이스 트리에서 “i2c” alias 노드가 있고 버스 id가 지정된 경우 그 번호로 adapter를 등록시킨다.
  • 코드 라인 14~21에서 idr Radix 트리를 통해 i2c 버스 id를 발급받고 adapter에 지정한다. (0부터 시작)
  • 코드 라인 23에서 adapter를 등록한다.

 

__i2c_add_numbered_adapter()

drivers/i2c/i2c-core-base.c

/**                                                                             
 * __i2c_add_numbered_adapter - i2c_add_numbered_adapter where nr is never -1   
 * @adap: the adapter to register (with adap->nr initialized)                   
 * Context: can sleep                                                           
 *                                                                              
 * See i2c_add_numbered_adapter() for details.                                  
 */                                                                             
static int __i2c_add_numbered_adapter(struct i2c_adapter *adap)                 
{                                                                               
        int id;                                                                 
                                                                                
        mutex_lock(&core_lock);                                                 
        id = idr_alloc(&i2c_adapter_idr, adap, adap->nr, adap->nr + 1, GFP_KERNEL);
        mutex_unlock(&core_lock);                                               
        if (WARN(id < 0, "couldn't get idr"))                                   
                return id == -ENOSPC ? -EBUSY : id;                             
                                                                                
        return i2c_register_adapter(adap);                                      
}

지정한 i2c adapter id 번호로 id를 발급받아 i2c adapter 디바이스를 등록한다. 그런 후 해당 i2c 버스에서 검출되는 i2c 디바이스들을 등록한다.

 

i2c_register_adapter()

drivers/i2c/i2c-core-base.c

static int i2c_register_adapter(struct i2c_adapter *adap)                       
{                                                                               
        int res = -EINVAL;                                                      
                                                                                
        /* Can't register until after driver model init */                      
        if (WARN_ON(!is_registered)) {                                          
                res = -EAGAIN;                                                  
                goto out_list;                                                  
        }                                                                       
                                                                                
        /* Sanity checks */                                                     
        if (WARN(!adap->name[0], "i2c adapter has no name"))                    
                goto out_list;                                                  
                                                                                
        if (!adap->algo) {                                                      
                pr_err("adapter '%s': no algo supplied!\n", adap->name);        
                goto out_list;                                                  
        }                                                                       
                                                                                
        if (!adap->lock_ops)                                                    
                adap->lock_ops = &i2c_adapter_lock_ops;                         
                                                                                
        rt_mutex_init(&adap->bus_lock);                                         
        rt_mutex_init(&adap->mux_lock);                                         
        mutex_init(&adap->userspace_clients_lock);                              
        INIT_LIST_HEAD(&adap->userspace_clients);                               
                                                                                
        /* Set default timeout to 1 second if not already set */                
        if (adap->timeout == 0)                                                 
                adap->timeout = HZ;                                             
                                                                                
        /* register soft irqs for Host Notify */                                
        res = i2c_setup_host_notify_irq_domain(adap);                           
        if (res) {                                                              
                pr_err("adapter '%s': can't create Host Notify IRQs (%d)\n",    
                       adap->name, res);                                        
                goto out_list;                                                  
        }                                                                       
                                                                                
        dev_set_name(&adap->dev, "i2c-%d", adap->nr);                           
        adap->dev.bus = &i2c_bus_type;                                          
        adap->dev.type = &i2c_adapter_type;                                     
        res = device_register(&adap->dev);                                      
        if (res) {                                                              
                pr_err("adapter '%s': can't register device (%d)\n", adap->name, res);
                goto out_list;                                                  
        }                                                                       
                                                                                
        dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);

i2c adapter 디바이스를 등록한다. 이 후 지정된 주소에 대해 HW 디텍트를 시도하여 자동 등록한다.

  • 코드 라인 6~9에서 디바이스 드라이버 모델이 초기화되고 i2c_init() 함수가 호출되기 전까지는 adapter가 등록되지 않게 제한한다.
  • 코드 라인 12~13에서 adapter 명이 지정되지 않은 경우 경고 메시지를 출력하고 에러를 반환한다.
  • 코드 라인 15~18에서 알고리즘이 지정되지 않은 경우 에러 메시지를 출력하고 에러를 반환한다.
  • 코드 라인 20~21에서 adapter에서 지원하는 bus lock 오퍼레이션이 없는 경우 디폴트 i2c bus lock 오퍼레이션을 사용하게 한다.
  • 코드 라인 23~26에서 adapter의 lock들과 userspace_clients 리스트를 초기화한다.
  • 코드 라인 29~30에서 타임아웃이 지정되지 않은 경우 1초로 제한한다.
  • 코드 라인 33~38에서 smbus host notify를 위해 0x78개의 irq 디스크립터용 리니어 irq 도메인을 생성한다.
  • 코드 라인 40~47에서 adapter 이름을 지정하고 버스 및 i2c adapter 타입을 지정한 후 디바이스로 등록한다.

 

        pm_runtime_no_callbacks(&adap->dev);                                    
        pm_suspend_ignore_children(&adap->dev, true);                           
        pm_runtime_enable(&adap->dev);                                          
                                                                                
#ifdef CONFIG_I2C_COMPAT                                                        
        res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,    
                                       adap->dev.parent);                       
        if (res)                                                                
                dev_warn(&adap->dev,                                            
                         "Failed to create compatibility class link\n");        
#endif                                                                          
                                                                                
        i2c_init_recovery(adap);                                                
                                                                                
        /* create pre-declared device nodes */                                  
        of_i2c_register_devices(adap);                                          
        i2c_acpi_register_devices(adap);                                        
        i2c_acpi_install_space_handler(adap);                                   
                                                                                
        if (adap->nr < __i2c_first_dynamic_bus_num)                             
                i2c_scan_static_board_info(adap);                               
                                                                                
        /* Notify drivers */                                                    
        mutex_lock(&core_lock);                                                 
        bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);     
        mutex_unlock(&core_lock);                                               
                                                                                
        return 0;                                                               
                                                                                
out_list:                                                                       
        mutex_lock(&core_lock);                                                 
        idr_remove(&i2c_adapter_idr, adap->nr);                                 
        mutex_unlock(&core_lock);                                               
        return res;                                                             
}
  • 코드 라인 1에서 이 adapter 디바이스에 대한 런타임 PM 콜백을 무시하게 한다.
  • 코드 라인 2에서 이 adapter 디바이스의 자식 디바이스들에 대해 절전 모드 진입을 무시하게 한다.
  • 코드 라인 3에서 이 adapter 디바이스에 대한 런타임 PM을 enable한다.
  • 코드 라인 5~11에서 기존 user-space에서 클래스 디바이스를 사용하던 디바이스들과의 호환을 위해 심볼링크를 생성한다.
    • /sys/class/i2c-adapter/i2c-0 심볼링크가  /sys/devices/platform/66080000.i2c/i2c-0 adapter 클래스 디바이스를 가리키게한다.
    • /sys/devices/platform/66080000.i2c/i2c-0/device 심볼링크가 /sys/devices/platform/66080000.i2c를 가리키게한다.
  • 코드 라인 13에서 adapter가 관리하는 i2c 버스 리커버리를 초기화한다.
  • 코드 라인 16에서 adpater에 해당하는 버스 노드 뒤의 디바이스들을 등록한다.
    • 호스트 컨트롤러 노드의 서브노드에 “i2c-bus” 노드가 있으면 그 하위 노드들에 있는 디바이스들을 등록한다. 만일 “i2c-bus” 노드가 없으면 adapter 노드의 서브 노드들에 있는 디바이스들을 등록한다.
  • 코드 라인 17~18에서 adapter 버스에 매치되는 acpi 테이블의 디바이스들을 등록하고 i2c acpi address space 핸들러를 등록한다.
  • 코드 라인 20~21에서 __i2c_board_list에 등록된 디바이스들 중 버스 번호가 같은 디바이스들을 static하게 등록한다.
    • i2c_register_board_info() 함수를 사용하여 등록한다.
  • 코드 라인 25에서 마지막으로 해당 adapter의 i2c 버스에서 등록된 모든 드라이버들의 주소 리스트를 사용하여 즉, detect 지원되는 디바이스들을 대상으로 HW detect 후 발견되면 등록한다. 그리고 legacy 드라이버가 디바이스를 디텍트할 수 있도록 (*attach_adapter) 후크가 있는 경우 호출한다.

 

__process_new_adapter()

drivers/i2c/i2c-core-base.c

static int __process_new_adapter(struct device_driver *d, void *data)           
{                                                                               
        return i2c_do_add_adapter(to_i2c_driver(d), data);                      
}

아래 함수로 연결된다.

 

i2c_do_add_adapter()

drivers/i2c/i2c-core-base.c

static int i2c_do_add_adapter(struct i2c_driver *driver,                        
                              struct i2c_adapter *adap)                         
{                                                                               
        /* Detect supported devices on that bus, and instantiate them */        
        i2c_detect(adap, driver);                                               
                                                                                
        /* Let legacy drivers scan this bus for matching devices */             
        if (driver->attach_adapter) {                                           
                dev_warn(&adap->dev, "%s: attach_adapter method is deprecated\n",
                         driver->driver.name);                                  
                dev_warn(&adap->dev,                                            
                         "Please use another way to instantiate your i2c_client\n");
                /* We ignore the return code; if it fails, too bad */           
                driver->attach_adapter(adap);                                   
        }                                                                       
        return 0;                                                               
}

adapter에 해당하는 i2c 버스에서 detect 지원되는 디바이스들을 HW detect 후 발견되면 등록한다. 그리고 legacy 드라이버가 디바이스를 디텍트할 수 있도록 (*attach_adapter) 후크가 있는 경우 호출한다.

 

I2C 디바이스 검출

i2c_detect()

drivers/i2c/i2c-core-base.c

static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)   
{                                                                               
        const unsigned short *address_list;                                     
        struct i2c_client *temp_client;                                         
        int i, err = 0;                                                         
        int adap_id = i2c_adapter_id(adapter);                                  
                                                                                
        address_list = driver->address_list;                                    
        if (!driver->detect || !address_list)                                   
                return 0;                                                       
                                                                                
        /* Warn that the adapter lost class based instantiation */              
        if (adapter->class == I2C_CLASS_DEPRECATED) {                           
                dev_dbg(&adapter->dev,                                          
                        "This adapter dropped support for I2C classes and won't auto-detect %s devices anymore. "
                        "If you need it, check 'Documentation/i2c/instantiating-devices' for alternatives.\n",
                        driver->driver.name);                                   
                return 0;                                                       
        }                                                                       
                                                                                
        /* Stop here if the classes do not match */                             
        if (!(adapter->class & driver->class))                                  
                return 0;                                                       
                                                                                
        /* Set up a temporary client to help detect callback */                 
        temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);           
        if (!temp_client)                                                       
                return -ENOMEM;                                                 
        temp_client->adapter = adapter;                                         
                                                                                
        for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {                
                dev_dbg(&adapter->dev,                                          
                        "found normal entry for adapter %d, addr 0x%02x\n",     
                        adap_id, address_list[i]);                              
                temp_client->addr = address_list[i];                            
                err = i2c_detect_address(temp_client, driver);                  
                if (unlikely(err))                                              
                        break;                                                  
        }                                                                       
                                                                                
        kfree(temp_client);                                                     
        return err;                                                             
}

adapter에 해당하는 i2c 버스에서 detect 지원되는 디바이스들을 HW detect 후 발견되면 등록한다.

  • 코드 라인 8~10에서 드라이버에 지정된 주소 리스트가 없거나 드라이버에 (*detect) 후크 함수가 없는 경우 HW 디텍트를 수행할 필요가 없으므로 성공(0) 결과로 함수를 빠져나간다.
  • 코드 라인 13~18에서 adapter 클래스가 I2C_CLASS_DEPRECATED로 지정된 경우 디버그 메시지를 출력하고 성공(0) 결과로 함수를 빠져나간다.
  • 코드 라인 21~22에서 adapter와 드라이버가 가리키는 클래스가 동일하지 않으면 성공(0) 결과로 함수를 빠져나간다.
  • 코드 라인 25~28에서 i2c_client를 하나 임시로 만들고 adapter를 지정한다.
  • 코드 라인 30~38에서 주소 리스트 수 만큼 순회하며 주소로 HW 디텍트를 시도하여 검출되는 디바이스를 등록한다.
  • 코드 라인 40에서 임시로 할당한 i2c_client를 할당해제한다.

 

i2c_detect_address()

drivers/i2c/i2c-core-base.c

static int i2c_detect_address(struct i2c_client *temp_client,                   
                              struct i2c_driver *driver)                        
{                                                                               
        struct i2c_board_info info;                                             
        struct i2c_adapter *adapter = temp_client->adapter;                     
        int addr = temp_client->addr;                                           
        int err;                                                                
                                                                                
        /* Make sure the address is valid */                                    
        err = i2c_check_7bit_addr_validity_strict(addr);                        
        if (err) {                                                              
                dev_warn(&adapter->dev, "Invalid probe address 0x%02x\n",       
                         addr);                                                 
                return err;                                                     
        }                                                                       
                                                                                
        /* Skip if already in use (7 bit, no need to encode flags) */           
        if (i2c_check_addr_busy(adapter, addr))                                 
                return 0;                                                       
                                                                                
        /* Make sure there is something at this address */                      
        if (!i2c_default_probe(adapter, addr))                                  
                return 0;                                                       
                                                                                
        /* Finally call the custom detection function */                        
        memset(&info, 0, sizeof(struct i2c_board_info));                        
        info.addr = addr;                                                       
        err = driver->detect(temp_client, &info);                               
        if (err) {                                                              
                /* -ENODEV is returned if the detection fails. We catch it      
                   here as this isn't an error. */                              
                return err == -ENODEV ? 0 : err;                                
        }                                                                       
                                                                                
        /* Consistency check */                                                 
        if (info.type[0] == '\0') {                                             
                dev_err(&adapter->dev,                                          
                        "%s detection function provided no name for 0x%x\n",    
                        driver->driver.name, addr);                             
        } else {                                                                
                struct i2c_client *client;                                      
                                                                                
                /* Detection succeeded, instantiate the device */               
                if (adapter->class & I2C_CLASS_DEPRECATED)                      
                        dev_warn(&adapter->dev,                                 
                                "This adapter will soon drop class based instantiation of devices. "
                                "Please make sure client 0x%02x gets instantiated by other means. "
                                "Check 'Documentation/i2c/instantiating-devices' for details.\n",
                                info.addr);                                     
                                                                                
                dev_dbg(&adapter->dev, "Creating %s at 0x%02x\n",               
                        info.type, info.addr);                                  
                client = i2c_new_device(adapter, &info);                        
                if (client)                                                     
                        list_add_tail(&client->detected, &driver->clients);     
                else                                                            
                        dev_err(&adapter->dev, "Failed creating %s at 0x%02x\n",
                                info.type, info.addr);                          
        }                                                                       
        return 0;                                                               
}

주소로 HW 디텍트를 시도하여 검출되는 디바이스를 등록한다.

  • 코드 라인 10~15에서 요청한 타겟 주소가 0x08~0x77 범위를 벗어나는 경우 경고 메시지를 출력하고 함수를 빠져나간다.
  • 코드 라인 18~19에서 요청한 타겟 주소가 mux 트리에 연결되어 있을 수 있으므로 요청한 타겟 주소까지 busy한 장비가 있는지 확인한다.
  • 코드 라인 22~23에서 smbus를 사용하는 특정 장치의 HW detect를 위해 smbus에서 1바이트를 전송 또는 수신하는 것으로 probe 한다.
  • 코드 라인 26~33에서 최종 custom detection을 위해 i2c_board_info 구조체에 타겟 주소를 담고  드라이버의 (*detect) 후크 함수를 호출한다.
  • 코드 라인 36~39에서 디바이스 이름을 알아오지 못한 경우 에러 메시지를 출력한다.
  • 코드 라인 40~49에서 adapter 클래스가 I2C_CLASS_DEPRECATED 인 경우 경고 메시지를 출력한다.
  • 코드 라인 53~58에서 검출된 디바이스를 등록한다. 그리고 드라이버의 clients 리스트에 detect된 디바이스를 추가한다.

 

i2c_check_7bit_addr_validity_strict()

drivers/i2c/i2c-core-base.c

/* And this is a strict address validity check, used when probing. If a
 * device uses a reserved address, then it shouldn't be probed. 7-bit
 * addressing is assumed, 10-bit address devices are rare and should be
 * explicitly enumerated. */
int i2c_check_7bit_addr_validity_strict(unsigned short addr)
{
        /*
         * Reserved addresses per I2C specification:
         *  0x00       General call address / START byte
         *  0x01       CBUS address
         *  0x02       Reserved for different bus format
         *  0x03       Reserved for future purposes
         *  0x04-0x07  Hs-mode master code
         *  0x78-0x7b  10-bit slave addressing
         *  0x7c-0x7f  Reserved for future purposes
         */
        if (addr < 0x08 || addr > 0x77)
                return -EINVAL;
        return 0;
}

요청한 타겟 주소가 0x08~0x77 범위이내인 경우 성공(0)을 반환한다.

 

i2c_default_probe()

drivers/i2c/i2c-core-base.c

/* ----------------------------------------------------
 * the i2c address scanning function
 * Will not work for 10-bit addresses!
 * ----------------------------------------------------
 */

/*
 * Legacy default probe function, mostly relevant for SMBus. The default
 * probe method is a quick write, but it is known to corrupt the 24RF08
 * EEPROMs due to a state machine bug, and could also irreversibly
 * write-protect some EEPROMs, so for address ranges 0x30-0x37 and 0x50-0x5f,
 * we use a short byte read instead. Also, some bus drivers don't implement
 * quick write, so we fallback to a byte read in that case too.
 * On x86, there is another special case for FSC hardware monitoring chips,
 * which want regular byte reads (address 0x73.) Fortunately, these are the
 * only known chips using this I2C address on PC hardware.
 * Returns 1 if probe succeeded, 0 if not.
 */
static int i2c_default_probe(struct i2c_adapter *adap, unsigned short addr)
{
        int err;
        union i2c_smbus_data dummy;

#ifdef CONFIG_X86
        if (addr == 0x73 && (adap->class & I2C_CLASS_HWMON)
         && i2c_check_functionality(adap, I2C_FUNC_SMBUS_READ_BYTE_DATA))
                err = i2c_smbus_xfer(adap, addr, 0, I2C_SMBUS_READ, 0,
                                     I2C_SMBUS_BYTE_DATA, &dummy);
        else
#endif
        if (!((addr & ~0x07) == 0x30 || (addr & ~0x0f) == 0x50)
         && i2c_check_functionality(adap, I2C_FUNC_SMBUS_QUICK))
                err = i2c_smbus_xfer(adap, addr, 0, I2C_SMBUS_WRITE, 0,
                                     I2C_SMBUS_QUICK, NULL);
        else if (i2c_check_functionality(adap, I2C_FUNC_SMBUS_READ_BYTE))
                err = i2c_smbus_xfer(adap, addr, 0, I2C_SMBUS_READ, 0,
                                     I2C_SMBUS_BYTE, &dummy);
        else {
                dev_warn(&adap->dev, "No suitable probing method supported for address 0x%02X\n",
                         addr);
                err = -EOPNOTSUPP;
        }

        return err >= 0;
}

레거시 default probe 펑션으로 7비트 주소를 사용하며 대부분 SMBus에서 사용된다. 제한된 범위의 주소를 사용하는 디바이스들을 대상으로 SMBus에 읽거나 쓰는 것으로 HW detect를 하기 위해 probe 준비를 한다. 성공의 경우 1을 반환한다.

  • 코드 라인 6~12에서 x86 pc만 해당하며 0x73 주소를 사용하는 센서 디바이스(lm73)에서 1바이트 데이터를 읽어온다.
  • 코드 라인 13~16에서 0x30~0x37 또는 0x50~0x5f 주소에 해당하는 디바이스들을 대상으로 smbus quick 송신한다. (1 바이트)
  • 코드 라인 17~19에서 그 외의 디바이스를 대상으로 smbus로 1바이트를 읽어온다.

 

I2C 주소 busy 체크

i2c_check_addr_busy()

drivers/i2c/i2c-core-base.c

static int i2c_check_addr_busy(struct i2c_adapter *adapter, int addr)
{
        struct i2c_adapter *parent = i2c_parent_is_i2c_adapter(adapter);
        int result = 0;

        if (parent)
                result = i2c_check_mux_parents(parent, addr);

        if (!result)
                result = device_for_each_child(&adapter->dev, &addr,
                                                i2c_check_mux_children);

        return result;
}

주소 busy 체크를 수행한다. i2c mux가 사용될 수 있으므로 윗 방향과 아랫 방향을 계속 조사한다. 성공의 경우 0을 반환한다.

 

i2c_check_mux_parents()

drivers/i2c/i2c-core-base.c

/* walk up mux tree */
static int i2c_check_mux_parents(struct i2c_adapter *adapter, int addr)
{
        struct i2c_adapter *parent = i2c_parent_is_i2c_adapter(adapter);
        int result;

        result = device_for_each_child(&adapter->dev, &addr,
                                        __i2c_check_addr_busy);

        if (!result && parent)
                result = i2c_check_mux_parents(parent, addr);

        return result;
}

mux 트리의 윗 방향으로 이동하면서 주소 busy 체크를 수행한다. 성공의 경우 0을 반환한다.

  • 코드 라인 4에서 i2c-mux 인 경우 부모 adapter를 알아온다. 없는 경우 null이다.
  • 코드 라인 7~8에서 현재 adapter에 연결된 i2c 디바이스들을 대상으로 주소 busy 체크를 수행한다. 성공은 0을 반환한다.
  • 코드 라인 10~11에서 결과가 성공이면서 부모 adapter가 있는 경우 부모 adapter로 이 함수를 재귀 호출한다.

 

i2c_check_mux_children()

drivers/i2c/i2c-core-base.c

/* recurse down mux tree */
static int i2c_check_mux_children(struct device *dev, void *addrp)
{
        int result;

        if (dev->type == &i2c_adapter_type)
                result = device_for_each_child(dev, addrp,
                                                i2c_check_mux_children);
        else
                result = __i2c_check_addr_busy(dev, addrp);

        return result;
}

디바이스가 adapter인 경우 자식 디바이스들을 대상으로 busy 체크하기 위해 재귀 호출읋하고, 클라이언트 디바이스인 경우 busy 체크를 수행한다. 성공의 경우 0을 반환한다.

 

__i2c_check_addr_busy()

drivers/i2c/i2c-core-base.c

static int __i2c_check_addr_busy(struct device *dev, void *addrp)
{
        struct i2c_client       *client = i2c_verify_client(dev);
        int                     addr = *(int *)addrp;

        if (client && i2c_encode_flags_to_addr(client) == addr)
                return -EBUSY;
        return 0;
}

클라이언트 디바이스의 주소가이미 엔코딩되어 있는 경우 -EBUSY를 반환한다. 그 외 성공(0)을 반환한다.

 

i2c_encode_flags_to_addr()

drivers/i2c/i2c-core-base.c

/* Return a unique address which takes the flags of the client into account */
static unsigned short i2c_encode_flags_to_addr(struct i2c_client *client)
{
        unsigned short addr = client->addr;

        /* For some client flags, add an arbitrary offset to avoid collisions */
        if (client->flags & I2C_CLIENT_TEN)
                addr |= I2C_ADDR_OFFSET_TEN_BIT;

        if (client->flags & I2C_CLIENT_SLAVE)
                addr |= I2C_ADDR_OFFSET_SLAVE;

        return addr;
}

클라이언트 디바이스에 사용하는 주소를 엔코딩한다.

  • 클라이언트 디바이스가 10 비트 주소를 사용하는 경우 주소에 0xa000을 or하고, slave 디바이스의 경우 0x20을 or 한다.

 

i2c 클라이언트 등록

i2c_client 구조체

include/linux/i2c.h

/**                                                                             
 * struct i2c_client - represent an I2C slave device                            
 * @flags: I2C_CLIENT_TEN indicates the device uses a ten bit chip address;     
 *      I2C_CLIENT_PEC indicates it uses SMBus Packet Error Checking            
 * @addr: Address used on the I2C bus connected to the parent adapter.          
 * @name: Indicates the type of the device, usually a chip name that's          
 *      generic enough to hide second-sourcing and compatible revisions.        
 * @adapter: manages the bus segment hosting this I2C device                    
 * @dev: Driver model device node for the slave.                                
 * @irq: indicates the IRQ generated by this device (if any)                    
 * @detected: member of an i2c_driver.clients list or i2c-core's                
 *      userspace_devices list                                                  
 * @slave_cb: Callback when I2C slave mode of an adapter is used. The adapter   
 *      calls it to pass on slave events to the slave driver.                   
 *                                                                              
 * An i2c_client identifies a single device (i.e. chip) connected to an         
 * i2c bus. The behaviour exposed to Linux is defined by the driver             
 * managing the device.                                                         
 */
struct i2c_client {                                                             
        unsigned short flags;           /* div., see below              */      
        unsigned short addr;            /* chip address - NOTE: 7bit    */      
                                        /* addresses are stored in the  */      
                                        /* _LOWER_ 7 bits               */      
        char name[I2C_NAME_SIZE];                                               
        struct i2c_adapter *adapter;    /* the adapter we sit on        */      
        struct device dev;              /* the device structure         */      
        int irq;                        /* irq issued by device         */      
        struct list_head detected;                                              
#if IS_ENABLED(CONFIG_I2C_SLAVE)                                                
        i2c_slave_cb_t slave_cb;        /* callback for slave mode      */      
#endif                                                                          
};
  • flags
    • I2C_CLIENT_PEC
      • smbus에서 패킷 에러 체킹
    • I2C_CLIENT_TEN
      • 10bit 주소 사용
    • I2C_CLIENT_SLAVE
      • slave 디바이스(EEPROM 등)
    • I2C_CLIENT_HOST_NOTIFY
      • smbus의 i2c host notify (인터럽트)
    • I2C_CLIENT_WAKE
      • 절전 기능을 위해 깨워야 하는경우 사용
    • I2C_CLIENT_SCCB
      • 옴니비전의 SCCB 프로토콜에서 사용
  • addr
    • 디바이스 주소
  • name
    • 디바이스 명
  • *adapter
    • adapter를 가리킨다.
  • dev
    • 디바이스
  • irq
    • irq가 사용되는 경우 irq 번호
  • detected
    • i2c_driver의 clients 리스트에 사용되는 노드
  • slave_cb
    • 슬레이브 callback

 

다음 그림은 i2c 클라이언트 디바이스 및 드라이버가 등록되어 매치되는 경우 드라이버의 probe 함수가 호출되는 과정을 보여준다.

 

i2c 디바이스 등록

i2c_new_device()

drivers/i2c/i2c-core-base.c

/**
 * i2c_new_device - instantiate an i2c device
 * @adap: the adapter managing the device
 * @info: describes one I2C device; bus_num is ignored
 * Context: can sleep
 *
 * Create an i2c device. Binding is handled through driver model
 * probe()/remove() methods.  A driver may be bound to this device when we
 * return from this function, or any later moment (e.g. maybe hotplugging will
 * load the driver module).  This call is not appropriate for use by mainboard
 * initialization logic, which usually runs during an arch_initcall() long
 * before any i2c_adapter could exist.
 *
 * This returns the new i2c client, which may be saved for later use with
 * i2c_unregister_device(); or NULL to indicate an error.
 */
struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
        struct i2c_client       *client;
        int                     status;

        client = kzalloc(sizeof *client, GFP_KERNEL);
        if (!client)
                return NULL;

        client->adapter = adap;

        client->dev.platform_data = info->platform_data;

        if (info->archdata)
                client->dev.archdata = *info->archdata;

        client->flags = info->flags;
        client->addr = info->addr;

        client->irq = info->irq;
        if (!client->irq)
                client->irq = i2c_dev_irq_from_resources(info->resources,
                                                         info->num_resources);

        strlcpy(client->name, info->type, sizeof(client->name));

        status = i2c_check_addr_validity(client->addr, client->flags);
        if (status) {
                dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx\n",
                        client->flags & I2C_CLIENT_TEN ? 10 : 7, client->addr);
                goto out_err_silent;
        }

인자로 전달받은 adapter와 i2c 보드 정보로 i2c 디바이스를 생성한 후 등록한다. 매치되는 드라이버가 있는 경우 드라이버의 probe 후크를 호출한다.

  • 코드 라인 7~21에서 i2c_client 구조체를 할당받은 후 인자로 전달받은 adapter 포인터와 i2c 보드 정보로 설정한다.
  • 코드 라인 22~24에서 irq 정보가 없으면 리소스에서 찾아온다.
  • 코드 라인 26에서 보드 정보에 있는 이름을 i2c_client 이름으로 설정한다.
  • 코드 라인 28~33에서 디바이스 주소가 유효한지 체크한다.
    • 10비트 주소를 사용하는 경우 주소는 0x3ff를 초과하면 안된다.
    • 7비트 주소를 사용하는 경우 주소는 0x01~0x7f 범위를 벗어나면 안된다.

 

        /* Check for address business */
        status = i2c_check_addr_busy(adap, i2c_encode_flags_to_addr(client));
        if (status)
                goto out_err;
                
        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;
        client->dev.fwnode = info->fwnode;

        i2c_dev_set_name(adap, client);

        if (info->properties) {
                status = device_add_properties(&client->dev, info->properties);
                if (status) {
                        dev_err(&adap->dev,
                                "Failed to add properties to client %s: %d\n",
                                client->name, status);
                        goto out_err;
                }
        }

        status = device_register(&client->dev);
        if (status)
                goto out_free_props;
                
        dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n",
                client->name, dev_name(&client->dev));
                
        return client;

out_free_props:
        if (info->properties)
                device_remove_properties(&client->dev);
out_err:        
        dev_err(&adap->dev,
                "Failed to register i2c client %s at 0x%02x (%d)\n",
                client->name, client->addr, status);
out_err_silent:
        kfree(client);
        return NULL;
}
EXPORT_SYMBOL_GPL(i2c_new_device);
  • 코드 라인 2~4에서 해당 주소의 디바이스까지 접근하는데 busy하지 않은지 체크한다.
    • 해당 주소를 사용하는 디바이스까지 mux 트리를 검색한다.
  • 코드 라인 6~12에서 i2c_client 멤버 정보를 설정한다.
  • 코드 라인 14~22에서 디바이스 속성이 주어진 경우 디바이스에 속성들을 추가한다.
  • 코드 라인 24~26에서 디바이스를 등록한다.

 

i2c_driver 구조체

include/linux/i2c.h

/**                                                                             
 * struct i2c_driver - represent an I2C device driver                           
 * @class: What kind of i2c device we instantiate (for detect)                  
 * @attach_adapter: Callback for bus addition (deprecated)                      
 * @probe: Callback for device binding - soon to be deprecated                  
 * @probe_new: New callback for device binding                                  
 * @remove: Callback for device unbinding                                       
 * @shutdown: Callback for device shutdown                                      
 * @alert: Alert callback, for example for the SMBus alert protocol             
 * @command: Callback for bus-wide signaling (optional)                         
 * @driver: Device driver model driver                                          
 * @id_table: List of I2C devices supported by this driver                      
 * @detect: Callback for device detection                                       
 * @address_list: The I2C addresses to probe (for detect)                       
 * @clients: List of detected clients we created (for i2c-core use only)        
 * @disable_i2c_core_irq_mapping: Tell the i2c-core to not do irq-mapping       
 *                                                                              
 * The driver.owner field should be set to the module owner of this driver.     
 * The driver.name field should be set to the name of this driver.              
 *                                                                              
 * For automatic device detection, both @detect and @address_list must          
 * be defined. @class should also be set, otherwise only devices forced         
 * with module parameters will be created. The detect function must             
 * fill at least the name field of the i2c_board_info structure it is           
 * handed upon successful detection, and possibly also the flags field.         
 *                                                                              
 * If @detect is missing, the driver will still work fine for enumerated        
 * devices. Detected devices simply won't be supported. This is expected        
 * for the many I2C/SMBus devices which can't be detected reliably, and         
 * the ones which can always be enumerated in practice.                         
 *                                                                              
 * The i2c_client structure which is handed to the @detect callback is          
 * not a real i2c_client. It is initialized just enough so that you can         
 * call i2c_smbus_read_byte_data and friends on it. Don't do anything           
 * else with it. In particular, calling dev_dbg and friends on it is            
 * not allowed.                                                                 
 */
struct i2c_driver {                                                             
        unsigned int class;                                                     
                                                                                
        /* Notifies the driver that a new bus has appeared. You should avoid    
         * using this, it will be removed in a near future.                     
         */                                                                     
        int (*attach_adapter)(struct i2c_adapter *) __deprecated;               
                                                                                
        /* Standard driver model interfaces */                                  
        int (*probe)(struct i2c_client *, const struct i2c_device_id *);        
        int (*remove)(struct i2c_client *);                                     
                                                                                
        /* New driver model interface to aid the seamless removal of the        
         * current probe()'s, more commonly unused than used second parameter.  
         */                                                                     
        int (*probe_new)(struct i2c_client *);                                  
                                                                                
        /* driver model interfaces that don't relate to enumeration  */         
        void (*shutdown)(struct i2c_client *);                                  
                                                                                
        /* Alert callback, for example for the SMBus alert protocol.            
         * The format and meaning of the data value depends on the protocol.    
         * For the SMBus alert protocol, there is a single bit of data passed   
         * as the alert response's low bit ("event flag").                      
         * For the SMBus Host Notify protocol, the data corresponds to the      
         * 16-bit payload data reported by the slave device acting as master.   
         */                                                                     
        void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol,    
                      unsigned int data);                                       
                                                                                
        /* a ioctl like command that can be used to perform specific functions  
         * with the device.                                                     
         */                                                                     
        int (*command)(struct i2c_client *client, unsigned int cmd, void *arg); 
                                                                                
        struct device_driver driver;                                            
        const struct i2c_device_id *id_table;                                   
                                                                                
        /* Device detection callback for automatic device creation */           
        int (*detect)(struct i2c_client *, struct i2c_board_info *);            
        const unsigned short *address_list;                                     
        struct list_head clients;                                               
                                                                                
        bool disable_i2c_core_irq_mapping;                                      
};
  • class
    • detect를 위한 i2c 디바이스 타입 번호이다.
    • pure smbus에서 자동 디텍션이 필요한 경우 다음과 같은 타입을 선택하여 사용한다.
      • I2C_CLASS_HWMON
        • lm 센서등에서 사용한다.
      • I2C_CLASS_DDC
        • 그래픽 어댑터에 위치한 DDC 버스에서 사용한다.
      • I2C_CLASS_SPD
        • 메모리 모듈에서 사용한다.
      • I2C_CLASS_DEPRECATED
        • detect를 위해 클래스 구분을 하지 않을 계획으로 deprecated되었다.
  • (*attach_adapter)
    • legacy device detection 방식으로 추후 deprecated
  • (*probe)
    • 표준 드라이버 probe 인터페이스
  • (*remove)
    • 표준 드라이버 remove 인터페이스
  • (*probe_new)
    • 새로운 드라이버 probe 인터페이스이다.
  • (*shutdown)
    • shutdown 전에 사용될 후크 함수가 지정된다.
  • (*alert)
    • smbus에서 alert를 위한 호스트 콜백 후크이다.
  • (*command)
    • ioctl과 유사한 명령이다.
  • driver
    • 드라이버
  • *id_table
    • 디바이스와 매치될 때 사용할 id 테이블
  • (*detect)
    • smbus에서 디바이스를 등록하기 위해 디바이스 HW 디텍트 시도를 통해 디텍션할 후크 함수를 지정한다.
  • *address_list
    • smbus에서 디텍트할 디바이스 주소
  • clients
    • i2c 클라이언트가 등록되는 리스트이다.
  • disable_i2c_core_irq_mapping
    • irq 매핑을 하지 않을 때 사용한다.

 

Extra 클라이언트 데이터의 취급

i2c_client 구조체에 임베드되어 있는 device 구조체의 driver_data 필드를 사용하여 extra 클라이언트 데이터를 사용할 수 있도록 다음 API들이 사용된다.

  • 저장하기
    • void i2c_set_clientdata(struct i2c_client *client, void *data);
  • 가져오기
    • void *i2c_get_clientdata(const struct i2c_client *client);

 

클라이언트에서 i2c 전송

간단히 1 바이트 데이터 송신 및 수신하는 방법은 다음과 같다.

int foo_read_value(struct i2c_client *client, u8 reg)                           
{                                                                               
        if (reg < 0x10) /* byte-sized register */                               
                return i2c_smbus_read_byte_data(client, reg);                   
        else            /* word-sized register */                               
                return i2c_smbus_read_word_data(client, reg);                   
}                                                                               
                                                                                
int foo_write_value(struct i2c_client *client, u8 reg, u16 value)               
{                                                                               
        if (reg == 0x10)        /* Impossible to write - driver error! */       
                return -EINVAL;                                                 
        else if (reg < 0x10)    /* byte-sized register */                       
                return i2c_smbus_write_byte_data(client, reg, value);           
        else                    /* word-sized register */                       
                return i2c_smbus_write_word_data(client, reg, value);           
}

 

i2c 드라이버 등록

i2c_register_driver()

drivers/i2c/i2c-core-base.c

/*                                                                              
 * An i2c_driver is used with one or more i2c_client (device) nodes to access   
 * i2c slave chips, on a bus instance associated with some i2c_adapter.         
 */                                                                            
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)        
{
        int res;

        /* Can't register until after driver model init */                      
        if (WARN_ON(!is_registered))                                            
                return -EAGAIN;                                                 
                                                                                
        /* add the driver to the list of i2c drivers in the driver core */      
        driver->driver.owner = owner;                                           
        driver->driver.bus = &i2c_bus_type;                                     
        INIT_LIST_HEAD(&driver->clients);                                       
                                                                                
        /* When registration returns, the driver core                           
         * will have called probe() for all matching-but-unbound devices.       
         */                                                                     
        res = driver_register(&driver->driver);                                 
        if (res)                                                                
                return res;                                                     
                                                                                
        pr_debug("driver [%s] registered\n", driver->driver.name);              
                                                                                
        /* Walk the adapters that are already present */                        
        i2c_for_each_dev(driver, __process_new_driver);                         
                                                                                
        return 0;                                                               
}                                                                               
EXPORT_SYMBOL(i2c_register_driver);

i2c 드라이버를 등록한다. 매치되는 디바이스가 있는 경우 드라이버의 probe 후크를 호출한다.

  • 코드 라인 14~16에서 드라이버의 모듈 정보와 i2c 버스 타입을 지정한다.그리고 드라이버의 클라이언트 리스트를 초기화한다.
  • 코드 라인 21~23에서 드라이버를 등록한다.
  • 코드 라인 28에서 i2c 버스에 등록된 모든 디바이스들에 대해 순회하며 만일 디바이스가 어답터인 경우 워커스레드에서 아답터에 등록된 주소들을 대상으로 디바이스를 디텍트 시도하여 등록한다.

 

I2C Functionality

모든 i2c adapter가 i2c 전송 또는 smbus를 지원하는 것은 아니다. 따라서 adapter가 지원하는 기능을 비트별로 노출하여 i2c 클라이언트가 이를 확인하여 동작할 수 있게 지원한다.

i2c_adapter 구조체의 functionality 필드의 각 비트는 다음과 같다.

  • I2C_FUNC_I2C
    • i2c 레벨 전송 방식을 사용한다.
  • I2C_FUNC_10BIT_ADDR
    • 10비트 주소를 지원한다.
  • I2C_FUNC_PROTOCOL_MANGLING
    • i2c 트랜잭션을 처리 시 다음 플래그들을 사용할 수 있다.
      • I2C_M_IGNORE_NAK
      • I2C_M_REV_DIR_ADDR
      • I2C_M_NO_RD_ACK
  • I2C_FUNC_SMBUS_PEC
    • smbus에 PEC(Packet Error Checking)를 사용한다.
  • I2C_FUNC_NOSTART
    • 반복(repeted) start를 skip할 수 있다.
    • 이 기능은 I2C_FUNC_PROTOCOL_MANGLING에서 처리되었던 기능을 kernel v3.5부터 분리하여 플래그로 제공한다.
  • I2C_FUNC_SLAVE
    • slave 디바이스가 clock streaching을 할 수 있도록 지원한다.
    • slave 디바이스가 clock을 low 상태로 유지시킨 채 정지하면 마스터가 버스를 사용하지 못한다.
      • 디바이스를 리셋하도록 하는 버스 리커버리가 필요하다.
  • I2C_FUNC_SMBUS_BLOCK_PROC_CALL
    • smbus 프로토콜 2.0을 사용하여 블럭(최대 32 바이트) 데이터를 송신 후 다시 블럭 데이터 수신 지원
  • I2C_FUNC_SMBUS_QUICK
    • smbus 프로토콜을 사용하여 주소만 지정하고 이에 응답하는 1 비트 ACK를 읽는 quick read 지원
    • R/W 필드 1비트를 데이터로 사용하여 quick write하는 특이한 경우도 있다.
  • I2C_FUNC_SMBUS_READ_BYTE
    • smbus 프로토콜을 사용하여 1 바이트 읽기 지원
    • i2c_smbus_read_byte() 함수
  • I2C_FUNC_SMBUS_WRITE_BYTE
    • smbus 프로토콜을 사용하여 1 바이트 쓰기 지원
    • i2c_smbus_write_byte() 함수
  • I2C_FUNC_SMBUS_READ_BYTE_DATA
    • smbus 프로토콜을 사용하여 smbus 메시지 구조체를 사용하여 1 바이트 command 쓰기 + 1 바이트 읽기
  • I2C_FUNC_SMBUS_WRITE_BYTE_DATA
    • smbus 프로토콜을 사용하여 smbus 메시지 구조체를 사용하여 1 바이트 command 쓰기 + 1 바이트 쓰기
  • I2C_FUNC_SMBUS_READ_WORD_DATA
    • smbus 프로토콜을 사용하여 워드(2 바이트) 데이터 읽기 지원
  • I2C_FUNC_SMBUS_WRITE_WORD_DATA
    • smbus 프로토콜을 사용하여 워드(2 바이트) 데이터 쓰기 지원
  • I2C_FUNC_SMBUS_PROC_CALL
    • smbus 프로토콜 2.0을 사용하여 워드(2 바이트)를 송신 후 다시 워드 수신 지원
  • I2C_FUNC_SMBUS_READ_BLOCK_DATA
    • smbus 프로토콜을 사용하여 블럭(최대 32바이트) 데이터 읽기 지원
  • I2C_FUNC_SMBUS_WRITE_BLOCK_DATA
    • smbus 프로토콜을 사용하여 불럭(최대 32바이트) 데이터 쓰기 지원
  • I2C_FUNC_SMBUS_READ_I2C_BLOCK
    • smbus 프로토콜을 사용하여 i2c 블럭 읽기 지원
  • I2C_FUNC_SMBUS_WRITE_I2C_BLOCK
    • smbus 프로토콜을 사용하여 i2c 블럭 쓰기 지원
  • I2C_FUNC_SMBUS_HOST_NOTIFY
    • smbus로부터 host notify 기능 사용 지원

 

복합 플래그
  • I2C_FUNC_SMBUS_BYTE
    • 1 바이트 읽기 및 쓰기
  • I2C_FUNC_SMBUS_BYTE_DATA
    • 바이트 데이터 읽기 및 쓰기
  • I2C_FUNC_SMBUS_WORD_DATA
    • 워드 데이터 읽기 및 쓰기
  • I2C_FUNC_SMBUS_BLOCK_DATA
    • 블럭 데이터 읽기 및 쓰기
  • I2C_FUNC_SMBUS_I2C_BLOCK
    • i2c 블럭 읽기 및 쓰기
  • I2C_FUNC_SMBUS_EMUL
    • i2c 전송 방식을 사용하지만 smbus 명령도 에뮬레이션하여 처리한다.

 

다음 그림은 functionality에 사용하는 플래그들과 복합  플래그들을 보여준다.

  • rpi 및 ns2의 경우 I2C_FUNC_I2C 플래그와 I2C_FUNC_SMBUS_EMUL 복합 플래그를 사용한다.

 

Adapter 구현 (i2c vs smbus)

smbus만 지원하는 adapter의 경우 일반적으로 다음과 같은 기능을 지원한다. 예) i2c-piix4 드라이버

  • I2C_FUNC_SMBUS_QUICK
  • I2C_FUNC_SMBUS_BYTE
  • I2C_FUNC_SMBUS_BYTE_DATA
  • I2C_FUNC_SMBUS_WORD_DATA
  • I2C_FUNC_SMBUS_BLOCK_DATA

 

full-I2C 기능을 갖는 adapter의 경우 일반적으로 다음과 같은 기능을 지원한다.

  • I2C_FUNC_I2C
  • I2C_FUNC_SMBUS_EMUL
    • 모든  smbus transaction들을 포함한다.

 

Client Checking

클라이언트가 adapter에 attach를 시도할 때 adapter에서 이 디바이스가 사용하는 기능이 있는지 먼저 체크해야 한다.

static int lm75_detect(...)
{
        (...)
        if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
                                     I2C_FUNC_SMBUS_WORD_DATA))
                goto exit;
        (...)
}

위의 코드 체크에서 통과하는 경우 이제 i2c_smbus_read_byte_data() 함수 및 i2c_smbus_write_byte_data() 함수를 사용할 수 있다는 것을 의미한다.

 

I2C Character Device (i2c-dev)

유저 스페이스에서 i2c adapter를 control할 수 있도록 다음 파일명으로 i2c 캐릭터 디바이스가 노출된다.

  • /dev/i2c-N
    • 예) /dev/i2c-0
  • 또는 /dev/i2c/N
    • 예) /dev/i2c/0

 

i2c 버스에 i2c adapter 디바이스가 등록/해제될 때 다음 함수를 통해 i2c-dev 클래스 디바이스가 생성/해제되며 이 때 i2c 캐릭터 디바이스도 생성/삭제된다.

  • 등록
    • i2cdev_attach_adapter()
  • 등록 해제
    • i2cdev_detach_adapter()

 

참고로 i2c adapter에 대한 클래스 디바이스는 다음과 같이 adater 디바이스 바로 뒤에 생성된다.

  • /sys/devices/…./66080000.i2c/i2c-0/i2c-dev/i2c-0

 

ioctl 지원

생성된 캐릭터 디바이스 파일에서 ioctl() 함수를 통해 다음과 같은 기능이 지원된다.

  • I2C_RETRIES
    • 재시도 수
    • 파라메터: long
  • I2C_TIMEOUT
    • 타임아웃(ms)
    • 10ms 단위로 설정해야 한다.
    • 파라메터: long
  • I2C_SLAVE
    • slave 디바이스 주소를 사용
    • 파라메터: long
  • I2C_SLAVE_FORCE
    • 드라이버에서 사용된다 하더라도 slave 디바이스 주소를 사용
    • 파라메터: long
  • I2C_TENBIT
    • 0인 경우 7비트 주소, 0이 아닌 경우 10비트 주소를 사용한다.
    • 파라메터: long
  • I2C_FUNCS
    • adapter가 지원하는 functionality 값을 알아온다.
    • 파라메터: unsigned long *
  • I2C_RDWR
    • combined 전송
    • 파라메터: i2c_rdwr_ioctl_data 구조체를 사용한다.
  • I2C_PEC
    • 0이 아닌 값으로 설정 시 smbus에서 PEC를 사용한다.
    • 파라메터: long
  • I2C_SMBUS
    • smbus 전송 방식을 사용
    • 파라메터: i2c_smbus_ioctl_data 구조체를 사용한다.

 

I2C 트랜잭션 데이터

i2c_rdwr_ioctl_data 구조체

include/uapi/linux/i2c-dev.h

/* This is the structure as used in the I2C_RDWR ioctl call */                  
struct i2c_rdwr_ioctl_data {                                                    
        struct i2c_msg __user *msgs;    /* pointers to i2c_msgs */              
        __u32 nmsgs;                    /* number of i2c_msgs */                
};
  • *msgs
    • i2c 트랜잭션 메시지
  • nmsgs
    • 메시지 수

 

i2c_msg 구조체

include/uapi/linux/i2c.h

/**
 * struct i2c_msg - an I2C transaction segment beginning with START
 * @addr: Slave address, either seven or ten bits.  When this is a ten
 *      bit address, I2C_M_TEN must be set in @flags and the adapter
 *      must support I2C_FUNC_10BIT_ADDR.
 * @flags: I2C_M_RD is handled by all adapters.  No other flags may be
 *      provided unless the adapter exported the relevant I2C_FUNC_*
 *      flags through i2c_check_functionality().
 * @len: Number of data bytes in @buf being read from or written to the
 *      I2C slave address.  For read transactions where I2C_M_RECV_LEN
 *      is set, the caller guarantees that this buffer can hold up to
 *      32 bytes in addition to the initial length byte sent by the
 *      slave (plus, if used, the SMBus PEC); and this value will be
 *      incremented by the number of block data bytes received.
 * @buf: The buffer into which data is read, or from which it's written.
 *
 * An i2c_msg is the low level representation of one segment of an I2C
 * transaction.  It is visible to drivers in the @i2c_transfer() procedure,
 * to userspace from i2c-dev, and to I2C adapter drivers through the
 * @i2c_adapter.@master_xfer() method.
 *
 * Except when I2C "protocol mangling" is used, all I2C adapters implement
 * the standard rules for I2C transactions.  Each transaction begins with a
 * START.  That is followed by the slave address, and a bit encoding read
 * versus write.  Then follow all the data bytes, possibly including a byte
 * with SMBus PEC.  The transfer terminates with a NAK, or when all those
 * bytes have been transferred and ACKed.  If this is the last message in a
 * group, it is followed by a STOP.  Otherwise it is followed by the next
 * @i2c_msg transaction segment, beginning with a (repeated) START.
 *
 * Alternatively, when the adapter supports I2C_FUNC_PROTOCOL_MANGLING then
 * passing certain @flags may have changed those standard protocol behaviors.
 * Those flags are only for use with broken/nonconforming slaves, and with
 * adapters which are known to support the specific mangling options they
 * need (one or more of IGNORE_NAK, NO_RD_ACK, NOSTART, and REV_DIR_ADDR).
 */
struct i2c_msg {
        __u16 addr;     /* slave address                        */
        __u16 flags;
#define I2C_M_RD                0x0001  /* read data, from slave to master */
                                        /* I2C_M_RD is guaranteed to be 0x0001! */
#define I2C_M_TEN               0x0010  /* this is a ten bit chip address */
#define I2C_M_RECV_LEN          0x0400  /* length will be first received byte */
#define I2C_M_NO_RD_ACK         0x0800  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK        0x1000  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR      0x2000  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART           0x4000  /* if I2C_FUNC_NOSTART */
#define I2C_M_STOP              0x8000  /* if I2C_FUNC_PROTOCOL_MANGLING */
        __u16 len;              /* msg length                           */
        __u8 *buf;              /* pointer to msg data                  */
};
  • addr
    • 타겟 slave 디바이스 주소
  • flags
    • I2C_M_RD
      • slave 디바이스로 부터 읽는다.
    • I2C_M_TEN
      • 10비트 주소 사용
    • I2C_M_RECV_LEN
      • 블럭 데이터 수신할 바이트의 길이
    • 다음 항목들은 Modified 트랜잭션에 사용되는 플래그들로 아래에서 알아본다.
      • I2C_M_NO_RD_ACK
      • I2C_M_IGNORE_NAK
      • I2C_M_REV_DIR_ADDR
      • I2C_M_NOSTART
      • I2C_M_STOP
  • len
    • 메시지 길이
  • *buf
    • 전송 및 수신 시 사용할 메시지 데이터 포인터

 

다음 그림은 유저 스페이스에서 i2c 프로토콜로 전송하기 위해 데이터를 준비한 모습을 보여준다.

 

Modified 트랜잭션

i2c_msg 구조체에서 사용하는 플래그 중 표준 전송과 다른 방법을 사용하기 위해 사용하는 플래그들은 다음과 같다. 특정 디바이스와의 통신에 문제가 있어 이를 해결하기 위한 워크 어라운드 형태로 사용된다.

  • I2C_M_IGNORE_NAK
    • 일반적으로 클라이언트에서 NACK를 보내면 즉시 메시지가 중단된다. 이 플래그를 설정하면 NACK를 ACK로 취급되고 모든 메시지가 전송된다.
  • I2C_M_NO_RD_ACK
    • 메시지를 읽을 때 ACK/NACK를 무시한다.
    • 블럭 데이터를 읽을 때 사용하는 경우가 있다.
  • I2C_M_NOSTART
    • 특정 디바이스에만 드물게 사용한다.
      • 같은 버스에 다른 디바이스와 같이 사용되는 경우 문제가 되므로 단독으로 사용해야 한다.
    • Start condition 또는Repeted Start condition이 시작할 때 주소와 R/W 및 [ACK]가 연달아 처리되는데 이들을 모두 무시하고 처리할 때 사용한다.
    • 보통 combined 메시지를 처리할 때 두 번째 메시지에 이 플래그를 사용한다.
      • 예)  S Addr Rd [A] [Data] NA S Addr Wr [A] Data [A] P —>
        • 플래그 적용 시 -> S Addr Rd [A] [Data] NA Data [A] P
  • I2C_M_REV_DIR_ADDR
    • 주소 다음에 사용되는 R/W 플래그를 reverse한다.
      • 예) S Addr Wr [A] Data [A] Data [A] … [A] Data [A] P —>
        • 플래그 적용 시 -> S Addr Rd [A] Data [A] Data [A] … [A] Data [A] P —>
  • I2C_M_STOP
    • 메시지의 완료 시 마다 항상 P를 강제로 처리한다.
      • 그 후 다시 메시지를 처리하기 위해 Start condition을 보내기 위해 사용한다.
      • combination 메시지 사이에 항상 P를 강제로 처리한다.

 

SMBUS 트랜젝션 데이터

i2c_smbus_ioctl_data 구조체

include/uapi/linux/i2c-dev.h

/* This is the structure as used in the I2C_SMBUS ioctl call */                 
struct i2c_smbus_ioctl_data {                                                   
        __u8 read_write;                                                        
        __u8 command;                                                           
        __u32 size;                                                             
        union i2c_smbus_data __user *data;                                      
};
  • read_write
    • 읽기 = I2C_SMBUS_READ(1)
    • 쓰기 = I2C_SMBUS_WRITE(0)
  • command
    • 명령 바이트
  • size
    • 사이즈
  • *data
    • smbus 트랜젝션 메시지

 

i2c_smbus_data 구조체
/*
 * Data for SMBus Messages
 */
#define I2C_SMBUS_BLOCK_MAX     32      /* As specified in SMBus standard */
union i2c_smbus_data {
        __u8 byte;
        __u16 word;
        __u8 block[I2C_SMBUS_BLOCK_MAX + 2]; /* block[0] is used for length */
                               /* and one more for user-space compatibility */
};
  • byte
    • 바이트 데이터
  • word
    • 워드 데이터
  • block[34]
    • 블럭 데이터
    • block[0] -> 길이
    • block[1] ~ block[33]까지 최대 32바이트 데이터

 

다음 그림은 유저 스페이스에서 smbus 프로토콜로 전송하기 위해 데이터를 준비한 모습을 보여준다.

 

Functionality 체크

아래 코드와 같이 유저 스페이스에서 캐릭터 디바이스를 통해 접근하는 경우에도 i2c functionality 체크를 수행하고 사용해야 한다.

int file;
if (file = open("/dev/i2c-0", O_RDWR) < 0) {
        /* Some kind of error handling */
        exit(1);
}
if (ioctl(file, I2C_FUNCS, &funcs) < 0) {
        /* Some kind of error handling */
        exit(1);
}
if (!(funcs & I2C_FUNC_SMBUS_QUICK)) {
        /* Oops, the needed functionality (SMBus write_quick function) is
           not available! */
        exit(1);
}

 

관련  I2C Core API들

아답터 관련

  • i2c_verify_adapter()
  • i2c_add_adapter()
  • i2c_add_numbered_adapter()
  • i2c_del_adapter()
  • i2c_parse_fw_timings()
  • i2c_get_adapter()
  • i2c_put_adapter()
  • of_find_i2c_adapter_by_node()
  • of_get_i2c_adapter_by_node()

클라이언트 관련

  • i2c_match_id()
  • i2c_verify_client()
  • i2c_new_device()
  • i2c_unregister_device()
  • i2c_new_dummy()
  • i2c_new_secondary_device()
  • i2c_for_each_dev()
  • i2c_register_driver()
  • i2c_del_driver()
  • i2c_use_client()
  • i2c_clients_command()
  • i2c_new_probed_device()
  • i2c_acpi_match_device()
  • i2c_acpi_register_devices()
  • i2c_acpi_find_bus_speed()
  • i2c_acpi_new_device()
  • of_find_i2c_device_by_node()
  • i2c_of_match_device()
  • i2c_slave_register()
  • i2c_slave_unregister()
  • i2c_detect_slave_mode()

이벤트 관련

  • i2c_handle_smbus_host_notify()
  • i2c_setup_smbus_alert()
  • i2c_handle_smbus_alert()

전송 관련

1) i2c 전송 프로토콜
  • i2c_transfer()
  • i2c_master_send()
  • i2c_master_recv()
  • i2c_probe_func_quick_read()
  • i2c_check_7bit_addr_validity_strict()
2) smbus 전송 프로토콜
  • i2c_smbus_read_byte()
  • i2c_smbus_write_byte()
  • i2c_smbus_read_byte_data()
  • i2c_smbus_write_byte_data()
  • i2c_smbus_read_word_data()
  • i2c_smbus_write_word_data()
  • i2c_smbus_read_block_data()
  • i2c_smbus_write_block_data()
  • i2c_smbus_read_i2c_block_data()
  • i2c_smbus_write_i2c_block_data()
  • i2c_smbus_xfer()
  • i2c_smbus_read_i2c_block_data_or_emulated()
3) 버스 리커버리 관련
  • i2c_generic_recovery()
  • i2c_generic_scl_recovery()
  • i2c_generic_gpio_recovery()
  • i2c_recover_bus()

 

참고

 

 

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다