I2C Subsystem -4- (I2C-Mux)

 

I2C-Mux Core

다음 그림은 i2c 버스에 연결된 i2c 멀티플레서를 블럭 다이아그램으로 표현하였다.

 

i2c_mux_core 구조체

include/linux/i2c-mux.h

struct i2c_mux_core {                                                           
        struct i2c_adapter *parent;                                             
        struct device *dev;                                                     
        unsigned int mux_locked:1;                                              
        unsigned int arbitrator:1;                                              
        unsigned int gate:1;                                                    
                                                                                
        void *priv;                                                             
                                                                                
        int (*select)(struct i2c_mux_core *, u32 chan_id);                      
        int (*deselect)(struct i2c_mux_core *, u32 chan_id);                    
                                                                                
        int num_adapters;                                                       
        int max_adapters;                                                       
        struct i2c_adapter *adapter[0];                                         
};
  • *parent
    • 부모 adapter를 가리킨다.
  • *dev
    • 현 멀티플레서 디바이스를 가리킨다.
  • mux_locked
    • mux-lock 오퍼레이션을 선택사용하게 한다.
      • 1인 경우 i2c_mux_lock_ops를 사용한다.
        • I2C_LOCK_ROOT_ADAPTER 플래그를 사용한 경우에만 adapter->lock_ops->lock_bus() 후크 함수를 호출하게 한다.
      • 0인 경우 &i2c_parent_lock_ops를 사용한다.
        • 항상 adapter->lock_ops->lock_bus() 후크 함수를 호출하게 한다.
    • 다음 드라이버들이 지원한다.
      • drivers/iio/gyro/mpu3050-i2c
      • drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c
      • drivers/i2c/muxes/i2c-mux-ltc4306.c
      • drivers/media/dvb-frontends/lgdt3306a.c
      • drivers/media/dvb-frontends/si2168.c
      • drivers/media/dvb-frontends/rtl2832.c
  • arbitrator
    • mux-arbitrator 지원하는 경우 1
    • i2c 멀티플렉서가 1:N 형태로 버스를 확장시키는데 반해 N:1 형태로 버스를 합치는 성격의 멀티플렉서이다.
      • 예) pca9541 칩: 2개의 i2c 버스를 1개의 i2c 버스로 합치는 기능을 제공한다.
    • 디바이스 트리의 i2c-mux 노드의 서브 노드로 i2c-arb 노드가 있는 경우이다.
    • 다음 두 드라이버가 지원한다.
      • drivers/i2c/muxes/i2c-mux-pca9541.c
      • drivers/i2c/muxes/i2c-arb-gpio-challenge.c
  • gate
    • mux-gate 지원하는 경우 1
    • 디바이스 트리의 i2c-mux 노드의 서브 노드로 i2c-gate 노드가 있는 경우이다.
    • 다음 두 드라이버가 지원한다.
      • drivers/iio/gyro/mpu3050-i2c
      • drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c
  • *priv
    • 사용자 private 데이터가 있을 때 사용한다.
  • (*select)
    • i2c-mux에 있는 i2c 버스를 선택할 때 호출되는 후크이다.
  • (*deselect)
    • i2c-mux에 있는 i2c 버스의 사용이 완료한 후 호출되는 후크이다.
  • num_adapters
    • adpater 수
  • max_adapters
    • 최대 adapter 수
  • *adapter[0]
    • 최대 adapter 수 만큼의 adapter 포인터 배열이 할당될 위치이다.

 

i2c_mux_priv 구조체

drivers/i2c/muxes/i2c-mux.c

/* multiplexer per channel data */                                              
struct i2c_mux_priv {                                                           
        struct i2c_adapter adap;                                                
        struct i2c_algorithm algo;                                              
        struct i2c_mux_core *muxc;                                              
        u32 chan_id;                                                            
};
  • adap
    • i2c_adapter가 임베드되어 있다.
  • algo
    • i2c_algorithm이 임베드되어 있다.
  • *muxc
    • i2c mux를 가리킨다.
  • chan_id
    • 채널 번호

 

i2c-mux 할당

i2c_mux_alloc()

drivers/i2c/muxes/i2c-mux.c

struct i2c_mux_core *i2c_mux_alloc(struct i2c_adapter *parent,                  
                                   struct device *dev, int max_adapters,        
                                   int sizeof_priv, u32 flags,                  
                                   int (*select)(struct i2c_mux_core *, u32),   
                                   int (*deselect)(struct i2c_mux_core *, u32)) 
{                                                                               
        struct i2c_mux_core *muxc;                                              
                                                                                
        muxc = devm_kzalloc(dev, sizeof(*muxc)                                  
                            + max_adapters * sizeof(muxc->adapter[0])           
                            + sizeof_priv, GFP_KERNEL);                         
        if (!muxc)                                                              
                return NULL;                                                    
        if (sizeof_priv)                                                        
                muxc->priv = &muxc->adapter[max_adapters];                      
                                                                                
        muxc->parent = parent;                                                  
        muxc->dev = dev;                                                        
        if (flags & I2C_MUX_LOCKED)                                             
                muxc->mux_locked = true;                                        
        if (flags & I2C_MUX_ARBITRATOR)                                         
                muxc->arbitrator = true;                                        
        if (flags & I2C_MUX_GATE)                                               
                muxc->gate = true;                                              
        muxc->select = select;                                                  
        muxc->deselect = deselect;                                              
        muxc->max_adapters = max_adapters;                                      
                                                                                
        return muxc;                                                            
}                                                                               
EXPORT_SYMBOL_GPL(i2c_mux_alloc);

i2c_mux_core를 할당한다.

  • 코드 라인 9~13에서 i2c_mux_core 구조체 + adapter 포인터 수 + private 데이터 크기 만큼의 공간을 할당한다.
  • 코드 라인 15~16에서 muxc의 priv 포인터가 할당한 private 데이터 공간을 가리키게한다.
  • 코드 라인 18~19에서 부모 i2c adapter와 디바이스를 지정한다.
  • 코드 라인 20~25에서 플래그에 해당하는 muxc 멤버(locked, arbtrator, gate) 변수를 설정한다.
  • 코드 라인 26~28에서 (*select) 및 (*deselect) 후크 함수를 지정하고, max_adapters 까지 설정한다.

 

다음 그림은 8개의 adapter를 위한 i2c_mux_core가 준비됨을 알 수 있다.

 

 

mux에 adapter 추가

i2c_mux_add_adapter()

drivers/i2c/muxes/i2c-mux.c -1/3-

int i2c_mux_add_adapter(struct i2c_mux_core *muxc,                              
                        u32 force_nr, u32 chan_id,                              
                        unsigned int class)                                     
{                                                                               
        struct i2c_adapter *parent = muxc->parent;                              
        struct i2c_mux_priv *priv;                                              
        char symlink_name[20];                                                  
        int ret;                                                                
                                                                                
        if (muxc->num_adapters >= muxc->max_adapters) {                         
                dev_err(muxc->dev, "No room for more i2c-mux adapters\n");      
                return -EINVAL;                                                 
        }                                                                       
                                                                                
        priv = kzalloc(sizeof(*priv), GFP_KERNEL);                              
        if (!priv)                                                              
                return -ENOMEM;                                                 
                                                                                
        /* Set up private adapter data */                                       
        priv->muxc = muxc;                                                      
        priv->chan_id = chan_id;                                                
                                                                                
        /* Need to do algo dynamically because we don't know ahead              
         * of time what sort of physical adapter we'll be dealing with.         
         */                                                                     
        if (parent->algo->master_xfer) {                                        
                if (muxc->mux_locked)                                           
                        priv->algo.master_xfer = i2c_mux_master_xfer;           
                else                                                            
                        priv->algo.master_xfer = __i2c_mux_master_xfer;         
        }                                                                       
        if (parent->algo->smbus_xfer) {                                         
                if (muxc->mux_locked)                                           
                        priv->algo.smbus_xfer = i2c_mux_smbus_xfer;             
                else                                                            
                        priv->algo.smbus_xfer = __i2c_mux_smbus_xfer;           
        }                                                                       
        priv->algo.functionality = i2c_mux_functionality;                       
                                                                                
        /* Now fill out new adapter structure */                                
        snprintf(priv->adap.name, sizeof(priv->adap.name),                      
                 "i2c-%d-mux (chan_id %d)", i2c_adapter_id(parent), chan_id);   
        priv->adap.owner = THIS_MODULE;                                         
        priv->adap.algo = &priv->algo;                                          
        priv->adap.algo_data = priv;                                            
        priv->adap.dev.parent = &parent->dev;                                   
        priv->adap.retries = parent->retries;                                   
        priv->adap.timeout = parent->timeout;                                   
        priv->adap.quirks = parent->quirks;                                     
        if (muxc->mux_locked)                                                   
                priv->adap.lock_ops = &i2c_mux_lock_ops;                        
        else                                                                    
                priv->adap.lock_ops = &i2c_parent_lock_ops;                     
                                                                                
        /* Sanity check on class */                                             
        if (i2c_mux_parent_classes(parent) & class)                             
                dev_err(&parent->dev,                                           
                        "Segment %d behind mux can't share classes with ancestors\n",
                        chan_id);                                               
        else                                                                    
                priv->adap.class = class;

adapter를 추가한 후 i2c_mux_core에 연결한다.

  • 코드 라인 10~13에서 이미 최대 adapter 수 만큼 등록한 경우 추가할 수 없다.
  • 코드 라인 15~21에서 i2c_mux_priv를 할당하고 muxc 및 chan_id를 지정한다.
  • 코드 라인 26~31에서 부모 i2c adapter의 i2c 전송 후크가 있는 경우 현재 생성할 adapter의 i2c 전송 후크에 select가 포함된 부모 adapter로의 i2c 전송 함수를 지정한다. mux_locked 여부에 따라 2개가 준비되었다.
  • 코드 라인 32~38에서부모 i2c adapter의 smbus 전송 후크가 있는 경우 현재 생성할 adapter의 smbus 전송 후크에 select가 포함된 부모 adapter로의 smbus 전송 함수를 지정한다. mux_locked 여부에 따라 2개가 준비되었다.
  • 코드 라인 39에서 부모 i2c adapter가 사용하는 functionality를 지정한다.
  • 코드 라인44~50에서 할당한 adapter의 멤버를 설정한다. retries, timeout 및 quirks는 부모 adapter가 사용하는 값을 그대로 적용한다.
  • 코드 라인 51~54에서 할당한 adapter에서 사용할 lock operation은 부모 adapter가 사용하는 lock 버스를 사용한다. mux_locked 여부에 따라 2개가 준비되었다.
  • 코드 라인 57~62에서 adapter의 클래스를 지정한다. 만일 클래스와 부모 adapter의 클래스가 동일한 경우 에러 메시지를 출력한다.

 

drivers/i2c/muxes/i2c-mux.c -2/3-

.       /*                                                                      
         * Try to populate the mux adapter's of_node, expands to                
         * nothing if !CONFIG_OF.                                               
         */                                                                     
        if (muxc->dev->of_node) {                                               
                struct device_node *dev_node = muxc->dev->of_node;              
                struct device_node *mux_node, *child = NULL;                    
                u32 reg;                                                        
                                                                                
                if (muxc->arbitrator)                                           
                        mux_node = of_get_child_by_name(dev_node, "i2c-arb");   
                else if (muxc->gate)                                            
                        mux_node = of_get_child_by_name(dev_node, "i2c-gate");  
                else                                                            
                        mux_node = of_get_child_by_name(dev_node, "i2c-mux");   
                                                                                
                if (mux_node) {                                                 
                        /* A "reg" property indicates an old-style DT entry */  
                        if (!of_property_read_u32(mux_node, "reg", &reg)) {     
                                of_node_put(mux_node);                          
                                mux_node = NULL;                                
                        }                                                       
                }                                                               
                                                                                
                if (!mux_node)                                                  
                        mux_node = of_node_get(dev_node);                       
                else if (muxc->arbitrator || muxc->gate)                        
                        child = of_node_get(mux_node);                          
                                                                                
                if (!child) {                                                   
                        for_each_child_of_node(mux_node, child) {               
                                ret = of_property_read_u32(child, "reg", &reg); 
                                if (ret)                                        
                                        continue;                               
                                if (chan_id == reg)                             
                                        break;                                  
                        }                                                       
                }                                                               
                                                                                
                priv->adap.dev.of_node = child;                                 
                of_node_put(mux_node);                                          
        }
  • 코드 라인 5~15에서 디바이스 트리에 mux 디바이스 노드가 있는 경우 mux의 arbitratory, gate 유무에 따라 mux 디바이스의 서브 노드에서 mux 노드를 알아온다. 각각 mux 디바이스 노드의 서브 노드에 “i2c-arb”, “i2c-gate”, “i2c-mux” 이름으로 노드가 있으면 mux_node로 알아온다.
  • 코드 라인 17~23에서 mux_node에 “reg” 속성이 존재하면 old-style DT 이다. 이러한 경우 mux_node를 null로 설정한다.
    • arbitratory 및 gate 타입의 경우 i2c 버스가 1개이다. 따라서 adapter 번호가 지정될 필요 없다.
  • 코드 라인 25~26에서 mux_node가 null인 경우 디바이스 노드를 mux_node로 사용한다.
  • 코드 라인 27~28에서 arbitrator 또는 gate 타입의 경우 mux_node를 child에 대입한다.
  • 코드 라인 30~38에서 arbitrator, gate에 대한 노드가 없는 경우 mux 노드의 child 노드의 “reg” 속성 값을 읽어서 chan_id로 사용한다.
  • 코드 라인 40~41에서 adapter의 노드를 지정한다.

 

drivers/i2c/muxes/i2c-mux.c -3/3-

        /*                                                                      
         * Associate the mux channel with an ACPI node.                         
         */                                                                     
        if (has_acpi_companion(muxc->dev))                                      
                acpi_preset_companion(&priv->adap.dev,                          
                                      ACPI_COMPANION(muxc->dev),                
                                      chan_id);                                 
                                                                                
        if (force_nr) {                                                         
                priv->adap.nr = force_nr;                                       
                ret = i2c_add_numbered_adapter(&priv->adap);                    
                if (ret < 0) {                                                  
                        dev_err(&parent->dev,                                   
                                "failed to add mux-adapter %u as bus %u (error=%d)\n",
                                chan_id, force_nr, ret);                        
                        goto err_free_priv;                                     
                }                                                               
        } else {                                                                
                ret = i2c_add_adapter(&priv->adap);                             
                if (ret < 0) {                                                  
                        dev_err(&parent->dev,                                   
                                "failed to add mux-adapter %u (error=%d)\n",    
                                chan_id, ret);                                  
                        goto err_free_priv;                                     
                }                                                               
        }                                                                       
                                                                                
        WARN(sysfs_create_link(&priv->adap.dev.kobj, &muxc->dev->kobj,          
                               "mux_device"),                                   
             "can't create symlink to mux device\n");                           
                                                                                
        snprintf(symlink_name, sizeof(symlink_name), "channel-%u", chan_id);    
        WARN(sysfs_create_link(&muxc->dev->kobj, &priv->adap.dev.kobj,          
                               symlink_name),                                   
             "can't create symlink for channel %u\n", chan_id);                 
        dev_info(&parent->dev, "Added multiplexed i2c bus %d\n",                
                 i2c_adapter_id(&priv->adap));                                  
                                                                                
        muxc->adapter[muxc->num_adapters++] = &priv->adap;                      
        return 0;                                                               
                                                                                
err_free_priv:                                                                  
        kfree(priv);                                                            
        return ret;                                                             
}                                                                               
EXPORT_SYMBOL_GPL(i2c_mux_add_adapter);
  • 코드 라인 4~7에서 acpi 노드와 chan_id를 연결한다.
  • 코드 라인 9~17에서 adapter 번호를 지정하여 adapter를 추가한다.
  • 코드 라인 18~26에서 adapter 번호를 자동 할당하여 adapter를 추가한다.
  • 코드 라인 28~31에서 adapter 디렉토리에 mux_device 심볼링크를 생성하여 mux 디렉토리를 가리킨다.
    • /sys/devices/platform/660a0000.i2c/i2c-0/0-0070/i2c-1/mux_device ->  /sys/devices/platform/660a0000.i2c/i2c-0/0-0070
  • 코드 라인 33~36에서 mux 디렉토리에 “channel-N” 라는 심볼링크를 생성하여 adapter 디렉토리를 가리킨다.
    • /sys/devices/platform/660a0000.i2c/i2c-0/0-0070/channle-0 ->  /sys/devices/platform/660a0000.i2c/i2c-0/0-0070/i2c-1
  • 코드 라인 i2c_mux_core의 adapter 포인터가 생성된 i2c_mux_priv의 adapter를 가리키도록 한다.

 

다음 그림은 i2c_mux_add_adapter() 함수가 4번 호출되어 4개의 i2c 채널(i2c_mux_priv)이 생성되고 그 내부에 adapter와 algorithm이 있는 것을 확인할 수 있다.

 

다음 그림과 같이 i2c-mux의 경우 child 노드의 파란색 reg 속성 값이 adapter 번호가 된다. 즉 i2c 버스 번호가 된다. 그러나 적색으로된 i2c-arb 또는 i2c-gate 노드의 경우 i2c 버스가 하나이므로 reg 속성이 주어지지 않는 것을 알 수 있다.

 

채널 셀렉트 및 전송

전송 Core

다음 그림은 i2c_transfer() 함수를 사용 시 버스 락 처리 및 i2c-mux의 채널 선택과 전송을 수행하는 과정을 보여준다.

 

i2c-mux core 전송

__i2c_mux_master_xfer()

drivers/i2c/i2c-mux.c

static int __i2c_mux_master_xfer(struct i2c_adapter *adap,                      
                                 struct i2c_msg msgs[], int num)                
{                                                                               
        struct i2c_mux_priv *priv = adap->algo_data;                            
        struct i2c_mux_core *muxc = priv->muxc;                                 
        struct i2c_adapter *parent = muxc->parent;                              
        int ret;                                                                
                                                                                
        /* Switch to the right mux port and perform the transfer. */            
                                                                                
        ret = muxc->select(muxc, priv->chan_id);                                
        if (ret >= 0)                                                           
                ret = __i2c_transfer(parent, msgs, num);                        
        if (muxc->deselect)                                                     
                muxc->deselect(muxc, priv->chan_id);                            
                                                                                
        return ret;                                                             
}

adapter에 해당하는 채널을 선택한 채로 부모 adapter를 통해 메시지를 전송한다. 사용이 완료되면 deselect 하여야 한다.

 

i2c-mux 버스 락 core

i2c_parent_lock_bus()

drivers/i2c/i2c-mux.c

static void i2c_parent_lock_bus(struct i2c_adapter *adapter,                    
                                unsigned int flags)                             
{                                                                               
        struct i2c_mux_priv *priv = adapter->algo_data;                         
        struct i2c_adapter *parent = priv->muxc->parent;                        
                                                                                
        rt_mutex_lock(&parent->mux_lock);                                       
        i2c_lock_bus(parent, flags);                                            
}

mux 뮤텍스 락을 건채로 부모 adapter 버스 락을 건다.

 

i2c_lock_bus()

include/linux/i2c.h

/**                                                                             
 * i2c_lock_bus - Get exclusive access to an I2C bus segment                    
 * @adapter: Target I2C bus segment                                             
 * @flags: I2C_LOCK_ROOT_ADAPTER locks the root i2c adapter, I2C_LOCK_SEGMENT   
 *      locks only this branch in the adapter tree                              
 */                                                                             
static inline void                                                              
i2c_lock_bus(struct i2c_adapter *adapter, unsigned int flags)                   
{                                                                               
        adapter->lock_ops->lock_bus(adapter, flags);                            
}

adapter에 해당하는 버스 락 오퍼레이션을 수행한다.

 

채널 선택 (for 954x chip)

pca954x_select_chan()

drivers/i2c/muxes/i2c-mux-pca954x.c

static int pca954x_select_chan(struct i2c_mux_core *muxc, u32 chan)             
{                                                                               
        struct pca954x *data = i2c_mux_priv(muxc);                              
        struct i2c_client *client = data->client;                               
        const struct chip_desc *chip = data->chip;                              
        u8 regval;                                                              
        int ret = 0;                                                            
                                                                                
        /* we make switches look like muxes, not sure how to be smarter */      
        if (chip->muxtype == pca954x_ismux)                                     
                regval = chan | chip->enable;                                   
        else                                                                    
                regval = 1 << chan;                                             
                                                                                
        /* Only select the channel if its different from the last channel */    
        if (data->last_chan != regval) {                                        
                ret = pca954x_reg_write(muxc->parent, client, regval);          
                data->last_chan = ret < 0 ? 0 : regval;                         
        }                                                                       
                                                                                
        return ret;                                                             
}

i2c-mux의 요청받은 채널 번호에 해당하는 i2c 버스를 HW 선택한다.

  • 채널을 선택하기 위해서는 부모 i2c adapter를 통해 i2c-mux 장치에게 1바이트의 채널 선택 value를 전송해야 한다. 즉 일반적인 1 바이트의 i2c 메시지를 전송한다.
  • pca954x 시리즈는 i2c 멀티플렉서와 i2c 스위치 두 타입이 있다. 타입에 따라 전송하는 값은 다음과 같다.
    • i2c 멀티 플렉서
      • 채널 번호 + enable 비트
      • enable 비트는 다음과 같이 결정된다.
        • 2채널 멀티플렉서 -> bit1
        • 4채널 멀티플렉서 -> bit2
        • 8채널 멀티플렉서 -> bit3
    • i2c 스위치
      • 각 비트가 하나의 채널을 담당한다.
      • 하나의 채널만 선택하려면
        • 1 << 채널 번호

 

pca954x_reg_write()

drivers/i2c/muxes/i2c-mux-pca954x.c

/* Write to mux register. Don't use i2c_transfer()/i2c_smbus_xfer()             
   for this as they will try to lock adapter a second time */                   
static int pca954x_reg_write(struct i2c_adapter *adap,                          
                             struct i2c_client *client, u8 val)                 
{                                                                               
        int ret = -ENODEV;                                                      
                                                                                
        if (adap->algo->master_xfer) {                                          
                struct i2c_msg msg;                                             
                char buf[1];                                                    
                                                                                
                msg.addr = client->addr;                                        
                msg.flags = 0;                                                  
                msg.len = 1;                                                    
                buf[0] = val;                                                   
                msg.buf = buf;                                                  
                ret = __i2c_transfer(adap, &msg, 1);                            
                                                                                
                if (ret >= 0 && ret != 1)                                       
                        ret = -EREMOTEIO;                                       
        } else {                                                                
                union i2c_smbus_data data;                                      
                ret = adap->algo->smbus_xfer(adap, client->addr,                
                                             client->flags,                     
                                             I2C_SMBUS_WRITE,                   
                                             val, I2C_SMBUS_BYTE, &data);       
        }                                                                       
                                                                                
        return ret;                                                             
}

i2c 전송 후크가 있는 경우 i2c 버스에 인자로 요청한 1 바이트 값을 전송한다. i2c 전송 후크가 없는 경우 smbus에 인자로 요청한 1 바이트 값을 전송한다.

 

필립스 i2c 멀티플렉서/스위치 pca954x 시리즈

pca9541x 제품 종류

가장 많이 사용되는 필립스 반도체의 i2c-mux 제품들 중 954X 시리즈를 기능별로 구분할 수 있는 테이블이다.

 

i2c-스위치 pca9549 블록 다이어그램

  • 8개 채널을 각각 선택할 수 있다.
  • hw reset 핀이 지원된다.
  • a0~a2 핀을 사용하여 주소를 설정할 수 있다.

 

주소 설정

각 제품마다 하위 2~4개의 비트를 설정하여 주소를 설정할 수 있다.

  • 예) pca9545a의 경우 0x70 ~ 0x73까지 4개의 주소를 설정하여 사용할 수 있다.

 

채널 선택(select)

다음과 같이 채널을 선택한다.

  • 4채널 i2c 멀티 플렉서 pca9544a의 경우
    • 채널 선택: bit0 ~ bit1
    • enable bit: bit2
    • 예) 0x6 -> 채널 2 선택
  • 2채널 i2c 스위치 pca9543a의 경우
    • 채널 선택: bit0 ~ bit3
    • 예) 0x3 -> 채널 0과 채널 1이 동시 선택

 

다음 그림은 some device와 통신하기 위해 i2c-mux 칩인 pca9545a 디바이스에 해당하는 주소 0x72로 0x8 값을 전송하여 #3번 채널을 선택하는 모습을 보여준다.

 

참고

 

I2C Subsystem -3- (Transfer)

<kernel v4.14>

전송에 관여하는 다음 항목들에 대해 알아본다.

  • I2C Quirks
  • I2C Bus Recovery
  • I2C Bus Lock
  • I2C Transfer

 

I2C Quirks

Kernel v4.1-rc1에서 추가된 기능으로 특정 i2c 호스트 컨트롤러들에서 HW 전송 제한이 걸려 있는 경우 이에 대한 전송 제한 체크를 수행할 수 있도록 하였다.

 

I2C Quirks 체크

i2c_check_for_quirks()

drivers/i2c/base/i2c-core-base.c

static int i2c_check_for_quirks(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{                                                                               
        const struct i2c_adapter_quirks *q = adap->quirks;                      
        int max_num = q->max_num_msgs, i;                                       
        bool do_len_check = true;                                               
                                                                                
        if (q->flags & I2C_AQ_COMB) {                                           
                max_num = 2;                                                    
                                                                                
                /* special checks for combined messages */                      
                if (num == 2) {                                                 
                        if (q->flags & I2C_AQ_COMB_WRITE_FIRST && msgs[0].flags & I2C_M_RD)
                                return i2c_quirk_error(adap, &msgs[0], "1st comb msg must be write");
                                                                                
                        if (q->flags & I2C_AQ_COMB_READ_SECOND && !(msgs[1].flags & I2C_M_RD))
                                return i2c_quirk_error(adap, &msgs[1], "2nd comb msg must be read");
                                                                                
                        if (q->flags & I2C_AQ_COMB_SAME_ADDR && msgs[0].addr != msgs[1].addr)
                                return i2c_quirk_error(adap, &msgs[0], "comb msg only to same addr");
                                                                                
                        if (i2c_quirk_exceeded(msgs[0].len, q->max_comb_1st_msg_len))
                                return i2c_quirk_error(adap, &msgs[0], "msg too long");
                                                                                
                        if (i2c_quirk_exceeded(msgs[1].len, q->max_comb_2nd_msg_len))
                                return i2c_quirk_error(adap, &msgs[1], "msg too long");
                                                                                
                        do_len_check = false;                                   
                }                                                               
        }                                                                       
                                                                                
        if (i2c_quirk_exceeded(num, max_num))                                   
                return i2c_quirk_error(adap, &msgs[0], "too many messages");    
                                                                                
        for (i = 0; i < num; i++) {                                             
                u16 len = msgs[i].len;                                          
                                                                                
                if (msgs[i].flags & I2C_M_RD) {                                 
                        if (do_len_check && i2c_quirk_exceeded(len, q->max_read_len))
                                return i2c_quirk_error(adap, &msgs[i], "msg too long");
                } else {                                                        
                        if (do_len_check && i2c_quirk_exceeded(len, q->max_write_len))
                                return i2c_quirk_error(adap, &msgs[i], "msg too long");
                }                                                               
        }                                                                       
                                                                                
        return 0;                                                               
}

송/수신할 메시지에 대해 메시지 수 및 길이 제한을 체크한다. 성공 시 0을 반환한다.

  • 코드 라인  7~29에서 I2C_AQ_COMB 플래그를 사용한 combined 메시지인 경우 2개의 메시지만을 송/수신할 수 있는데 특별히 다음과 같은 조건인 경우 에러 메시지를 출력하고 에러를 반환한다.
    • 첫 번째 메시지가 read인 경우
    • 두 번째 메시지가 read가 아닌 경우
    • 첫 번째와 두 번째 메시지의 타겟 주소가 동일하지 않는 경우
    • 첫 번째 메시지의 길이가 quirks에서 지정한 첫 번째 메시지보다 큰 경우
    • 두 번째 메시지의 길이가 quirks에서 지정한 두 번째 메시지보다 큰 경우
  • 코드 라인 31~32에서 인자로 요청한 일반 메시지 수가 quirks에서 지정한 메시지 수 제한을 초과하는 경우 에러 메시지를 출력하고 에러를 반환한다.
    • 단 quirks에 메시지 수가 지정되지 않은 경우 제한 초과 체크하지 않는다.
    • #define i2c_quirk_exceeded(val, quirk)    ((quirk) && ((val) > (quirk)))
  • 코드 라인 34~44에서 모든 메시지 수 만큼 순회하며 read 및 write 메시지의 경우 quirks에서 제한한 길이를 초과하는 경우 에러 메시지를 출력하고 에러를 반환한다.

 

다음 그림은 quirks 체크를 수행하는 모습을 보여준다.

 

i2c_adapter_quirks 구조체

/**                                                                             
 * struct i2c_adapter_quirks - describe flaws of an i2c adapter                 
 * @flags: see I2C_AQ_* for possible flags and read below                       
 * @max_num_msgs: maximum number of messages per transfer                       
 * @max_write_len: maximum length of a write message                            
 * @max_read_len: maximum length of a read message                              
 * @max_comb_1st_msg_len: maximum length of the first msg in a combined message 
 * @max_comb_2nd_msg_len: maximum length of the second msg in a combined message
 *                                                                              
 * Note about combined messages: Some I2C controllers can only send one message 
 * per transfer, plus something called combined message or write-then-read.     
 * This is (usually) a small write message followed by a read message and       
 * barely enough to access register based devices like EEPROMs. There is a flag 
 * to support this mode. It implies max_num_msg = 2 and does the length checks  
 * with max_comb_*_len because combined message mode usually has its own        
 * limitations. Because of HW implementations, some controllers can actually do 
 * write-then-anything or other variants. To support that, write-then-read has  
 * been broken out into smaller bits like write-first and read-second which can 
 * be combined as needed.                                                       
 */
struct i2c_adapter_quirks {                                                     
        u64 flags;                                                              
        int max_num_msgs;                                                       
        u16 max_write_len;                                                      
        u16 max_read_len;                                                       
        u16 max_comb_1st_msg_len;                                               
        u16 max_comb_2nd_msg_len;                                               
};
  • flags
    • I2C_AQ_COMB(1)
      • 메시지의 전송을 2개로 제한하고, max_comb_1st_msg_len 및 max_comb_2nd_msg_len 길이 체크를 사용한다.
    • I2C_AQ_COMB_WRITE_FIRST(2)
      • 첫 번째 combined 메시지를 먼저 write 해야한다.
    • I2C_AQ_COMB_READ_SECOND(4)
      • 두 번째 combined 메시지를 read 해야 한다.
    • I2C_AQ_COMB_SAME_ADDR(8)
      • 두 개의 메시지의 타겟 주소가 반드시 동일해야 한다.
    • I2C_AQ_COMB_WRITE_THEN_READ(15)
      • 같은 디바이스에 주소로 한 번 쓴 후, 다시 한 번 읽는다.
      • 위의 4개 플래그를 동시에 사용하여 편리하게 사용할 수 있도록 하였다.
    • I2C_AQ_NO_CLK_STRETCH(16)
      • clock stretching을 지원하지 않는다.
  • max_num_msgs
    • 전송할 메시지의 최대 수
  • max_write_len
    • 최대 전송 길이
  • max_read_len
    • 최대 수신 길이
  • max_comb_1st_msg_len
    • 첫 번째 메시지 최대 길이
  • max_comb_2nd_msg_len
    • 두 번째 메시지 최대 길이

 

I2C Bus Recovery

I2C 버스 리커버리는 kernel v3.10-rc1에서 소개되었다.

 

Bus Clear

i2c 프로토콜 규격의 섹션 3.1.16에서 “Bus clear”라는 항목이 구현되었다.

  • 드물게 클록(SCL) 또는 데이터 라인(SDA)가 고정되어 움직이지 않는 경우가 있다. 이러한 경우 다음과 같이 처리한다.
    • A) 클록 (SCL)이 LOW로 고정된 경우
      • I2C 디바이스에 HW 리셋 신호를 사용하여 버스를 리셋한다.
      • 만일 I2C 디바이스에 HW 리셋 입력이 없으면 디바이스 전원을 껏다 켠다.
    • B) 데이터 라인 (SDA)이 LOW로 고정된 경우
      • 버스 마스터는 9 개의 클럭 펄스를 전송해야 한다.
      • 버스를 LOW로 유지 한 장치는 9 클럭 내에서 이에 반응하여 해제해야 한다.
      • 여전히 동작하지 않는 경우 A)와 같이 처리한다.

 

i2c 프로토콜 규격 – 섹션 3.1.16에서 “Bus clear” 원문
3.1.16 Bus clear

In the unlikely event where the clock (SCL) is stuck LOW, the preferential procedure is to reset the bus using the HW reset signal if your I2C devices have HW reset inputs. If the I2C devices do not have HW reset inputs, cycle power to the devices to activate the mandatory internal Power-On Reset (POR) circuit.

If the data line (SDA) is stuck LOW, the master should send nine clock pulses. The device that held the bus LOW should release it sometime within those nine clocks. If not, then
use the HW reset or cycle power to clear the bus.

 

i2c Bus Recovery 준비

i2c 버스를 리커버리하는 방법은 다음과 같은 방법들이 있다.

  • 커널에 미리 준비된 i2c_generic_scl_recovery() 함수 사용
  • 커널에 미리 준비된 i2c_generic_gpio_recovery() 함수 사용
  • custom 리커버리 함수를 사용하여 직접 HW 설정

 

i2c_init_recovery()

drivers/i2c/base/i2c-core-base.c

static void i2c_init_recovery(struct i2c_adapter *adap)                         
{                                                                               
        struct i2c_bus_recovery_info *bri = adap->bus_recovery_info;            
        char *err_str;                                                          
                                                                                
        if (!bri)                                                               
                return;                                                         
                                                                                
        if (!bri->recover_bus) {                                                
                err_str = "no recover_bus() found";                             
                goto err;                                                       
        }                                                                       
                                                                                
        /* Generic GPIO recovery */                                             
        if (bri->recover_bus == i2c_generic_gpio_recovery) {                    
                if (!gpio_is_valid(bri->scl_gpio)) {                            
                        err_str = "invalid SCL gpio";                           
                        goto err;                                               
                }                                                               
                                                                                
                if (gpio_is_valid(bri->sda_gpio))                               
                        bri->get_sda = get_sda_gpio_value;                      
                else                                                            
                        bri->get_sda = NULL;                                    
                                                                                
                bri->get_scl = get_scl_gpio_value;                              
                bri->set_scl = set_scl_gpio_value;                              
        } else if (bri->recover_bus == i2c_generic_scl_recovery) {              
                /* Generic SCL recovery */                                      
                if (!bri->set_scl || !bri->get_scl) {                           
                        err_str = "no {get|set}_scl() found";                   
                        goto err;                                               
                }                                                               
        }                                                                       
                                                                                
        return;                                                                 
 err:                                                                           
        dev_err(&adap->dev, "Not using recovery: %s\n", err_str);               
        adap->bus_recovery_info = NULL;                                         
}

i2c 버스 리커버리를 준비한다.

  • 코드 라인 9~12에서 리커버리용 후크가 설정되지 않은 경우 함수를 빠져나간다.
  • 코드 라인 15~27에서 gpio를 사용한 리커버리인 경우 scl_gpio 및 sda_gpio 핀 번호 값이 이상 없는지 확인한다. 그런 후 get_scl, set_scl 및 get_sda의 디폴트 함수를 설정 한다.
  • 코드 라인 28~34에서 scl을 사용한 리커버리인 경우 set_scl 및 get_scl 후크가 지정되지 않은 경우 에러를 출력하고 함수를 빠져나간다.

 

다음 그림은 i2c_init_recovery() 함수에서 3가지 타입의 (*recover_bus) 후크에 대해 처리하는 과정을 보여준다.

  • A) SCL 리커버리: (*get_scl) 및 (*set_scl) 후크 함수 체크
  • B) gpio 리커버리: (*get_scl), (*set_scl) 및 (*get_sda) 후크에 디폴트 함수 지정 및 scl_gpio, sda_gpio 핀 번호 체크

 

i2c_bus_recovery_info 구조체

include/linux/i2c.h

/**                                                                             
 * struct i2c_bus_recovery_info - I2C bus recovery information                  
 * @recover_bus: Recover routine. Either pass driver's recover_bus() routine, or
 *      i2c_generic_scl_recovery() or i2c_generic_gpio_recovery().              
 * @get_scl: This gets current value of SCL line. Mandatory for generic SCL     
 *      recovery. Used internally for generic GPIO recovery.                    
 * @set_scl: This sets/clears SCL line. Mandatory for generic SCL recovery. Used
 *      internally for generic GPIO recovery.                                   
 * @get_sda: This gets current value of SDA line. Optional for generic SCL      
 *      recovery. Used internally, if sda_gpio is a valid GPIO, for generic GPIO
 *      recovery.                                                               
 * @prepare_recovery: This will be called before starting recovery. Platform may
 *      configure padmux here for SDA/SCL line or something else they want.     
 * @unprepare_recovery: This will be called after completing recovery. Platform 
 *      may configure padmux here for SDA/SCL line or something else they want. 
 * @scl_gpio: gpio number of the SCL line. Only required for GPIO recovery.     
 * @sda_gpio: gpio number of the SDA line. Only required for GPIO recovery.     
 */
struct i2c_bus_recovery_info {                                                  
        int (*recover_bus)(struct i2c_adapter *);                               
                                                                                
        int (*get_scl)(struct i2c_adapter *);                                   
        void (*set_scl)(struct i2c_adapter *, int val);                         
        int (*get_sda)(struct i2c_adapter *);                                   
                                                                                
        void (*prepare_recovery)(struct i2c_adapter *);                         
        void (*unprepare_recovery)(struct i2c_adapter *);                       
                                                                                
        /* gpio recovery */                                                     
        int scl_gpio;                                                           
        int sda_gpio;                                                           
};
  • (*recover_bus)
    • 이 후크 함수를 호출하여 i2c 리커버리를 시도한다.
      • scl 또는 sda가 low 상태에서 벗어나지 못하면 이 함수를 호출하여 9번의 클럭을 만들면서 sda가 high 상태로 풀려나는지 체크한다.
  • (*get_scl)
    • 시리얼 클럭(scl) 상태를 알아온다. 1/0
  • (*set_scl)
    • 시리얼 클럭(scl) 상태를 설정한다. 1/0
  •  (*get_sda)
    • 시리얼 데이터(sda) 상태를 알아온다. 1/0
  • (*prepare_recovery)
    • 버스 리커버리를 수행하기 전에 먼저 처리할 일을 지정한다.
  • (*prepare_recovery)
    • 버스 리커버리를 완료한 후 처리할 일을 지정한다.
  • scl_gpio
    • gpio를 사용하여 시리얼 클럭(scl)을 만들 gpio  핀 번호
  • sda_gpio
    • gpio를 사용하여 시리얼 데이터(sda) 값을 읽을 핀 번호

 

i2c Bus Recovery 동작

다음 그림은 i2c 버스가 stuck 상태일 때 100Khz의 클럭 펄스를 9번 만들어 i2c 버스가 리커버리를 시도하는 모습을 보여준다.

  • 100Khz 클럭을 만들기 위해 매 사이클마다 low에서 5ms, high에서 5ms씩 delay해야 한다.

 

i2c_recover_bus()

drivers/i2c/base/i2c-core-base.c

int i2c_recover_bus(struct i2c_adapter *adap)                                   
{                                                                               
        if (!adap->bus_recovery_info)                                           
                return -EOPNOTSUPP;                                             
                                                                                
        dev_dbg(&adap->dev, "Trying i2c bus recovery\n");                       
        return adap->bus_recovery_info->recover_bus(adap);                      
}                                                                               
EXPORT_SYMBOL_GPL(i2c_recover_bus);

i2c 버스 리커버리를 시도한다.

 

i2c_generic_scl_recovery()

drivers/i2c/base/i2c-core-base.c

int i2c_generic_scl_recovery(struct i2c_adapter *adap)                          
{                                                                               
        return i2c_generic_recovery(adap);                                      
}                                                                               
EXPORT_SYMBOL_GPL(i2c_generic_scl_recovery);

scl 핀에 9번의 클럭을 만들어 i2c 버스 리커버리를 시도한다.

 

i2c_generic_recovery()

drivers/i2c/base/i2c-core-base.c

/*                                                                              
 * We are generating clock pulses. ndelay() determines durating of clk pulses.  
 * We will generate clock with rate 100 KHz and so duration of both clock levels
 * is: delay in ns = (10^6 / 100) / 2                                           
 */                                                                             
#define RECOVERY_NDELAY         5000                                            
#define RECOVERY_CLK_CNT        9                                               
                                                                                
static int i2c_generic_recovery(struct i2c_adapter *adap)                       
{                                                                               
        struct i2c_bus_recovery_info *bri = adap->bus_recovery_info;            
        int i = 0, val = 1, ret = 0;                                            
                                                                                
        if (bri->prepare_recovery)                                              
                bri->prepare_recovery(adap);                                    
                                                                                
        bri->set_scl(adap, val);                                                
        ndelay(RECOVERY_NDELAY);                                                
                                                                                
        /*                                                                      
         * By this time SCL is high, as we need to give 9 falling-rising edges  
         */                                                                     
        while (i++ < RECOVERY_CLK_CNT * 2) {                                    
                if (val) {                                                      
                        /* Break if SDA is high */                              
                        if (bri->get_sda && bri->get_sda(adap))                 
                                        break;                                  
                        /* SCL shouldn't be low here */                         
                        if (!bri->get_scl(adap)) {                              
                                dev_err(&adap->dev,                             
                                        "SCL is stuck low, exit recovery\n");   
                                ret = -EBUSY;                                   
                                break;                                          
                        }                                                       
                }                                                               
                                                                                
                val = !val;                                                     
                bri->set_scl(adap, val);                                        
                ndelay(RECOVERY_NDELAY);                                        
        }                                                                       
                                                                                
        if (bri->unprepare_recovery)                                            
                bri->unprepare_recovery(adap);                                  
                                                                                
        return ret;                                                             
}

scl 핀에 9번의 클럭을 만들어 i2c 버스 리커버리를 시도한다.

  • 코드 라인 14~15에서 버스 리커버리를 시도하기 전에 먼저 수행해야 할 일을 처리한다.
  • 코드 라인 17~41에서 9번 루프를 돌며 scl을 high 상태로 설정하고, sda 상태를 읽어본다. sda 상태가 high가 되면 리커버리가 되었으므로 루프를 벗어나다. 그렇지 않은 경우 scl 상태가 변경이 잘 되었는지 확인해본다. high 상태가 아니면 scl이 고정되어 stuck이 걸렸으므로 -EBUSY 에러를 반환한다. SCL 상태가 잘 변경이 되는 경우에는 계속하여 scl을 low 및 high로 변경해가면서 루프를 돌며 리커버리를 시도한다.
  • 코드 라인 43~44에서 버스 리커버리를 완료한 후 수행해야 할 일을 처리한다.

 

i2c_generic_gpio_recovery()

drivers/i2c/base/i2c-core-base.c

int i2c_generic_gpio_recovery(struct i2c_adapter *adap)                         
{                                                                               
        int ret;                                                                
                                                                                
        ret = i2c_get_gpios_for_recovery(adap);                                 
        if (ret)                                                                
                return ret;                                                     
                                                                                
        ret = i2c_generic_recovery(adap);                                       
        i2c_put_gpios_for_recovery(adap);                                       
                                                                                
        return ret;                                                             
}                                                                               
EXPORT_SYMBOL_GPL(i2c_generic_gpio_recovery);

gpio 모드로 변경하여 scl을 준비하고 9번의 클럭을 만들어 i2c 버스 리커버리를 시도한다. 처리한 후 다시 gpio 모드를 해제한다.

  • pinmux에 i2c 기능과 gpio 기능이 모두 있는 경우 i2c 기능으로 동작하다가 리커버리가 필요할 때 gpio 모드로 전환하여 리커버리를 수행할 수 있다.

 

다음 그림은 pinmux를 통해 i2c 펑션 모드로 사용하다가 i2c 버스 리커버리를 시도하는 모습을 보여준다.

  • gpio 펑션 모드로 전환 시켜 i2c 버스 리커버리를 하는 것을 알 수 있다.

 

i2c_get_gpios_for_recovery()

drivers/i2c/base/i2c-core-base.c

static int i2c_get_gpios_for_recovery(struct i2c_adapter *adap)                 
{                                                                               
        struct i2c_bus_recovery_info *bri = adap->bus_recovery_info;            
        struct device *dev = &adap->dev;                                        
        int ret = 0;                                                            
                                                                                
        ret = gpio_request_one(bri->scl_gpio, GPIOF_OPEN_DRAIN |                
                        GPIOF_OUT_INIT_HIGH, "i2c-scl");                        
        if (ret) {                                                              
                dev_warn(dev, "Can't get SCL gpio: %d\n", bri->scl_gpio);       
                return ret;                                                     
        }                                                                       
                                                                                
        if (bri->get_sda) {                                                     
                if (gpio_request_one(bri->sda_gpio, GPIOF_IN, "i2c-sda")) {     
                        /* work without SDA polling */                          
                        dev_warn(dev, "Can't get SDA gpio: %d. Not using SDA polling\n",
                                        bri->sda_gpio);                         
                        bri->get_sda = NULL;                                    
                }                                                               
        }                                                                       
                                                                                
        return ret;                                                             
}

scl_gpio 번 핀을 scl로 사용하기 위해 open-drain 설정으로 초기 high 출력 상태로 요청한다. (*get_sda) 후크가 사용되는 경우 sda_gpio 번 핀을 sda로 input mode로 사용 요청한다.

 

i2c_put_gpios_for_recovery()

drivers/i2c/base/i2c-core-base.c

static void i2c_put_gpios_for_recovery(struct i2c_adapter *adap)                
{                                                                               
        struct i2c_bus_recovery_info *bri = adap->bus_recovery_info;            
                                                                                
        if (bri->get_sda)                                                       
                gpio_free(bri->sda_gpio);                                       
                                                                                
        gpio_free(bri->scl_gpio);                                               
}

리커버리를 종료하였으므로 scl_gpio 및 sda_gpio 번 핀을 gpio 모드에서 해제한다.

 

gpio 리커버리에서 사용하는 세 함수들

get_scl_gpio_value()

drivers/i2c/base/i2c-core-base.c

static int get_scl_gpio_value(struct i2c_adapter *adap)                         
{                                                                               
        return gpio_get_value(adap->bus_recovery_info->scl_gpio);               
}

scl_gpio 번 핀의 값을 알아온다.

 

set_scl_gpio_value()

drivers/i2c/base/i2c-core-base.c

static void set_scl_gpio_value(struct i2c_adapter *adap, int val)               
{                                                                               
        gpio_set_value(adap->bus_recovery_info->scl_gpio, val);                 
}

scl_gpio 번 핀에 값을 출력한다.

 

get_sda_gpio_value()

drivers/i2c/base/i2c-core-base.c

static int get_sda_gpio_value(struct i2c_adapter *adap)                         
{                                                                               
        return gpio_get_value(adap->bus_recovery_info->sda_gpio);               
}

sda_gpio 번 핀의 값을 알아온다.

 

 

I2C 전송 및 버스 락

I2C 버스 락

i2c 호스트 컨트롤러를 통해 i2c 버스에 데이터를 송/수신할 때 먼저 버스를 점유하기 위해 i2c 버스 락을 걸고 사용한다. 이 때 대부분의 호스트 컨트롤러의 adapter 인터페이스는 lock_ops가 주어지지 않았을 때 디폴트로 adapter의 bus_lock 뮤텍스를 사용한다.

 

다음 그림은 i2c 전송 전에 디폴트 i2c 버스 락 오퍼레이션인 adapter의 bus_lock 뮤텍스를 사용하는 것을 보여준다.

  • i2c-mux 호스트 컨트롤러를 사용할 때 채널별로 있는 adapter 들의 lock 오퍼레이션은 별도로 지정하여 사용한다.

 

i2c_adapter_lock_bus()

drivers/i2c/i2c-core-base.c

/**                                                                             
 * i2c_adapter_lock_bus - Get exclusive access to an I2C bus segment            
 * @adapter: Target I2C bus segment                                             
 * @flags: I2C_LOCK_ROOT_ADAPTER locks the root i2c adapter, I2C_LOCK_SEGMENT   
 *      locks only this branch in the adapter tree                              
 */                                                                             
static void i2c_adapter_lock_bus(struct i2c_adapter *adapter,                   
                                 unsigned int flags)                            
{                                                                               
        rt_mutex_lock(&adapter->bus_lock);                                      
}

adapter에 있는 버스 뮤텍스 락을 건다.

 

i2c_adapter_trylock_bus()

drivers/i2c/i2c-core-base.c

/**                                                                             
 * i2c_adapter_trylock_bus - Try to get exclusive access to an I2C bus segment  
 * @adapter: Target I2C bus segment                                             
 * @flags: I2C_LOCK_ROOT_ADAPTER trylocks the root i2c adapter, I2C_LOCK_SEGMENT
 *      trylocks only this branch in the adapter tree                           
 */                                                                             
static int i2c_adapter_trylock_bus(struct i2c_adapter *adapter,                 
                                   unsigned int flags)                          
{                                                                               
        return rt_mutex_trylock(&adapter->bus_lock);                            
}

adapter에 있는 버스 뮤텍스 락을 시도한다.

 

i2c_adapter_unlock_bus()

drivers/i2c/i2c-core-base.c

/**                                                                             
 * i2c_adapter_unlock_bus - Release exclusive access to an I2C bus segment      
 * @adapter: Target I2C bus segment                                             
 * @flags: I2C_LOCK_ROOT_ADAPTER unlocks the root i2c adapter, I2C_LOCK_SEGMENT 
 *      unlocks only this branch in the adapter tree                            
 */                                                                             
static void i2c_adapter_unlock_bus(struct i2c_adapter *adapter,                 
                                   unsigned int flags)                          
{                                                                               
        rt_mutex_unlock(&adapter->bus_lock);                                    
}

adapter에 있는 버스 뮤텍스 락을 해제한다.

 

I2C 전송 Core

i2c 전송은 i2c_algorithm에 구현되어 있는 i2c용 전송 후크를 이용한다. 만일 smbus 전송이 필요한 경우 smbus용 후크도 지정하여 사용할 수 있다.

 

다음 그림은 i2c 전송에 관여하는 lock operation과 알고리즘 및 전송 제한 값 및 i2c 전송 함수의 호출 단계를 보여준다.

 

i2c_transfer()

drivers/i2c/i2c-core-base.c

/**                                                                             
 * i2c_transfer - execute a single or combined I2C message                      
 * @adap: Handle to I2C bus                                                     
 * @msgs: One or more messages to execute before STOP is issued to              
 *      terminate the operation; each message begins with a START.              
 * @num: Number of messages to be executed.                                     
 *                                                                              
 * Returns negative errno, else the number of messages executed.                
 *                                                                              
 * Note that there is no requirement that each message be sent to               
 * the same slave address, although that is the most common model.              
 */
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)       
{                                                                               
        int ret;                                                                
                                                                                
        /* REVISIT the fault reporting model here is weak:                      
         *                                                                      
         *  - When we get an error after receiving N bytes from a slave,        
         *    there is no way to report "N".                                    
         *                                                                      
         *  - When we get a NAK after transmitting N bytes to a slave,          
         *    there is no way to report "N" ... or to let the master            
         *    continue executing the rest of this combined message, if          
         *    that's the appropriate response.                                  
         *                                                                      
         *  - When for example "num" is two and we successfully complete        
         *    the first message but get an error part way through the           
         *    second, it's unclear whether that should be reported as           
         *    one (discarding status on the second message) or errno            
         *    (discarding status on the first one).                             
         */                                                                     
                                                                                
        if (adap->algo->master_xfer) {                                          
#ifdef DEBUG                                                                    
                for (ret = 0; ret < num; ret++) {                               
                        dev_dbg(&adap->dev,                                     
                                "master_xfer[%d] %c, addr=0x%02x, len=%d%s\n",  
                                ret, (msgs[ret].flags & I2C_M_RD) ? 'R' : 'W',  
                                msgs[ret].addr, msgs[ret].len,                  
                                (msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : ""); 
                }                                                               
#endif                                                                          
                                                                                
                if (in_atomic() || irqs_disabled()) {                           
                        ret = i2c_trylock_bus(adap, I2C_LOCK_SEGMENT);          
                        if (!ret)                                               
                                /* I2C activity is ongoing. */                  
                                return -EAGAIN;                                 
                } else {                                                        
                        i2c_lock_bus(adap, I2C_LOCK_SEGMENT);                   
                }                                                               
                                                                                
                ret = __i2c_transfer(adap, msgs, num);                          
                i2c_unlock_bus(adap, I2C_LOCK_SEGMENT);                         
                                                                                
                return ret;                                                     
        } else {                                                                
                dev_dbg(&adap->dev, "I2C level transfers not supported\n");     
                return -EOPNOTSUPP;                                             
        }                                                                       
}                                                                               
EXPORT_SYMBOL(i2c_transfer);

i2c 버스에 락을 건채 요청한 메시지 수 만큼 전송을 수행한다. 성공 시 0을 반환한다.

  • 코드 라인 22에서 i2c 전송을 지원하는 후크 함수가 있는 경우이다.
    • pc의 smbus는 i2c 전송은 지원하지 않고, smbus 전송만 지원하는 경우가 있다.
  • 코드 라인 34~38에서 전송을 하기 전에 먼저 버스를 점유해야 한다. irq context이거나 preempt disable 상태인 경우 i2c bus 락을 시도한다. 이 때 실패하는 경우 -EAGAIN 에러를 반환한다.
  • 코드 라인 39~41에서 i2c 버스 락을 건다.
  • 코드 라인 43~44에서 요청한 메시지 수 만큼 전송을 한 후 i2c 버스 언락을 한다.

 

__i2c_transfer()

drivers/i2c/i2c-core-base.c

/**                                                                             
 * __i2c_transfer - unlocked flavor of i2c_transfer                             
 * @adap: Handle to I2C bus                                                     
 * @msgs: One or more messages to execute before STOP is issued to              
 *      terminate the operation; each message begins with a START.              
 * @num: Number of messages to be executed.                                     
 *                                                                              
 * Returns negative errno, else the number of messages executed.                
 *                                                                              
 * Adapter lock must be held when calling this function. No debug logging       
 * takes place. adap->algo->master_xfer existence isn't checked.                
 */
int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)     
{                                                                               
        unsigned long orig_jiffies;                                             
        int ret, try;                                                           
                                                                                
        if (adap->quirks && i2c_check_for_quirks(adap, msgs, num))              
                return -EOPNOTSUPP;                                             
                                                                                
        /* i2c_trace_msg gets enabled when tracepoint i2c_transfer gets         
         * enabled.  This is an efficient way of keeping the for-loop from      
         * being executed when not needed.                                      
         */                                                                     
        if (static_key_false(&i2c_trace_msg)) {                                 
                int i;                                                          
                for (i = 0; i < num; i++)                                       
                        if (msgs[i].flags & I2C_M_RD)                           
                                trace_i2c_read(adap, &msgs[i], i);              
                        else                                                    
                                trace_i2c_write(adap, &msgs[i], i);             
        }                                                                       
                                                                                
        /* Retry automatically on arbitration loss */                           
        orig_jiffies = jiffies;                                                 
        for (ret = 0, try = 0; try <= adap->retries; try++) {                   
                ret = adap->algo->master_xfer(adap, msgs, num);                 
                if (ret != -EAGAIN)                                             
                        break;                                                  
                if (time_after(jiffies, orig_jiffies + adap->timeout))          
                        break;                                                  
        }                                                                       
                                                                                
        if (static_key_false(&i2c_trace_msg)) {                                 
                int i;                                                          
                for (i = 0; i < ret; i++)                                       
                        if (msgs[i].flags & I2C_M_RD)                           
                                trace_i2c_reply(adap, &msgs[i], i);             
                trace_i2c_result(adap, i, ret);                                 
        }                                                                       
                                                                                
        return ret;                                                             
}                                                                               
EXPORT_SYMBOL(__i2c_transfer);

i2c 버스에 요청한 메시지 수 만큼 전송을 수행한다. 성공 시 0을 반환한다.

  • 코드 라인 6~7에서 quirks 전송 제한에 걸린 경우 -EOPNOTSUPP 에러를 반환한다.
  • 코드 라인 13~20에서 읽고 쓸 메시지에 대해 trace 출력을 한다.
  • 코드 라인 23~30에서 i2c 전송을 한다. 전송 후 -EAGAIN 에러인 경우 retries 수 이내에서 루프를 돌며 시도한다. 만일 제한 시간을 초과하는 경우 재시도를 하지 않는다.
  • 코드 라인 32~38에서 읽은 메시지가 있는 경우 이에 대한 trace 출력을 한다.

 

i2c 전송 (for bcm-i2c-iproc)

broadcom ns2에 내장된 i2c에 사용하는 bcm-i2c-iproc 드라이버에서 사용하는 전송함수을 예로 알아본다.

 

bcm_iproc_i2c_xfer()

drivers/i2c/busses/i2c-bcm-iproc.c

static int bcm_iproc_i2c_xfer(struct i2c_adapter *adapter,                      
                              struct i2c_msg msgs[], int num)                   
{                                                                               
        struct bcm_iproc_i2c_dev *iproc_i2c = i2c_get_adapdata(adapter);        
        int ret, i;                                                             
                                                                                
        /* go through all messages */                                           
        for (i = 0; i < num; i++) {                                             
                ret = bcm_iproc_i2c_xfer_single_msg(iproc_i2c, &msgs[i]);       
                if (ret) {                                                      
                        dev_dbg(iproc_i2c->device, "xfer failed\n");            
                        return ret;                                             
                }                                                               
        }                                                                       
                                                                                
        return num;                                                             
}

요청한 메시지 수 만큼 메시지들을 전송한다. 성공 시 메시지 수가 반환된다. 실패의 경우 음수를 반환한다.

 

bcm_iproc_i2c_xfer_single_msg()

drivers/i2c/busses/i2c-bcm-iproc.c -1/2-

static int bcm_iproc_i2c_xfer_single_msg(struct bcm_iproc_i2c_dev *iproc_i2c,   
                                         struct i2c_msg *msg)                   
{                                                                               
        int ret, i;                                                             
        u8 addr;                                                                
        u32 val;                                                                
        unsigned int tx_bytes;                                                  
        unsigned long time_left = msecs_to_jiffies(I2C_TIMEOUT_MSEC);           
                                                                                
        /* check if bus is busy */                                              
        if (!!(readl(iproc_i2c->base + M_CMD_OFFSET) &                          
               BIT(M_CMD_START_BUSY_SHIFT))) {                                  
                dev_warn(iproc_i2c->device, "bus is busy\n");                   
                return -EBUSY;                                                  
        }                                                                       
                                                                                
        iproc_i2c->msg = msg;                                                   
                                                                                
        /* format and load slave address into the TX FIFO */                    
        addr = i2c_8bit_addr_from_msg(msg);                                     
        writel(addr, iproc_i2c->base + M_TX_OFFSET);                            
                                                                                
        /*                                                                      
         * For a write transaction, load data into the TX FIFO. Only allow      
         * loading up to TX FIFO size - 1 bytes of data since the first byte    
         * has been used up by the slave address                                
         */                                                                     
        tx_bytes = min_t(unsigned int, msg->len, M_TX_RX_FIFO_SIZE - 1);        
        if (!(msg->flags & I2C_M_RD)) {                                         
                for (i = 0; i < tx_bytes; i++) {                                
                        val = msg->buf[i];                                      
                                                                                
                        /* mark the last byte */                                
                        if (i == msg->len - 1)                                  
                                val |= 1 << M_TX_WR_STATUS_SHIFT;               
                                                                                
                        writel(val, iproc_i2c->base + M_TX_OFFSET);             
                }                                                               
                iproc_i2c->tx_bytes = tx_bytes;                                 
        }                                                                       
                                                                                
        /* mark as incomplete before starting the transaction */                
        reinit_completion(&iproc_i2c->done);                                    
        iproc_i2c->xfer_is_done = 0;                                            
                                                                                
        /*                                                                      
         * Enable the "start busy" interrupt, which will be triggered after the 
         * transaction is done, i.e., the internal start_busy bit, transitions  
         * from 1 to 0.                                                         
         */                                                                     
        val = BIT(IE_M_START_BUSY_SHIFT);                                       
                                                                                
        /*                                                                      
         * If TX data size is larger than the TX FIFO, need to enable TX        
         * underrun interrupt, which will be triggerred when the TX FIFO is     
         * empty. When that happens we can then pump more data into the FIFO    
         */                                                                     
        if (!(msg->flags & I2C_M_RD) &&                                         
            msg->len > iproc_i2c->tx_bytes)                                     
                val |= BIT(IE_M_TX_UNDERRUN_SHIFT);                             
                                                                                
        writel(val, iproc_i2c->base + IE_OFFSET);

1개의 메시지를 전송한다. 실패 시 음수를 반환한다.

  • 코드 라인 11~15에서 이미 명령이 시작되어 처리되고 있는 중이라 i2c 버스 Busy 상태이다. 이러한 경우 -EBUSY 에러를 반환한다.
  • 코드 라인 17~21에서 메시지에서 7비트 주소와 R/W 비트를 추가한 8비트 값을 tx 레지스터에 기록한다.
  • 코드 라인 28에서 전송할 메시지 길이를 최대 63바이트 범위이내로 제한한다.
  • 코드 라인 28~40에서 Read 메시지가 아니면 송신할 바이트 수 만큼 순회하며 tx 레지스터에 기록한다. 32비트 tx 레지스터에 1 바이트를 기록할 때 tx 레지스터의 msb를 항상 1로 설정한다.
    • 예) { 0x30, 0x40, 0x50, 0x60 } 을 tx 레지스터에 기록 시
      • -> { 0x8000_0030, 0x8000_0040, 0x8000_0050, 0x8000_0060 }
  • 코드 라인 43~44에서 전송이 완료되었음을 알린다.
  • 코드 라인 50~61에서 IE 레지스터에 값을 기록한다.
    • start_busy 비트와 송신 메시지인 경우 adapter의 tx FIFO 버퍼 크기보다 큰 경우 tx underrun 비트를 합친 값이다.

 

drivers/i2c/busses/i2c-bcm-iproc.c -2/2-

        /*                                                                      
         * Now we can activate the transfer. For a read operation, specify the  
         * number of bytes to read                                              
         */                                                                     
        val = BIT(M_CMD_START_BUSY_SHIFT);                                      
        if (msg->flags & I2C_M_RD) {                                            
                val |= (M_CMD_PROTOCOL_BLK_RD << M_CMD_PROTOCOL_SHIFT) |        
                       (msg->len << M_CMD_RD_CNT_SHIFT);                        
        } else {                                                                
                val |= (M_CMD_PROTOCOL_BLK_WR << M_CMD_PROTOCOL_SHIFT);         
        }                                                                       
        writel(val, iproc_i2c->base + M_CMD_OFFSET);                            
                                                                                
        time_left = wait_for_completion_timeout(&iproc_i2c->done, time_left);   
                                                                                
        /* disable all interrupts */                                            
        writel(0, iproc_i2c->base + IE_OFFSET);                                 
        /* read it back to flush the write */                                   
        readl(iproc_i2c->base + IE_OFFSET);                                     
                                                                                
        /* make sure the interrupt handler isn't running */                     
        synchronize_irq(iproc_i2c->irq);                                        
                                                                                
        if (!time_left && !iproc_i2c->xfer_is_done) {                           
                dev_err(iproc_i2c->device, "transaction timed out\n");          
                                                                                
                /* flush FIFOs */                                               
                val = (1 << M_FIFO_RX_FLUSH_SHIFT) |                            
                      (1 << M_FIFO_TX_FLUSH_SHIFT);                             
                writel(val, iproc_i2c->base + M_FIFO_CTRL_OFFSET);              
                return -ETIMEDOUT;                                              
        }                                                                       
                                                                                
        ret = bcm_iproc_i2c_check_status(iproc_i2c, msg);                       
        if (ret) {                                                              
                /* flush both TX/RX FIFOs */                                    
                val = (1 << M_FIFO_RX_FLUSH_SHIFT) |                            
                      (1 << M_FIFO_TX_FLUSH_SHIFT);                             
                writel(val, iproc_i2c->base + M_FIFO_CTRL_OFFSET);              
                return ret;                                                     
        }                                                                       
                                                                                
        /*                                                                      
         * For a read operation, we now need to load the data from FIFO         
         * into the memory buffer                                               
         */                                                                     
        if (msg->flags & I2C_M_RD) {                                            
                for (i = 0; i < msg->len; i++) {                                
                        msg->buf[i] = (readl(iproc_i2c->base + M_RX_OFFSET) >>  
                                      M_RX_DATA_SHIFT) & M_RX_DATA_MASK;        
                }                                                               
        }                                                                       
                                                                                
        return 0;                                                               
}
  • 코드 라인 5~12에서 M_CMD 레지스터에 적절한 값을 기록한다.
  • 코드 라인 14에서 남은 타임아웃 시간만큼 전송이 완료될 때 까지 기다린다.
  • 코드 라인 17~19에서 모든 인터럽트를 disable하고 write 플러쉬를 수행한다.
  • 코드 라인 22에서 인터럽트 핸들러가 처리 중인 경우 완료될 때 까지 대기한다.
  • 코드 라인 24~32에서 시간이 만료되었고 여전히 전송이 완료되지 않은 경우 RX, TX FIFO 버퍼를 flush한 후 -ETIMEOUT 에러를 반환한다.
  • 코드 라인 34~41에서 다시 i2c 버스 상태가 성공(0) 상태가 아닌 경우에도 RX, TX FIFO 버퍼를 flush한 후 에러를 반환한다.
  • 코드 라인 47~52에서 read 메시지 요청인 경우 M_RX 레지스터에서 요청한 메시지 길이 만큼 반복하여 읽어온다.
    • 수신한 데이터의 하위 8비트만 mask하여 바이트로 저장한다.

 

Broadcom ns2 I2C Register

다음 그림은 broadcom ns2 SoC 내부에 임베드된 i2c 호스 컨트롤러에서 사용되는 주요 레지스터들이다.

 

참고

 

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하여 등록할 수 있다.
  • 디바이스 트리를 파싱하여 등록을 한다.
    • of_i2c_register_device()
  • ACPI 펌웨어를 통해 등록된다.
    • i2c_acpi_register_device()

 

i2c 드라이버 등록
  • i2c_add_driver() 또는 i2c_register_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()

 

참고

 

 

I2C Subsystem -1- (Basic)

 

I2C(Inter-Integrated Circuit)

I2C는 필립스에서 개발한 직렬 컴퓨터 버스이며 여러 개의 저속의 주변 기기를 연결하기 위해 사용된다. 시리얼 통신에 사용되는 UART 또는 SPI들보다 더 단순하여 심플한 회로 구성이 가능하다.

 

I2C 통신에 사용되는 2 개의 라인은 다음과 같은 기능을 수행한다.

  • SDA (Serial DAta)
    • 데이터의 직렬 전송에 사용된다.
  • SCL (Serial CLock)
    • 디바이스간 신호 동기화에 사용되는 클럭이다.

 

다음 그림은 I2C 버스에 연결되어 있는 디바이스들을 보여준다.

 

i2c 버스에 연결되는 디바이스 종류들은 수백~수천 가지로 매우 다양하다. 다음 그림은 몇 가지 종류의 디바이스들을 i2c 버스에 연결하여 사용할 때의 모습을 보여준다.

 

I2C Specification History

i2c 스펙은 다음과 같이 발전을 해왔다. 아래 노란 박스는 새 버전이 나올 때 마다 변경된 주요 항목이다.

  • 필립스에서 처음 I2C가 개발되었을 때 다음과 같은 specification으로 동작하였다.
    • 가장 널리 사용되는 스탠다드 모드가 100Khz로 사용되고, 저속 모드로 운영시에는 10Khz의 속도로 동작한다.
    • 7비트 주소를 사용하여 총 128개의 주소를 사용할 수 있지만 그 중 16개는 reserve되어 있고, 나머지 112개를 사용할 수 있다. 따라서 하나의 I2C 버스에는 최대 112개의 디바이스가 연결될 수 있다.
  • 그 후 추가 버전들은 속도가 증가되었고, 주소가 10비트로 확장되었다.
    • 속도 증가
      • Fast 모드에서 400Khz, High 모드에서 3.4Mhz로 속도가 증가됨을 알 수 있다.
    • 주소 확대
      • 10비트 모드가 사용되면서 최대 1008개의 디바이스를 연결할 수 있게 되었다.

  • (1) 3.4Mhz High-speed-mode를 사용하기 위해서는 SDAH SLCH를 사용하는 디바이스를 사용하고 arbitration 및 클럭 동기화를 사용하지 않아야 한다.
  • (2) 5Mhz Ultra Fast-speed-mode를 위해서는 USDA, USCL 라인이 추가되어야하고 Master → Slave 단방향만 지원된다.

 

멀티 마스터 지원

I2C는 다음 그림과 같이 1개의 버스에 2개 이상의 컨트롤러가 경쟁할 수 있다. 이 때 두 컨트롤러는 버스 Master 역할을 하기 위해 경쟁하게된다.

  • Multi-master
    • 2개 이상의 마스터가 메시지 손상 없이 동시에 버스를 제어하는 경우.
  • Arbitration
    • 2개 이상의 마스터가 버스를 동시에 통제할 때 오직 하나만이 허용되며 선택된 마스터의 메시지는 손상되지 않는다.
  • Synchronization
    • 두 개 이상의 디바이스의 클럭들을 동기화

 

I2C-Mux

다음과 같이 i2c 스위치 및 i2c 멀티플레서 두 종류로 나뉜다. 각각의 기능은 다음과 같다.

  • i2c 스위치
    • 1:N으로 구성 시 하위 채널 각각에 대해 select  및 deselect 할 수 있다. 즉 1개 이상을 select할 수 있다.
  • i2c 멀티플렉서
    • 1:N 또는 N:1로 구성 시 하위 채널 하나를 select 및 deselect 할 수 있다.  select 시 한 번에 하나의 채널만 select할 수 있다.

 

 

제품에 따라 다음과 같은 기능이 지원될 수도 있다.

  • HW reset 핀(option)
    • i2c 스위치에서 외부 HW reset(option) 핀이 지원되는 경우 버스 리커버리를 수행할 수 있다.
    • i2c 스위치 및 i2c 멀티플렉서 둘 다 내부에서 POR(Power On Reset) 기능을 지원한다.
  • 인터럽트(option)
    • 인터럽트를 받아 mux하여 전송할 수 있다.
  • HW addr 설정 핀
    • i2c-mux 디바이스를 하나의 버스에 여러 개를 사용할 경우 중복되지 않게 주소 하위 비트 몇 개(3~4)를 설정할 수 있는 핀이 패키지 외부로 제공된다.

 

다음 그림은 외부 reset, 인터럽트, HW 주소 설정핀 등이 지원되는 모습을 보여준다.

 

아래 그림은 2개의 i2c 컨트롤러와 그 중 하나에 연결된 4채널 i2c 멀티플렉서를 보여준다.

 

I2C 통신 프로토콜

2 개의 라인만을 사용하는 I2C 버스에서 I2C 디바이스들이 사용하는 통신 프로토콜을 알아보기로 하자.

 

Feature 리스트

다음 표와 같이 I2C 통신 프로토콜에 사용되는 Feature들이다. 디바이스들은 싱글 마스터, 멀티 마스터 또는 슬래이브 등의 각 구성(Configuration)에 따라 사용되는 Feature들이 다르다.

 

주요 Feature

Start & Stop condition

다음 그림과 같이 데이터를 송수신하기 위한 시작과 끝 조건을 알아본다.

  • Start condition
    • 클럭(SCL)이 high 시그널일 때 데이터(SDA)가 low 시그널로 변경될 때 Start condition으로 인식한다.
    • 표기법: S
  • Stop condition
    • 클럭(SCL)이 high 시그널일 때 데이터(SDA)가 high 시그널로 변경될 때 Stop condition으로 인식한다.
    • 표기법: P

 

i2c 데이터 통신 중에는 클럭이 high 시그널일 때에는 데이터(SDA) 시그널이 변경되면 안된다. 이러한 규칙으로 인해 클럭이 high인 상태에서는 데이터를 안정적으로 읽는 것을 보장한다.

 

그런데 아래 그림과 같이 클럭(SCL)이 high 상태일 때 데이터(SDA)가 변경되는 특수한 상태가 있다. 이러한 경우 데이터(SDA)가 변화한 방향에 따라 low 상태로 변경되면 Start condition이고 high 상태로 변경되면 Stop condition이라 한다.

 

1 바이트 전송 및 Acknowledge 처리

Start condition이 발생한 후 Stop condition이 나오기 전까지 디바이스들 간에 연속으로 바이트 단위의 통신을 할 수 있다. 1 바이트를 수신할 때마다 수신측 디바이스는 전송측을 위해 응답(Acknowledge) 비트를 전송해야 한다.

  • 전송측 디바이스는 1 바이트를 MSB부터 전송한다.
  • 수신측 디바이스는 1 바이트 수신 완료 후 1 비트의 Ack(low)를 전송한다.
  • 표기법: A 또는 ACK

 

아래 그림을 보면 클럭(SCL)을 마스터가 주기적으로 보내고 있음을 알 수 있다. 데이터(SDA)는 전송 디바이스측에서 8비트를 보낸 후 9번째에 high 상태를 유지하고 있다. 그리고 수신 디바이스측에서 9번째 클럭에 해당하는 위치가 오기전에 ACK(low) 상태를 유지하는 것을 알 수 있다.

Synchronization

하나의 i2c 버스에서 두 개의 마스터가 동작할 때 각각의 클럭을 and 하여 동기화 시키는 것으로 하나의 클럭을 만들어낸다.

 

Arbtration

하나의 i2c 버스가 free 상태일 때 두 개의 마스터가 동시에 버스를 차지하려고 각각 Start condition을 보낸 경우이다. 그 이후로 운좋게(?) 서로 데이터를 동일하게 보내다가 어느 시점에서 각자의 전송 비트가 달라지게 되는데 그 시점에서 데이터(SDA)를 high 상태로 바꾼 마스터가 arbtration의 패자가 된다. 즉 데이터(SDA)를 low 상태로 물고 있는 마스터가 승자이다.

 

아래 그림은 두 개의 마스터가 거의 동시에 start condtion을 보냈고, 첫 번째 마스터가 데이터를 high로 변경하였음에도 SDA 라인은 low 상태를 유지하고 있다. 이 때 첫 번째 마스터가 자신이 arbitration의 패자가 되었음을 인식한다.

 

Clock Streach (Clock low extending)

i2c 마스터가 규칙적으로 clock을 발생하여야 하고 이에 대해 i2c 디바이스는 클럭에 맞춰 데이터를 송/수신해야한다. 그러나 요청을 받은 후에 클럭 타임을 초과하는 처리 시간이 필요한 경우 slave 디바이스가 데이터를 준비하기 위해 클럭을 강제로 low로 설정해버린다. 그런 후 데이터가 준비되어 응답을 할 수 있으면 다시 클럭을 풀어준다. 그런 경우 마스터가 계속 하여 클럭을 진행할 수 있다.

  • 예) 주소 0x45를 갖는 EEPROM 디바이스에 offset 08번에 해당하는 데이터를 요청하였다. eeprom 장치에서 데이터를 가져오는 시간이 많이 걸릴 때가 있으므로 이러한 경우 잠시 클럭을 low로 묶어놓고 데이터가 다 준비된 경우 이를 응답하는 형식으로 처리를 하는 것을 clock streaching이라 한다.
  • ACK를 수신하면 인터럽트가 발생하고 이 인터럽트의 처리가 완료되기 전까지 클럭(SCL) 시그널은 low 상태로 고정되어 대기한다.
  • clock streach 기능을 사용하면 adapter가 I2C_FUNC_SLAVE 기능이 지원되어야 한다.
  • 클럭을 최대 100us까지 지연시킬 수 있다.
  • 두 가지 지연 방법이 있다.
    • Periodic clock stretching by slave
      • 주기적으로 매 번 클럭을 늘려 smbus 통신 속도를 10Khz로 줄일 수 있다.
    • Random clock stretching by master or slave
      • ISR 루틴에서 데이터 준비를 위해 지연을 시키는 경우이다.

 

다음 그림은 특정 디바이스로부터 무조건 1 바이트 값을 읽어오는 시나리오에서 디바이스가 clock streaching을 사용하는 유무에 대해 비교하였다.

  • 보통 주소를 전송하고 요청 offset 및 size를 요청하는데 이렇게 하면 길어지므로 아래 그림에서는 간단히 주소와 데이터를 수신하는 것으로 보여준다.
  • slave 디바이스가 scl을 low로 강제함으로써 마스터 디바이스가 계속하여 클럭을 발생시키지 못하게 홀드시킨다.

 

7-bit slave addresss (슬레이브 디바이스 선택 후 데이터 전송 또는 수신)

먼저 상대 디바이스를 선택한 후 데이터를 송/수신하는데 방법은 다음과 같다.

  • 7bit 주소를 전송하는 것으로 디바이스를 선택한다. 그 후 전송측을 결정하는 R/W 비트를 전송한다.
    • R/W 비트
      • 1=마스터가 수신
      • 0=마스터가 송신
  • 송수신 비트가 0인 경우 마스터가 슬레이브에 8 비트 데이터를 전송한다.
  • 송수신 비트가 1인 경우 슬레이브가 마스터에 8비트 데이터를 수신한다.
  • 그 후 수신측에서 응답(Ack) 1 비트를 전송한다.
  • 표기법:  Addr + R/W

 

특별 주소(Special Addresses)

다음의 16개 주소들은 다음과 같이 특별한 목적으로 사용된다.

  • R/W 비트 상태여부와 관계 없이 Address만을 보면 16개의 주소가 특별 주소로 사용된다.

  • smbus의 경우 다음 주소의 디바이스 용도는 다음과 같다.
    • 0001-000 (0x08)
      • SMBus host
    • 0001-001 (0x09)
      • Smart Battery Charger
    • 0001-010
      • Smart Battery Selector
      • Smart Battery System Manager
    • 0001-011
      • Smart Battery
    • 0001-100
      • SMBus
    • 0001-100 (0x0c)
      • ARA(Alert Response Address)
      • SMBus alert 응답 주소
    • 0101-000 (0x28)
      • ACCESS.bus host
    • 0101-100 (0x2c)
      • LCD Contrast Controller로 이전에 예약되어 있었지만 미래에 다시 설정할 예정이다.
    • 0101-101 (0x2d)
      • CCFL Backlight Driver로 이전에 예약되어 있었지만 미래에 다시 설정할 예정이다.
    • 0110-111 (0x37)
      • ACCESS.bus 디폴트 주소
    • 1000-0XX (0x40 ~ 0x43)
      • PCMCIA Socket Controller로 이전에 예약되어 있었지만 미래에 다시 설정할 예정이다.
    • 1000-100 (0x44)
      • VGA Graphics Controller로 이전에 예약되어 있었지만 미래에 다시 설정할 예정이다.
    • 1001-0XX (0x48 ~ 0x4b)
      • 제한없이 사용할 수 있는 주소
    • 1100-001 (0x61)
      • SMBus 디바이스의 디폴트 주소

 

참고로 ACCESS.bus는 2-wire를 사용하여 외부 장치들을 연결하여 나갈 수 있도록 설계한 시도이다.

  • 전원까지 4개의 pin을 사용한다.
  • RS-232C 인터페이스를 대체할 목적이다.

 

10-bit addressing

10비트 주소를 사용하는 경우 다음과 같이 2 바이트를 사용하여 디바이스의 주소를 지정할 수 있다.

  • 첫 번째 바이트
    • 10비트 중 2비트 주소
  • 두 번째 바이트
    • 10비트 중 8비트 주소

 

 

General Call address

General call 주소가 사용되면 디바이스에 몇 가지 특별한 요청을 할 수 있다. 이 기능은 아래 Start BYTE와 같이 대부분의 시스템에서 잘 사용되지 않는 기능이다.  이 기능은 주로 i2c 디바이스들 중 EEPROM등에서 프로그래밍등이 요구될 때 사용되는 특수 기능이다.

  • 첫 번째 바이트 구성을 General call(0x0)로 사용한다.
  • 두 번째 바이트에 기능 코드 또는 마스터 주소를 부여할 수 있다.
    • LSB가 0일 때 다음과 같은 기능 코드가 있다.
      • 0x04: Write programmable part of slave address by hardware
      • 0x06: Reset and write programmable part of slave address by hardware
    • LSB가 1인 경우 마스터 주소를 사용하는 경우 Hardware Generall Call을 사용할 수 있다.

 

 

Start BYTE

Srart BYTE feature는 0x00 데이터와 dummy NACK(1)을 전송한다. 두 개 이상의 호스트 컨트롤러가 버스 Master를 경쟁하고 있는 경우 stop condition을 detect한 경우 두 호스트 컨트롤러 중 하나가 버스 마스터가 된다. 특별한 경우 어떤 호스트 컨트롤러가 계속하여 버스를 점유하여 명령(reset, EEPROM 기록 등)을 처리해야 하는 경우 stop condition을 사용하지 않아야 한다. stop condition을 하는 경우 버스를 빼앗길 염려가 있다. 이러한 경우에 stop condition 대신 START BYTE를 보내고 다시 start condition을 사용하는 방식으로 반복하여 계속 명령을 보낼 수 있다. 잘 사용되지 않는 기능이지만 드믈게 특정 시스템에서 사용된다.

 

Device ID

디바이스에 대한 제조사 id와 파트 id 및 리비전 정보를 알아올 수 있다.

 

디바이스를 알아오는 경우 다음과 같은 제조사 id를 읽어낼 수 있다.

 

i2c 전송 포맷

i2c 전송 포맷은 다음과 같이 3가지 방법이 있다.

  • 마스터 송신
    • 슬레이브로 데이터를 송신 중에 방향이 바뀌지 않는다.
  • 마스터 수신
    • 마스터가 주소 송신 후 슬레이브로 부터 데이터를 수신한다. 역시 방향이 바뀌지 않는다.
  • Combined 포맷
    • 마스터와 슬레이브의 복합 송수신이 가능하다.

 

마스터 송신

다음 그림은 마스터 디바이스가 슬레이브 디바이스로 N 바이트를  전송하는 모습이다.

  • 슬레이브가 바이트를 받을 때 마다 ACK를 보내는데 더 이상 받을 수 없는 상황인 경우 NACK를 보낸다.

 

마스터 수신

다음 그림은 마스터 디바이스가 슬레이브 디바이스로 1 바이트를 수신하는 모습이다.

  • 마지막 바이트를 수신한 후 마스터가 NACK를 보내야한다.

 

Combination 전송

다음 그림은 마스터 디바이스가 슬레이브 디바이스로 여러 개의 메시지를 송신 또는 수신하는 모습이다.

  • 마스터가 버스를 계속 점유한 채로 전송 및 수신들의 combination을 완료해야 하는 경우 사용한다.
  • 메시지의 전송이 끝난 후 반복 시작하고자 할 때 중간에 repeated start condition 을 전송한다.
    • 표기법: Sr

 

 

SMBus(System Management Bus)

필립스의 i2c를 기반으로 동작하도록 인텔과 듀라셀이 1994년 정의한 버스이다. 특징은 다음과 같다.

  • 주로 PC 마더보드에 연결된 전력 제어 장치(power on/off) 및 온도 센서등이 smbus에 연결되어 사용된다.
  • 참고: System Management Bus (SMBus) Specification Version 2.0 August 3, 2000 | smbus.org – 다운로드 pdf

 

SMBus가 I2C와의 다른 점

타이밍
  • Speed
    • smbus
      • 10~100Khz를 사용한다.
        • 실제 대부분의 구현은 50~100Khz를 사용한다.
      • PMBus로 발전되면서 400Khz 속도도 사용할 수 있다.
    • i2c
      • 최저 속도는 100Khz이다.
  • 클럭(scl) 라인 low 타임아웃
    • smbus
      • 타임아웃을 설정할 수 있다.
      • Slave 디바이스의 경우 최대 25ms 까지 low 시간을 연장할 수 있다. (Clock Streaching)
      • Master 디바이스의 경우 최대 10ms 까지 low 시간을 연장할 수 있다. (Clock Streaching)
    • i2c
      • 타임아웃을 설정할 수 없다.
  • 데이터(sda) 라인 홀드 시간
    • smbus
      • 최대 300ns까지 홀드 시킬 수 있다.
    • i2c
      • 홀드할 수 없다.
  • Power-On-Reset 후 디바이스 준비 시간
    • smbus
      • 최대 0.5초까지 대기해야 한다.
ACK 및 NACK
  • addr 전송 후 ACK 용도
    • smbus
      • 탈부착 smbus 디바이스가 버스에 연결되었는지 여부를 detect 하는 비트 용도로 항상 ACK를 요구한다.
      • 슬레이브 디바이스는 자신의 주소(Addr) 비트를 인식하면 ACK를 회신한다.
      • S | Addr + R/W | ACK | …
    • i2c
      • ACK가 없어도 된다.
  • NACK 의미
    • smbus
      • N잘못된 명령이나 데이터의 수신을 의미한다.
    • i2c
      • 더이상 데이터를 전송하지 않는다는 것을 의미한다.
Protocol
  • 탈부착 디바이스 연결
    • smbus
      • 통신 중 smbus 디바이스가 smbus에 연결될 수 있다.
    • i2c
      • 통신 중 i2c 디바이스가 i2c 버스에 연결될 수 없다.
  • ARP(Address Resolution Protocol)
    • smbus
      • ARP를 지원한다.
    • i2c
      • ARP를 지원하지 않는다.
  • PEC(Packet Error Checking)
    • smbus
      • PEC를 지원한다.
      • 에러 체킹을 위해 CRC-8 규격으로 1 바이트를 추가 사용한다.
    • i2c
      • PEC를 지원하지 않는다.
Alert
  • smbus에 연결된 디바이스들이 alert라는 옵션 신호를 사용하여 host에 전달할 수 있게 하였다.
전기 규격
  • 전기적인 특성이 I2C와 일부 다르다.

 

SMBus protocol

SMBus 프로토콜에 사용되는 명령들을 알아본다. 참고로 그림에서 사용하는 기호는 다음과 같다.

  • S
    • Start condition
  • P
    • Stop condition
  • Sr
    • Repeted Start condition
  • Addr
    • 7비트 주소
  • R/W
    • 1비트 (R=1, W=0)
  • A
    • Ack (0)
  • PEC
    • 8비트 Packet Error Checking

 

Quick, Send & Receive 명령

Quick

슬레이브 디바이스로 R/W 1 비트를 전달할 수 있고, Ack 1비트를 수신하여 detect 여부를 확인할 수 있다.

  • R/W 비트를 수신하여 장치의 on/off를 제어하거나, low-power 스탠바이 모드를 enable/disable하는데 사용할 수 있다.
  • 데이터 바이트의 송신 또는 수신은 없다.

 

Send byte

슬레이브 디바이스에 1 바이트를 송신한다.

  • pure smbus의 ACK는 디바이스에서의 처리 결과를 호스트에 반환하는 기능을 가진다.
  • pure i2c-bus의 ACK는 디바이스가 항상 1을 응답해야한다.

 

Receive byte

슬레이브 디바이스로부터 1 바이트를 수신한다.

  • 마지막 ACK는 NACK일 수 있고 이는 마지막 바이트를 의미한다.

 

Write & Read 명령

Write byte

슬레이브 디바이스에 1 바이트 명령 + 1 바이트를 송신한다.

 

Write word

슬레이브 디바이스에 1 바이트 명령 + 워드(2 바이트)를 송신한다.

  • smbus에서 워드는 2바이트이다.

 

Read byte

슬레이브 디바이스에 1 바이트 명령을 송신하고 1 바이트를 수신한다.

  • 마지막 ACK는 NACK일 수 있고 이는 마지막 바이트를 의미한다.

 

Read word

슬레이브 디바이스에 1 바이트 명령을 송신하고 워드(2 바이트)를 수신한다.

  • 마지막 ACK는 NACK일 수 있고 이는 마지막 바이트를 의미한다.

 

Block 명령

Block write

슬레이브 디바이스에 명령을 전송한다. 그런 후 다시 슬레이브 디바이스에 길이 + 블럭(최대 32바이트) 데이터를 송신한다.

 

Block read

슬레이브 디바이스에 명령을 전송한다. 그런 후 다시 슬레이브 디바이스로부터 길이 + 블럭(최대 32바이트) 데이터를 수신한다.

  • 마지막 ACK는 NACK일 수 있고 이는 마지막 바이트를 의미한다.

 

Process Call 명령

Process call

슬레이브 디바이스에 명령 + 워드(2 바이트)를 전송한다. 그런 후 다시 슬레이브 디바이스로부터 워드(2 바이트)를 수신한다.

  • 마지막 ACK는 NACK일 수 있고 이는 마지막 바이트를 의미한다.

 

Block Process Call

슬레이브 디바이스에 명령 + 길이와 블럭 데이터(32 바이트까지)를 전송한다. 그런 후 다시 슬레이브 디바이스로부터 길이와 블럭(최대 32 바이트) 데이터를 수신한다.

  • 마지막 ACK는 NACK일 수 있고 이는 마지막 바이트를 의미한다.

 

이벤트

Host notify

슬레이브 디바이스가 호스트에 워드 데이터를 전달하고자 할 때 사용한다. 호스트는 워드 데이터를 수신하여 호스트에서 슬레이브를 담당하는 드라이버에 이벤트를 전달한다.

  • pure smbus에서 host notify는 반드시 구현되어야 한다.

 

smbalert#

슬레이브 디바이스가 smbalert# 인터럽트를 호스트에 발생시킨다. 발생 시킨 디바이스를 확인하기 위해 아래와 같이 ARA(Alert Response Address)로부터 디바이스 주소를 알아올 수 있다.

  • 만일 HW 구성 시 별도의 smbalert# 라인이 호스트에 연결되어 구성되지 않은 경우 호스트가 alert를 주기적으로 폴링하여 알아내는 방법도 사용할 수 있다.
  • pure smbus에서 alert는 option 사항이다.
  • alert callback을 위해 i2c_setup_smbus_alert() 함수를 사용하여 callback용 함수를 등록하여 사용한다.

 

I2C 서브시스템

I2C 디바이스와 드라이버를 등록하면 I2C 서브시스템은 유저 스페이스에 다양한 인터페이스를 제공한다.

  • i2c-core가 크게 두 가지를 관리한다.
    • i2c adapter 디바이스 및 드라이버
      • /sys/devices/…/i2c-N
        • i2c 어댑터 디바이스 인터페이스
      • /sys/class/i2c-adapter
        • i2c-adapter 클래스 디바이스 인터페이스
    • i2c client 디바이스 및 드라이버
      • /sys/devices/…/i2c-N/<client device>
        • i2c 클라이언트 디바이스 인터페이스
  • i2c-dev는 i2c 클라이언트 디바이스를 위해 2 개의 인터페이스를 제공한다.
    • /dev/i2c-N
      • 캐릭터 디바이스 인터페이스
    • /sys/class/i2c-dev
      • i2c-dev 클래스 디바이스 인터페이스

 

SoC 외부에 위치한 i2c 호스트 컨트롤러

임베디드 시스템에서 i2c 호스트 컨트롤러들은 보통 SoC 내부에 위치하여 플랫폼 디바이스로 곧바로 등록할 수 있다. 그러나 이를 사용하지 못하는 경우에는 속도가 빠른 버스에 연결하여 사용할 수 있다. 일반적인 PC에는 i2c 호스트 컨트롤러가 내장되지 않았다. 이러한 PC에는 pci 버스와 usb 버스가 연결되어 있는데 pci 버스 또는 usb 버스에 i2c 호스트 컨트롤러를 연결하여 사용하는 경우가 많다. 다음 그림은 usb 버스에 i2c 버스가 연결된 경우를 보여준다. 좌측 그림이 H/W 연결 구성이고 우측 그림이 이에 대한 각 버스 서브 시스템과 core 및 드라이버 위치를 보여준다.

  • 예) PCI 버스  —–>  USB 버스  —–>  I2C 버스

 

i2c sysfs

다음 그림은 i2c adapater 디바이스 및 드라이버가 등록된 모습을 sysfs 디렉토리로 보여준다.

  • 2 개의 i2c adapter가 플랫폼 디바이스로 등록되어있고, 2 개의 드라이버 역시 바인딩 되어 동작중인 상태이다.

 

다음 그림은 여러 개의 i2c client 디바이스 및 드라이버가 추가 등록된 모습을 sysfs 디렉토리로 보여준다.

  • 여러 가지 종류의 디바이스 및 드라이버가 등록되어 있는 모습을 보여준다.

 

i2c 호스트 컨트롤러(Adapter) 및 i2 디바이스(Client) 등록

 

i2c 디바이스는 버스 마스터 기능 유무에 따라 i2c 호스트 컨트롤러와 i2c 클라이언트의 역할을 한다.

  • i2c 호스트 컨트롤러(included i2c adapter)는 상위 버스와 i2c 버스간의 브릿지 기능을 수행한다.
  • i2c 클라이언트는 i2c 버스에 연결되는 consumer 디바이스이다.

 

i2c 버스는 pci나 usb 버스와 다르게 i2c 버스를 스캔하여 디바이스를 찾는 기능이 없다. 따라서 사전에 연결하고자 하는 디바이스의 주소를 미리 등록해두고 이 주소를 사용하여 디바이스가 있는지 HW detect를 시도할 수 있다.

 

다음 그림은 상위 버스에 i2c 호스트 컨트롤러(included i2c adapter)를 등록시키고 i2c 클라이언트 디바이스가 등록된 모습을 보여준다.

  • i2c 클라이언트 디바이스
    • 하드웨어 센서 모니터링 디바이스인 lm75를 예를 들었다.

 

i2c 호스트 컨트롤러 등록

i2c 호스트 컨트롤러 디바이스와 드라이버를 등록하기 위해 다음과 같은 API들을 사용한다.

예) 플랫폼 버스에 등록

  • 디바이스
    • platform_device_register() 함수를 사용하여 등록시키거나 디바이스 트리를 파싱하여 등록을 한다.
      • of_platform_device_create_pdata()
  • 드라이버
    • platform_register_driver() 함수를 사용하여 등록시킨다.
    • 드라이버 내부 probe 함수에서 i2c_add_adapter() 함수를 사용하여 i2c 버스를 제어할 i2c_adapter 구조체를 등록시킨다.

 

다음 그림은 broadcom 사의 i2c 호스트 컨트롤러 드라이버가 등록되는 주요 코드 과정을 보여준다.

 

i2c 클라이언트 등록

  • 디바이스 부분
    • i2c_new_device() 함수를 사용하여 등록시키거나 디바이스 트리를 파싱하여 등록을 한다.
      • of_i2c_register_device()
  • 드라이버 부분
    • i2c_add_driver() 또는 i2c_register_driver() 함수를 사용하여 등록시킨다.

 

다음 그림은 wm8750 디바이스가 i2c 클라이언트 디바이스 및 드라이버로 등록되는 과정에서의 주요 코드 부분을 보여준다.

  • i2c 노드에 포함된 wm8750@1a 노드가 파싱되어 i2c_client 구조체에 포함된 후 i2c 버스에 디바이스로 이미 포함된 상태이다.
  • i2c_driver 구조체가 wm8750@1a 디바이스에 대응하는

 

유저 스페이스 바인딩

드라이버를 i2c 디바이스와 attach하는 방법

  • 기존(old) 방법
    • modprobe <driver> probe=1,0×71
    • modprobe <driver> force=1,0×71
    • modprobe <driver> force_<device>=1,0×71
  • 새로운(new) 방법
    • echo <device> 0x71 > /sys/bus/i2c/devices/i2c-0/new_device

 

드라이버를 i2c 디바이스와 attach하지 못하게 하는 방법

  • 기존(old) 방법
    • modprobe <driver> ignore=1,0×71
  • 새로운(new) 방법
    • 드라이버를 dummy 디바이스와 연결해두는 방법을 사용한다.
      • echo dummy 0x2f > /sys/bus/i2c/devices/i2c-1/new_device
      • modprobe <driver>

 

I2C Tools

i2c 툴을 설치하기 위해서는 다음과 같이 i2c-tools를 설치한다.

  • sudo apt-get install i2c-tools

 

i2cdetect

명령어 사용법

$ i2cdetect
Error: No i2c-bus specified!
Usage: i2cdetect [-y] [-a] [-q|-r] I2CBUS [FIRST LAST]
i2cdetect -F I2CBUS
i2cdetect -l
I2CBUS is an integer or an I2C bus name
If provided, FIRST and LAST limit the probing range.

 

i2c 버스 및 i2c Adapter명 확인

x86 서버

다음과 같이 pc server는 두 개의 i2c adapter가 서로 다름을 알 수 있다.

$ i2cdetect -l
i2c-0   unknown         AST i2c bit bus                         N/A
i2c-1   unknown         SMBus I801 adapter at 0580              N/A

 

broadcom ns2(Cortex-A57)
$ i2cdetect -l
i2c-0 i2c Broadcom iProc I2C adapter I2C adapter
i2c-1 i2c Broadcom iProc I2C adapter I2C adapter

 

rpi3(Cortex-A53)
$ i2cdetect -l
i2c-1 i2c 3f804000.i2c I2C adapter

 

i2c 버스별 Functionalities 확인

x86 서버

0번 버스는 i2c  전송이 가능하고 smbus 전송도 에뮬레이션 처리가 가능하다. 반면에 1번 버스는 smbus 전송만 가능하다.

$ i2cdetect -F 0
Functionalities implemented by /dev/i2c-0:
I2C yes
SMBus Quick Command yes
SMBus Send Byte yes
SMBus Receive Byte yes
SMBus Write Byte yes
SMBus Read Byte yes
SMBus Write Word yes
SMBus Read Word yes
SMBus Process Call yes
SMBus Block Write yes
SMBus Block Read yes
SMBus Block Process Call yes
SMBus PEC yes
I2C Block Write yes
I2C Block Read yes

$ sudo i2cdetect -F 1
Functionalities implemented by /dev/i2c-1:
I2C no
SMBus Quick Command yes
SMBus Send Byte yes
SMBus Receive Byte yes
SMBus Write Byte yes
SMBus Read Byte yes
SMBus Write Word yes
SMBus Read Word yes
SMBus Process Call no
SMBus Block Write yes
SMBus Block Read yes
SMBus Block Process Call no
SMBus PEC yes
I2C Block Write yes
I2C Block Read yes

 

broadcom ns2(Cortex-A57)
$ i2cdetect -F 0
Functionalities implemented by /dev/i2c/0:
I2C yes
SMBus Quick Command yes
SMBus Send Byte yes
SMBus Receive Byte yes
SMBus Write Byte yes
SMBus Read Byte yes
SMBus Write Word yes
SMBus Read Word yes
SMBus Process Call yes
SMBus Block Write yes
SMBus Block Read no
SMBus Block Process Call no
SMBus PEC yes
I2C Block Write yes
I2C Block Read yes

$ i2cdetect -F 0
  (상동)

 

rpi3(Cortex-A53)
$ i2cdetect -F 1
Functionalities implemented by /dev/i2c-1:
I2C yes
SMBus Quick Command yes
SMBus Send Byte yes
SMBus Receive Byte yes
SMBus Write Byte yes
SMBus Read Byte yes
SMBus Write Word yes
SMBus Read Word yes
SMBus Process Call yes
SMBus Block Write yes
SMBus Block Read no
SMBus Block Process Call no
SMBus PEC yes
I2C Block Write yes
I2C Block Read yes

 

i2c 버스별 디바이스 스캔

x86 서버
$ sudo i2cdetect -y -r 0
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

$ sudo i2cdetect -y -r 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- 44 -- -- -- 48 -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

 

broadcom ns2(Cortex-A57)
  • 스캔된 디바이스는 숫자로 표기되고, UU로 표기된 디바이스는 이미 바인딩(dummy 포함)된 디바이스이다.
$ i2cdetect -y -r 0
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- UU -- -- --
50: 50 51 -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- UU -- -- -- -- -- -- --
70: UU -- UU -- -- -- -- --

# i2cdetect -y -r 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- UU
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

 

rpi3(Cortex-A53)
$ i2cdetect -y -r 0
Error: Could not open file `/dev/i2c-0' or `/dev/i2c/0': No such file or directory

$ i2cdetect -y -r 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

 

i2cdump

명령어 사용법
$ i2cdump
Error: No i2c-bus specified!
Usage: i2cdump [-f] [-o] [-y] [-r first-last] I2CBUS ADDRESS [MODE [BANK [BANKREG]]]
I2CBUS is an integer or an I2C bus name
ADDRESS is an integer (0x03 - 0x77)
MODE is one of:
b (byte, default)
w (word)
W (word on even register addresses)
s (SMBus block)
i (I2C block)
c (consecutive byte)

 

데이터 dump

1번 버스의 0x4f 디바이스 덤프

$ i2cdump -f -y 1 0x4f
0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef
00: 22 11 ff 00 00 ff 00 00 ff 00 ff 0f 00 00 00 00 "?.........?....
10: 00 ff 04 00 00 00 00 00 00 00 00 00 00 00 00 00 ..?.............
…
e0: 22 11 ff 00 00 ff 00 00 ff 00 ff 0f 00 00 00 00 "?.........?....
f0: 00 ff 04 00 00 00 00 00 00 00 00 00 00 00 00 00 ..?.............

 

i2cget

명령어 사용법
$ i2cget 
Usage: i2cget [-f] [-y] I2CBUS CHIP-ADDRESS [DATA-ADDRESS [MODE]]
I2CBUS is an integer or an I2C bus name
ADDRESS is an integer (0x03 - 0x77)
MODE is one of:
b (read byte data, default)
w (read word data)
c (write byte/read byte)
Append p for SMBus PEC

 

1 바이트 읽기

0번 버스의 0x4f 디바이스에서 offset 0x0b에 해당하는 바이트를 읽어온다.

$ i2cget -f -y 0 0x4c 0x0b
0x0f

 

i2cset

명령어 사용법
$ i2cset
Usage: i2cset [-f] [-y] [-m MASK] I2CBUS CHIP-ADDRESS DATA-ADDRESS [VALUE] ... [MODE]
I2CBUS is an integer or an I2C bus name
ADDRESS is an integer (0x03 - 0x77)
MODE is one of:
c (byte, no value)
b (byte data, default)
w (word data)
i (I2C block data)
s (SMBus block data)
Append p for SMBus PEC

 

1바이트 쓰기

1번 버스의 0x72 디바이스 0 offset에 0x01을 기록한다. (i2c 스위치에 0번 채널을 선택하게 한다.)

$ i2cset -f -y 1 0x72 0 0x01

 

i2c trace

i2c를 trace하기 위해 ftrace를 사용해본다. ftrace를 사용하기 위해 다음의 커널 옵션이 사용된다.

  • CONFIG_FTRACE
  • CONFIG_ENABLE_DEFAULT_TRACERS

 

먼저 tracefs를 /sys/kernel/tracing 디렉토리에 마운트한다.

$ mount -t tracefs nodev /sys/kernel/tracing

 

마운트한  후 해당 디렉토리를 살펴보면 다음과 같다.

# cd /sys/kernel/tracing/
# ls -la
total 0
drwx------    6 root     root             0 Jan  1 09:00 .
drwxr-xr-x    8 root     root             0 Jan  1 09:31 ..
-r--r--r--    1 root     root             0 Jan  1 09:00 README
-r--r--r--    1 root     root             0 Jan  1 09:00 available_events
-r--r--r--    1 root     root             0 Jan  1 09:00 available_tracers
-rw-r--r--    1 root     root             0 Jan  1 09:00 buffer_size_kb
-r--r--r--    1 root     root             0 Jan  1 09:00 buffer_total_size_kb
-rw-r--r--    1 root     root             0 Jan  1 09:00 current_tracer
drwxr-xr-x   45 root     root             0 Jan  1 09:00 events
--w-------    1 root     root             0 Jan  1 09:00 free_buffer
drwxr-xr-x    2 root     root             0 Jan  1 09:00 instances
drwxr-xr-x    2 root     root             0 Jan  1 09:00 options
drwxr-xr-x    6 root     root             0 Jan  1 09:00 per_cpu
-r--r--r--    1 root     root             0 Jan  1 09:00 printk_formats
-r--r--r--    1 root     root             0 Jan  1 09:00 saved_cmdlines
-rw-r--r--    1 root     root             0 Jan  1 09:00 saved_cmdlines_size
-rw-r--r--    1 root     root             0 Jan  1 09:00 set_event
-rw-r--r--    1 root     root             0 Jan  1 09:00 set_event_pid
-rw-r--r--    1 root     root             0 Jan  1 09:00 trace
-rw-r--r--    1 root     root             0 Jan  1 09:00 trace_clock
--w--w----    1 root     root             0 Jan  1 09:00 trace_marker
-rw-r--r--    1 root     root             0 Jan  1 09:00 trace_options
-r--r--r--    1 root     root             0 Jan  1 09:00 trace_pipe
-rw-r--r--    1 root     root             0 Jan  1 09:00 tracing_cpumask
-rw-r--r--    1 root     root             0 Jan  1 09:00 tracing_on
-rw-r--r--    1 root     root             0 Jan  1 09:00 tracing_thresh

 

사용할 수 있는 i2c 이벤트를 확인해본다.

$ cat available_events | grep i2c
i2c:smbus_result
i2c:smbus_reply
i2c:smbus_read
i2c:smbus_write
i2c:i2c_result
i2c:i2c_reply
i2c:i2c_read
i2c:i2c_write

 

다음 명령을 통해 i2c 이벤트 전체를 trace 하도록 enable 한다.

$ echo 1 > events/i2c/enable

 

만일 i2c-adapter(i2c-bus 호스트 컨트롤러)가 여러 개 준비되어 있는 경우 그 중 하나만 선택하려면 다음과 같은 명령을 사용할 수 있다.

$ echo adapter_nr==0 > events/i2c/filter

 

다음은 0x72 주소를 사용하는 i2c 스위치 디바이스에 0x80 데이터를 기록하여 총 8개 채널 중 마지막 채널을 연결한다.

  • trace에 0 값을 기록하는 경우 기존 trace 로그를 삭제한다.
$ echo 0 > trace; i2cset -f -y 0 0x72 0x80; cat trace
# tracer: nop
#
# entries-in-buffer/entries-written: 4/4   #P:2
#
#                              _-----=> irqs-off
#                             / _----=> need-resched
#                            | / _---=> hardirq/softirq
#                            || / _--=> preempt-depth
#                            ||| /     delay
#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
#              | |       |   ||||       |         |
          i2cset-168   [000] ....  2027.045809: smbus_write: i2c-0 a=072 f=0000 c=80 BYTE l=0 []
          i2cset-168   [000] ....  2027.045814: i2c_write: i2c-0 #0 a=072 f=0000 l=1 [80]
          i2cset-168   [000] ....  2027.046032: i2c_result: i2c-0 n=1 ret=1
          i2cset-168   [000] ....  2027.046033: smbus_result: i2c-0 a=072 f=0000 c=80 BYTE wr res=0

 

기록된 값을 확인해본다.

$ echo 0 > trace; i2cget -f -y 0 0x72; cat trace
0x80
# tracer: nop
#
# entries-in-buffer/entries-written: 6/6   #P:2
#
#                              _-----=> irqs-off
#                             / _----=> need-resched
#                            | / _---=> hardirq/softirq
#                            || / _--=> preempt-depth
#                            ||| /     delay
#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
#              | |       |   ||||       |         |
          i2cget-176   [000] ....  2326.033639: smbus_read: i2c-0 a=072 f=0000 c=0 BYTE
          i2cget-176   [000] ....  2326.033641: i2c_read: i2c-0 #0 a=072 f=0001 l=1
          i2cget-176   [000] ....  2326.033856: i2c_reply: i2c-0 #0 a=072 f=0001 l=1 [80]
          i2cget-176   [000] ....  2326.033857: i2c_result: i2c-0 n=1 ret=1
          i2cget-176   [000] ....  2326.033857: smbus_reply: i2c-0 a=072 f=0000 c=0 BYTE l=1 [80]
          i2cget-176   [000] ....  2326.033858: smbus_result: i2c-0 a=072 f=0000 c=0 BYTE rd res=0

 

위의 방법과 유사하지만 tracefs를 마운트하지 않고 /sys/kernel/debug/tracing 디렉토리에서 동일한 동작을 할 수도 있다.

 

U-Boot에서 I2C 제어

명령어 헬프
u-boot> i2c
i2c - I2C sub-system

Usage:
i2c crc32 chip address[.0, .1, .2] count - compute CRC32 checksum
i2c dev [dev] - show or set current I2C bus
i2c loop chip address[.0, .1, .2] [# of objects] - looping read of device
i2c md chip address[.0, .1, .2] [# of objects] - read from I2C device
i2c mm chip address[.0, .1, .2] - write to I2C device (auto-incrementing)
i2c mw chip address[.0, .1, .2] value [count] - write to I2C device (fill)
i2c nm chip address[.0, .1, .2] - write to I2C device (constant address)
i2c probe [address] - test for and show device(s) on the I2C bus
i2c read chip address[.0, .1, .2] length memaddress - read to memory
i2c write memaddress chip address[.0, .1, .2] length [-s] - write memory
          to I2C; the -s option selects bulk write in a single transaction
i2c reset - re-init the I2C Controller
i2c speed [speed] - show or set I2C bus speed

 

버스 선택 및 probe
u-boot> i2c dev 0
Setting bus to 0
u-boot> i2c probe
Valid chip addresses: 00 4C 68 70 72

 

u-boot> i2c dev 1
Setting bus to 1
u-boot> i2c probe
Valid chip addresses: 4F

 

데이터 덤프
u-boot> i2c md 0x4f 0
0000: 22 11 ff 00 00 ff 00 00 ff 00 ff 0f 00 00 00 00 "............…

u-boot> i2c md 0x4f 2 1
0002: ff

 

데이터 기록(0x4f 디바이스 2 offset에 0xf7 기록)
u-boot> i2c mw 0x4f 2 f7

 

참고

 

 

Device & Driver -3- (Platform Device)

<kernel v4.14>

플랫폼 디바이스

리눅스 커널에서 기능 별로 분류한 디바이스와 전혀 다르게 플랫폼 디바이스라고 불리는 디바이스가 있다. 플랫폼은 anonymous(virtual) 버스와 비슷한 개념이다. 이러한 플랫폼에 연결되는 디바이스가 플랫폼 디바이스인데 일반 디바이스와 다른 것이 무엇인지 알아보자.

플랫폼 버스와 연결된 플랫폼 디바이스와 해당 드라이버들이 있다.  플랫폼 디바이스가 일반 디바이스와 다르게 불리는 이유를 알아보자.

  • 플랫폼 디바이스는 SoC 내부에 임베드된 디바이스 또는 버스 디바이스로 cpu로 부터 직접 주소 지정이 가능한 특성을 가진다.
    • 플랫폼 디바이스의 레지스터를 가상 주소 공간에 매핑하여 직접 조작할 수 있다.
  • 플랫폼 디바이스는 디바이스의 시작(probe) 시점이 정해지지 않은 디바이스이다.
    • 자동 발견되지 않으므로 누군가 지정해줘야 하는 디바이스가 플랫폼 디바이스이다.
    • 누가 플랫폼 디바이스를 등록시킬까?
      • 디바이스 트리를 사용하기 전에는 보드 특정(board, machine 또는 architect specific) 셋업 코드에서 플랫폼 디바이스를 등록시켰다.
      • 디바이스 트리를 사용하는 최신 리눅스 커널은 대부분 디바이스 트리를 통해서 등록된다.
  • 플러그 가능한 PCI 및 USB 버스에 연결되는 디바이스는 플랫폼 디바이스가 아니다.
    • 참고로 pci 디바이스의 인식 및 가동 정보는 pci 자체적으로 파악할 수 있으므로 반드시 디바이스 트리를 사용할 필요가 없다.
  • 플랫폼 디바이스의 등록은 커널 부팅 시 매우 빠른(early) 시간에 해야한다.
  • 플랫폼 드라이버의 등록은 In-kernel 드라이버인 경우 커널 부팅 후에 하고 모듈 방식인 경우 모듈 로딩 시에 수행된다.

 

다음 그림과 같이 4개의 플랫폼 디바이스의 위치를 확인할 수 있다.

  • 다음과 같이 가정할 때 두 개의 i2c bus controller 중 A는 플랫폼 디바이스로, 그리고 C는 pci 디바이스로 등록해야한다.
    • bus controller A가 ic2 bus controller
    • bus controller B가 pci bus controller
    • bus controller C가 i2c bus controller

 

platform_device 구조체

플랫폼 디바이스를 등록하기 위해 아래의 platform_device 구조체를 등록한다. 디바이스 구조체를 임베드한 platform_device 구조체를 알아보자.

include/linux/platform_device.h

struct platform_device {                                                        
    const char  *name;                                                          
    int     id;                                                                 
    bool        id_auto;                                                        
    struct device   dev;                                                        
    u32     num_resources;                                                      
    struct resource *resource;                                                  
                                                                                
    const struct platform_device_id *id_entry;                                  
    char *driver_override; /* Driver name to force a match */                   
                                                                                
    /* MFD cell pointer */                                                      
    struct mfd_cell *mfd_cell;                                                  
                                                                                
    /* arch specific additions */                                               
    struct pdev_archdata    archdata;                                           
};

플랫폼 디바이스를 정의하기 위해 기본적으로 이름과 id 및 리소스들과 리소스의 수가 주어질 수 있다.

  • *name
    • 플랫폼 디바이스 이름
  • id
    • 전체 플랫폼이 아닌 해당 플랫폼 디바이스에서 유니크한 값
    • -1인 경우 하나밖에 없는 경우이다.
    • 두 개 이상의 디바이스 인스턴스가 주어진 경우 0부터 시작한다.
      • 예) serial/0 또는 serial.0
  • id_auto
    • id 발급을 자동으로할지 여부
  • dev
    • 디바이스 구조체가 임베드된다.
  • num_resources
    • 포함된 리소스의 수
  • *resource
    • 플랫폼 디바이스 고유의 리소스 데이터로 irq 또는 시작 주소와 사이즈를 갖는 리소스 값들

 

플랫폼 디바이스 등록

디바이스 드라이버 모듈을 따른 플랫폼 디바이스의 등록은 이전 32bit arm에서 많은 수의 보드 특정(board, machine 또는 architect specific) 셋업 코드에 아래의 플랫폼 디바이스 등록 API 코드들을 사용해왔다. 현재 디바이스 트리를 사용하는 arm64 커널은 arch-specific 셋업 코드를 사용하지 않고 디바이스 트리가 제공하는 정보를 파싱하여 각 성격에 맞는 디바이스로 등록된다. 참고로 이 과정에서 soc 디렉토리 뒤에 붙은 첫 디바이스들은 당연히 플랫폼 디바이스로 등록된다.

  • int platform_device_register(struct platform_device *pdev);
    • 플랫폼 디바이스를 등록한다.
  • int platform_add_devices(struct platform_device **pdevs, int ndev);
    • 다수의 플랫폼 디바이스를 등록한다.

 

Legacy 플랫폼 디바이스 등록

디바이스 드라이버 모듈을 따르지 않는 기존 플랫폼 디바이스는 모듈(핫플러그)로 등록될 수 없다. Legacy 드라이버에서 다음 API의 사용을 피하고 두 번째 API를 사용하는 것을 권장한다.

    • 직접 사용을 피해야 할 API
      • platform_device_alloc()
  • 권장 API
    • platform_device_register_simple()

 

다음과 같이 시스템에 등록된 플랫폼 디바이스들을 알아보려면 “/sys/devices/platform” 디렉토리를 확인한다.

$ ls /sys/devices/platform -la
drwxr-xr-x  3 root root    0 May 29 13:07 0.flash
drwxr-xr-x  3 root root    0 May 29 13:07 0.gpio
drwxr-xr-x  4 root root    0 May 29 13:07 3f000000.pcie
drwxr-xr-x  4 root root    0 May 29 13:07 9000000.pl011
drwxr-xr-x  4 root root    0 May 29 13:07 9010000.pl031
drwxr-xr-x  3 root root    0 May 29 13:07 9020000.fw-cfg
drwxr-xr-x  4 root root    0 May 29 13:07 Fixed MDIO bus.0
drwxr-xr-x  3 root root    0 May 29 13:07 a000000.virtio_mmio
drwxr-xr-x  3 root root    0 May 29 13:07 a000200.virtio_mmio
...
drwxr-xr-x  4 root root    0 May 29 13:07 a003e00.virtio_mmio
drwxr-xr-x  3 root root    0 May 29 13:07 alarmtimer
drwxr-xr-x  3 root root    0 May 29 13:07 gpio-keys
drwxr-xr-x  3 root root    0 May 29 13:07 platform@c000000
drwxr-xr-x  3 root root    0 May 29 13:07 pmu
drwxr-xr-x  2 root root    0 May 29 13:16 power
drwxr-xr-x  3 root root    0 May 29 13:07 psci
drwxr-xr-x  4 root root    0 May 29 13:07 reg-dummy
drwxr-xr-x  4 root root    0 May 29 13:07 serial8250
drwxr-xr-x  3 root root    0 May 29 13:07 snd-soc-dummy
drwxr-xr-x  3 root root    0 May 29 13:07 timer

 

플랫폼 리소스 정보

플랫폼 디바이스를 등록할 때 저장할 플랫폼 리소스 데이터가 주어질 수 있다.  추후 플랫폼 드라이버가 probe되어 호출될 때 이 정보를 사용하여 HW 설정을 한다. 플랫폼 디바이스를 위해 저장될 수 있는 리소스 정보는 다음과 같다.

  • io
    • io 시작 포트 및 끝 포트
    • 디바이스 트리 노드의 reg 속성에서 읽는 값들
  • mem
    • memory 시작 주소 및 끝 주소
  • irq
    • irq 번호
    • 디바이스 트리 노드의 interrupts 및 interrupt-names 속성에서 읽는 값들
  • dma
    • dma 채널 번호
  • bus
    • 버스 시작 주소 및 끝 주소

 

플랫폼 드라이버

표준 드라이버 모델은 드라이버 외부로부터 발견(discovery) 및 열거(enumeration)가 가능하므로 플랫폼 드라이버 역시 (*probe) 후크 함수와 (*remove) 후크 함수등이 구현되어야 한다. 추가로 디바이스에 절전 기능이  있는 경우 해당 후크업 함수들도 구현되어야 한다.

 

platfrom_driver 구조체

include/linux/platform_device.h

struct platform_driver {                                                        
    int (*probe)(struct platform_device *);                                     
    int (*remove)(struct platform_device *);                                    
    void (*shutdown)(struct platform_device *);                                 
    int (*suspend)(struct platform_device *, pm_message_t state);               
    int (*resume)(struct platform_device *);                                    
    struct device_driver driver;                                                
    const struct platform_device_id *id_table;                                  
    bool prevent_deferred_probe;                                                
};
  • (*probe)
    • HW 디바이스 존재 유무를 판단하기 위해 호출되고 해당 디바이스를 사용하기 위해 디바이스 드라이버와 바인드한다.
    • 디바이스 리소스를 사용하여 irq, 레지스터 매핑등을 할 수 있다.
  • (*remove)
    • 디바이스의 사용을 완료시킨 경우 호출된다.
  • (*shutdown)
    • 디바이스의 전원을 끄려고할 때 호출된다.
  • (*suspend)
    • 디바이스가 절전 모드로 진입할 때 호출된다.
  • (*resume)
    • 디바이스가 절전 모드로부터 정상 모드로 돌아올 때 호출된다.
  • driver
    • 디바이스 드라이버 구조체가 임베드된다.
  • *id_table
    • 식별용 디바이스 id
  • prevent_deferred_probe
    • probe 유예 금지

 

플랫폼 드라이버의 등록

플랫폼 드라이버를 등록하는 방법은 다음과 같이 몇 가지가 있다.

  • int platform_driver_register(struct platform_driver *drv);
    • 일반적으로 사용하는 API이다.
  • int platform_driver_probe(struct platform_driver *drv, int (*probe)(struct platform_device *))
    • 모듈(핫플러그) 형태의 디바이스 드라이버가 아닌 경우 이 API를 사용하면 메모리 foot-print를 줄일 수 있다.
    • 이 함수 코드가 .init 섹션에 등록시키므로 커널이 부트업한 후 .init 섹션을 모두 할당해제하여 메모리 사용량을 줄인다.
  • #define platform_register_drivers(drivers, count)
    • int __platform_register_drivers(struct platform_driver * const *drivers, unsigned int count, struct module *owner);
    • 복수의 디바이스 드라이버들을 등록하는 API이다. 만일 하나라도 디바이스 드라이버의 등록이 실패하면 한꺼번에 해제시킨다.

 

Early 플랫폼 디바이스 및 드라이버

예를 들어 “earlyprintk”를 지원하기 위해 early 플랫폼 디바이스로 early 시리얼 콘솔을 사용할 수 있도록 커널은 6 단계의 스텝을 수행하였다.

  1. eraly 플랫폼 디바이스 데이터 등록
  2. 커널 커멘드 라인 파싱
  3. 해당 클래스로의 early 플랫픔 드라이버 설치
  4. early 플랫폼 드라이버 등록
  5. 해당 클래스에서 early 플랫폼 드라이버의 probing
  6. early 플랫폼 드라이버 probe 내부

 

플랫폼 디바이스 등록

 

다음 그림은 플랫폼 디바이스를 등록하는 과정을 보여준다.

  • 커널 부트업 시 몇 개의 등록된 initcall 함수를 통해 플랫폼 디바이스가 등록된다.
  • of_platform_default_populate_init() 함수를 통해 디바이스 트리를 통해서 등록되는 플랫폼 디바이스가 더 많다.

 

다음 그림은 플랫폼 디바이스와 플랫폼 드라이버의 등록 및 바인딩하는 모습을 보여준다.

 

platform_device_register()

drivers/base/platform.c

/**                                                                             
 * platform_device_register - add a platform-level device                       
 * @pdev: platform device we're adding                                          
 */                                                                             
int platform_device_register(struct platform_device *pdev)                      
{                                                                               
    device_initialize(&pdev->dev);                                              
    arch_setup_pdev_archdata(pdev);                                             
    return platform_device_add(pdev);                                           
}                                                                               
EXPORT_SYMBOL_GPL(platform_device_register)

인자로 주어진 플랫폼 디바이스를 등록한다.

  • 코드 라인 7에서 플랫폼 디바이스 내부의 디바이스 구조체의 멤버들을 초기화한다.
  • 코드 라인 8에서 플랫폼 디바이스의 archdata 조작이 필요한 경우 호출하는 함수이다.
    • 현재는 ppc 아키텍처에서만 사용하고 있다.
  • 코드 라인 9에서 플랫폼 디바이스를 추가한다.

 

device_initialize()

drivers/base/core.c

/**                                                                             
 * device_initialize - init device structure.                                   
 * @dev: device.                                                                
 *                                                                              
 * This prepares the device for use by other layers by initializing             
 * its fields.                                                                  
 * It is the first half of device_register(), if called by                      
 * that function, though it can also be called separately, so one               
 * may use @dev's fields. In particular, get_device()/put_device()              
 * may be used for reference counting of @dev after calling this                
 * function.                                                                    
 *                                                                              
 * All fields in @dev must be initialized by the caller to 0, except            
 * for those explicitly set to some other value.  The simplest                  
 * approach is to use kzalloc() to allocate the structure containing            
 * @dev.                                                                        
 *                                                                              
 * NOTE: Use put_device() to give up your reference instead of freeing          
 * @dev directly once you have called this function.                            
 */                                                                             
void device_initialize(struct device *dev)                                      
{                                                                               
    dev->kobj.kset = devices_kset;                                              
    kobject_init(&dev->kobj, &device_ktype);                                    
    INIT_LIST_HEAD(&dev->dma_pools);                                            
    mutex_init(&dev->mutex);                                                    
    lockdep_set_novalidate_class(&dev->mutex);                                  
    spin_lock_init(&dev->devres_lock);                                          
    INIT_LIST_HEAD(&dev->devres_head);                                          
    device_pm_init(dev);                                                        
    set_dev_node(dev, -1);                                                      
#ifdef CONFIG_GENERIC_MSI_IRQ                                                   
    INIT_LIST_HEAD(&dev->msi_list);                                             
#endif                                                                          
    INIT_LIST_HEAD(&dev->links.consumers);                                      
    INIT_LIST_HEAD(&dev->links.suppliers);                                      
    dev->links.status = DL_DEV_NO_DRIVER;                                       
}                                                                               
EXPORT_SYMBOL_GPL(device_initialize);

디바이스 구조체의 멤버들을 초기화한다.

 

platform_device_add()

drivers/base/platform.c

/**                                                                             
 * platform_device_add - add a platform device to device hierarchy              
 * @pdev: platform device we're adding                                          
 *                                                                              
 * This is part 2 of platform_device_register(), though may be called           
 * separately _iff_ pdev was allocated by platform_device_alloc().              
 */                                                                             
int platform_device_add(struct platform_device *pdev)                           
{                                                                               
    int i, ret;                                                                 
                                                                                
    if (!pdev)                                                                  
        return -EINVAL;                                                         
                                                                                
    if (!pdev->dev.parent)                                                      
        pdev->dev.parent = &platform_bus;                                       
                                                                                
    pdev->dev.bus = &platform_bus_type;                                         
                                                                                
    switch (pdev->id) {                                                         
    default:                                                                    
        dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id);               
        break;                                                                  
    case PLATFORM_DEVID_NONE:                                                   
        dev_set_name(&pdev->dev, "%s", pdev->name);                             
        break;                                                                  
    case PLATFORM_DEVID_AUTO:                                                   
        /*                                                                      
         * Automatically allocated device ID. We mark it as such so             
         * that we remember it must be freed, and we append a suffix            
         * to avoid namespace collision with explicit IDs.                      
         */                                                                     
        ret = ida_simple_get(&platform_devid_ida, 0, 0, GFP_KERNEL);            
        if (ret < 0)                                                            
            goto err_out;                                                       
        pdev->id = ret;                                                         
        pdev->id_auto = true;                                                   
        dev_set_name(&pdev->dev, "%s.%d.auto", pdev->name, pdev->id);           
        break;                                                                  
    }

플랫폼 디바이스를 등록한다.

  • 코드 라인 15~16에서 부모 디바이스가 지정되지 않은 경우 플랫폼 버스를 지정한다.
  • 코드 라인 18에서 디바이스에 플랫폼 버스타입을 지정한다.
  • 코드 라인 20~40에서 디바이스 id를 포함하여 디바이스명을 지정한다.
    • pdev->id가 주어진 경우
      • 예) foo.1
    • id가 없는 경우, pdev->id = PLATFORM_DEVID_NONE
      • 예) foo
    • id를 자동 생성하는 경우, pdev->id = PLATFORM_DEVID_AUTO
      • 예) foo.1.auto

 

    for (i = 0; i < pdev->num_resources; i++) {                                 
        struct resource *p, *r = &pdev->resource[i];                            
                                                                                
        if (r->name == NULL)                                                    
            r->name = dev_name(&pdev->dev);                                     
                                                                                
        p = r->parent;                                                          
        if (!p) {                                                               
            if (resource_type(r) == IORESOURCE_MEM)                             
                p = &iomem_resource;                                            
            else if (resource_type(r) == IORESOURCE_IO)                         
                p = &ioport_resource;                                           
        }                                                                       
                                                                                
        if (p && insert_resource(p, r)) {                                       
            dev_err(&pdev->dev, "failed to claim resource %d: %pR\n", i, r);    
            ret = -EBUSY;                                                       
            goto failed;                                                        
        }                                                                       
    }                                                                           
                                                                                
    pr_debug("Registering platform device '%s'. Parent at %s\n",                
         dev_name(&pdev->dev), dev_name(pdev->dev.parent));                     
                                                                                
    ret = device_add(&pdev->dev);                                               
    if (ret == 0)                                                               
        return ret;                                                             
                                                                                
 failed:                                                                        
    if (pdev->id_auto) {                                                        
        ida_simple_remove(&platform_devid_ida, pdev->id);                       
        pdev->id = PLATFORM_DEVID_AUTO;                                         
    }                                                                           
                                                                                
    while (--i >= 0) {                                                          
        struct resource *r = &pdev->resource[i];                                
        if (r->parent)                                                          
            release_resource(r);                                                
    }                                                                           
                                                                                
 err_out:                                                                       
    return ret;                                                                 
}                                                                               
EXPORT_SYMBOL_GPL(platform_device_add);
  • 코드 라인 1~5에서 플랫폼 디바이스에 지정된 리소스를 대상으로 손회하며 리소스 명이 없는 경우 플랫폼 디바이스 명을 사용한다.
  • 코드 라인 7~20에서 부모 리소스의 현재 리소스 정보의 추가를 시도한다. 만일 부모 리소스가 없으면 리소스의 타입에 따라 최상위 리소스 트리인 iomem_resource 또는 ioport_resource를 사용한다.
  • 코드 라인 25~27에서 디바이스를 추가한다.

 

디바이스 트리를 사용한 플랫폼 디바이스 등록

 

아래 그림은 디바이스 트리에서 플랫폼 디바이스로 등록되는 노드와 그렇지 않은 노드를 보여준다.

  • 플랫폼 디바이스로 등록 가능한 노드
    • 부모 노드가 루트 노드인 경우
    • 부모 노드가 플랫폼 버스인 경우

 

다음 그림은 디바이스 트리 노드를 읽어 플랫폼 디바이스로 등록할 때 플랫폼 리소스 정보도 같이 수집하여 등록되는 과정을 보여준다.

  • 아래 예와 같이 메모리 정보와 irq 정보를 플랫폼 디바이스의 리소스로 등록한다.

 

다음 그림은 Legacy irq 대신 MSI를 사용하는 플랫폼 디바이스를 등록하는 과정을 보여준다.

  • 대부분의 MSI는 PCI 디바이스가 사용한다. 플랫폼 디바이스가 사용하는 매우 드물다.

 

of_platform_default_populate_init()

drivers/of/platform.c

static int __init of_platform_default_populate_init(void)                       
{                                                                               
    struct device_node *node;                                                   
                                                                                
    if (!of_have_populated_dt())                                                
        return -ENODEV;                                                         
                                                                                
    /*                                                                          
     * Handle ramoops explicitly, since it is inside /reserved-memory,          
     * which lacks a "compatible" property.                                     
     */                                                                         
    node = of_find_node_by_path("/reserved-memory");                            
    if (node) {                                                                 
        node = of_find_compatible_node(node, NULL, "ramoops");                  
        if (node)                                                               
            of_platform_device_create(node, NULL, NULL);                        
    }                                                                           
                                                                                
    /* Populate everything else. */                                             
    of_platform_default_populate(NULL, NULL, NULL);                             
                                                                                
    return 0;                                                                   
}                                                                               
arch_initcall_sync(of_platform_default_populate_init);

다비이스 트리를 파싱하여 플랫폼 디바이스들을 생성하여 등록한다.

  • 코드 라인 5~6에서 디바이스 트리가 없는 경우 -ENODEV 결과를 반환한다.
  • 코드 라인 12~17에서 “/reserved-memory” 노드가 발견된 경우 그 노드 안에 “ramoops” 디바이스명이 있으면 “ramoops”  디바이스를 생성하여 등록한다.
  • 코드 라인 20에서 디바이스 트리를 모두 파싱하여 플랫폼 디바이스를 생성하고 등록한다.

 

of_platform_default_populate()

drivers/of/platform.c

int of_platform_default_populate(struct device_node *root,                      
                 const struct of_dev_auxdata *lookup,                           
                 struct device *parent)                                         
{                                                                               
    return of_platform_populate(root, of_default_bus_match_table, lookup,       
                    parent);                                                    
}                                                                               
EXPORT_SYMBOL_GPL(of_platform_default_populate);

디바이스 트리를 루트부터 모두 파싱하여 아래 4개의 기본 버스 타입에 연결된 디바이스를 플랫폼 디바이스로 인식하여 생성하고 등록한다.

  • “simple-bus”
  • “simple-mfd”
  • “isa”
  • “arm,amba-bus”

of_default_bus_match_table[]

drivers/of/platform.c

const struct of_device_id of_default_bus_match_table[] = {                      
    { .compatible = "simple-bus", },                                            
    { .compatible = "simple-mfd", },                                            
    { .compatible = "isa", },                                                   
#ifdef CONFIG_ARM_AMBA                                                          
    { .compatible = "arm,amba-bus", },                                          
#endif /* CONFIG_ARM_AMBA */                                                    
    {} /* Empty terminated list */                                              
};

 

of_platform_populate()

drivers/of/platform.c

/**                                                                             
 * of_platform_populate() - Populate platform_devices from device tree data     
 * @root: parent of the first level to probe or NULL for the root of the tree   
 * @matches: match table, NULL to use the default                               
 * @lookup: auxdata table for matching id and platform_data with device nodes   
 * @parent: parent to hook devices from, NULL for toplevel                      
 *                                                                              
 * Similar to of_platform_bus_probe(), this function walks the device tree      
 * and creates devices from nodes.  It differs in that it follows the modern    
 * convention of requiring all device nodes to have a 'compatible' property,    
 * and it is suitable for creating devices which are children of the root       
 * node (of_platform_bus_probe will only create children of the root which      
 * are selected by the @matches argument).                                      
 *                                                                              
 * New board support should be using this function instead of                   
 * of_platform_bus_probe().                                                     
 *                                                                              
 * Returns 0 on success, < 0 on failure.                                        
 */
int of_platform_populate(struct device_node *root,                              
            const struct of_device_id *matches,                                 
            const struct of_dev_auxdata *lookup,                                
            struct device *parent)                                              
{                                                                               
    struct device_node *child;                                                  
    int rc = 0;                                                                 
                                                                                
    root = root ? of_node_get(root) : of_find_node_by_path("/");                
    if (!root)                                                                  
        return -EINVAL;                                                         
                                                                                
    pr_debug("%s()\n", __func__);                                               
    pr_debug(" starting at: %pOF\n", root);                                     
                                                                                
    for_each_child_of_node(root, child) {                                       
        rc = of_platform_bus_create(child, matches, lookup, parent, true);      
        if (rc) {                                                               
            of_node_put(child);                                                 
            break;                                                              
        }                                                                       
    }                                                                           
    of_node_set_flag(root, OF_POPULATED_BUS);                                   
                                                                                
    of_node_put(root);                                                          
    return rc;                                                                  
}                                                                               
EXPORT_SYMBOL_GPL(of_platform_populate);

디바이스 트리를 첫 번째 인자로 요청한 노드부터 시작하여 그 이하 노드를 파싱한다. 두 번째 인자로 요청한 버스 노드와 버스 매치 조건인 경우 자녀 노드들도 플랫폼 디바이스로 생성하고 등록한다.

  • 코드 라인 9~11에서 첫 번째 인자가 정상적으로 주어지지 않은 경우 -EINVAL 결과를 반환한다.
  • 코드 라인 16~22에서 두 번째 인자로 요청한 버스 노드와 버스 매치 조건인 경우 자녀 노드들도 플랫폼 디바이스로 생성하고 등록한다.
  • 코드 라인 23에서 첫 번째 인자로 요청한 노드에 OF_POPULATED_BUS 플래그를 설정하여 버스를 이미 커널에 등록하였음을 나타낸다.

 

of_platform_bus_create()

drivers/of/platform.c

/**                                                                             
 * of_platform_bus_create() - Create a device for a node and its children.      
 * @bus: device node of the bus to instantiate                                  
 * @matches: match table for bus nodes                                          
 * @lookup: auxdata table for matching id and platform_data with device nodes   
 * @parent: parent for new device, or NULL for top level.                       
 * @strict: require compatible property                                         
 *                                                                              
 * Creates a platform_device for the provided device_node, and optionally       
 * recursively create devices for all the child nodes.                          
 */                                                                             
static int of_platform_bus_create(struct device_node *bus,                      
                  const struct of_device_id *matches,                           
                  const struct of_dev_auxdata *lookup,                          
                  struct device *parent, bool strict)                           
{                                                                               
    const struct of_dev_auxdata *auxdata;                                       
    struct device_node *child;                                                  
    struct platform_device *dev;                                                
    const char *bus_id = NULL;                                                  
    void *platform_data = NULL;                                                 
    int rc = 0;                                                                 
                                                                                
    /* Make sure it has a compatible property */                                
    if (strict && (!of_get_property(bus, "compatible", NULL))) {                
        pr_debug("%s() - skipping %pOF, no compatible prop\n",                  
             __func__, bus);                                                    
        return 0;                                                               
    }                                                                           
                                                                                
    if (of_node_check_flag(bus, OF_POPULATED_BUS)) {                            
        pr_debug("%s() - skipping %pOF, already populated\n",                   
            __func__, bus);                                                     
        return 0;                                                               
    }                                                                           
                                                                                
    auxdata = of_dev_lookup(lookup, bus);                                       
    if (auxdata) {                                                              
        bus_id = auxdata->name;                                                 
        platform_data = auxdata->platform_data;                                 
    }                                                                           
                                                                                
    if (of_device_is_compatible(bus, "arm,primecell")) {                        
        /*                                                                      
         * Don't return an error here to keep compatibility with older          
         * device tree files.                                                   
         */                                                                     
        of_amba_device_create(bus, bus_id, platform_data, parent);              
        return 0;                                                               
    }                                                                           
                                                                                
    dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);  
    if (!dev || !of_match_node(matches, bus))                                   
        return 0;                                                               
                                                                                
    for_each_child_of_node(bus, child) {                                        
        pr_debug("   create child: %pOF\n", child);                             
        rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict); 
        if (rc) {                                                               
            of_node_put(child);                                                 
            break;                                                              
        }                                                                       
    }                                                                           
    of_node_set_flag(bus, OF_POPULATED_BUS);                                    
    return rc; 
}

첫 번째 인자로 요청한 버스 노드와 버스 매치 조건인 경우 자녀 노드들도 플랫폼 디바이스로 생성하고 등록한다.

  • 코드 라인 25~29에서 다섯 번째 인자가 true로 주어진 경우 “compatible” 속성이 발견되지 않으면 skip 하기 위해 성공(0)을 반환한다.
  • 코드 라인 31~35에서 이 버스가 이미 커널에 등록된 경우 skip 하기 위해 성공(0)을 반환한다.
  • 코드 라인 37~41에서 lookup 테이블에서 검색하여 플랫폼 데이터를 알아온다. lookup이 지정되지 않은 경우 아무런 일도 하지 않는다.
    • lookup table은 machine-specific 코드 셋업 루틴을 사용하는 디바이스 드라이버에서 사용되었지만 지금은 거의 사용하지 않는다.
  • 코드 라인 43~50에서 버스 노드의 compatible명이 “arm,primecell”인 경우 amba 버스 디바이스를 생성하기 위해 별도의 처리 함수를 사용한다. 그런 후 정상(0) 결과로 빠져나간다.
  • 코드 라인 52~54에서 플랫폼 디바이스를 생성하고 등록한다. 만일 생성이 실패하거나 버스가 매치되지 않은 경우 서브 디렉토리를 skip하기 위해 성공(0)을 반환한다.
    • 결국 부모 노드가 루트 노드인 경우 플랫폼 디바이스로 등록된다.
    • 부모 노드가 플랫폼 버스가 아닌 경우 그 이하의 서브 디렉토리들은 플랫폼 디바이스로 등록되지 않는다.
  • 코드 라인 56~63에서 버스 노드 아래 child 노드를 대상으로 이 함수를 재귀호출한다.
  • 코드 라인 64에서 요청한 노드에 OF_POPULATED_BUS 플래그를 설정하여 버스를 이미 커널에 등록하였음을 나타낸다.

 

auxdata lookup 테이블 예)
static struct of_dev_auxdata foo_auxdata_lookup[] __initdata = {            
    OF_DEV_AUXDATA("foo", 0xf1010600, "foo.0", NULL),       
    {},                                                                         
};
  • of_dev_auxdata 구조체에 사용되는 데이터는 compatible 디바이스명, 물리주소, 노드명, 플랫폼 데이터

 

of_platform_device_create_pdata()

drivers/of/platform.c

static struct platform_device *of_platform_device_create_pdata(                 
                    struct device_node *np,                                     
                    const char *bus_id,                                         
                    void *platform_data,                                        
                    struct device *parent)                                      
{                                                                               
    struct platform_device *dev;                                                
                                                                                
    if (!of_device_is_available(np) ||                                          
        of_node_test_and_set_flag(np, OF_POPULATED))                            
        return NULL;                                                            
                                                                                
    dev = of_device_alloc(np, bus_id, parent);                                  
    if (!dev)                                                                   
        goto err_clear_flag;                                                    
                                                                                
    dev->dev.bus = &platform_bus_type;                                          
    dev->dev.platform_data = platform_data;                                     
    of_msi_configure(&dev->dev, dev->dev.of_node);                              
                                                                                
    if (of_device_add(dev) != 0) {                                              
        platform_device_put(dev);                                               
        goto err_clear_flag;                                                    
    }                                                                           
                                                                                
    return dev;                                                                 
                                                                                
err_clear_flag:                                                                 
    of_node_clear_flag(np, OF_POPULATED);                                       
    return NULL;                                                                
}

플랫폼 디바이스를 생성하고 디바이스 트리로부터 msi 정보 및 플랫폼 리소스를 읽어 지정한 후 플랫폼 디바이스로 등록한다.

  • 코드 라인 8~11에서 디바이스가 준비되지 않은 경우 또는 노드가 이미 등록된 경우 null을 반환한다.
  • 코드 라인 13~15에서 디바이스를 생성한다.
  • 코드 라인 17~19에서 플랫폼 버스 타입과, 플랫폼 데이터를 지정한다. 그리고 디바이스 트리에서 msi 정보를 읽어 디바이스에 설정한다.
  • 코드 라인 21~24에서 플랫폼 디바이스를 생성하고 디바이스 트리로부터 플랫폼 리소스를 읽어 지정한 후 플랫폼 디바이스로 등록한다.

 

of_device_alloc()

drivers/of/platform.c

/**                                                                             
 * of_device_alloc - Allocate and initialize an of_device                       
 * @np: device node to assign to device                                         
 * @bus_id: Name to assign to the device.  May be null to use default name.     
 * @parent: Parent device.                                                      
 */                                                                             
struct platform_device *of_device_alloc(struct device_node *np,                 
                  const char *bus_id,                                           
                  struct device *parent)                                        
{                                                                               
    struct platform_device *dev;                                                
    int rc, i, num_reg = 0, num_irq;                                            
    struct resource *res, temp_res;                                             
                                                                                
    dev = platform_device_alloc("", PLATFORM_DEVID_NONE);                       
    if (!dev)                                                                   
        return NULL;                                                            
                                                                                
    /* count the io and irq resources */                                        
    while (of_address_to_resource(np, num_reg, &temp_res) == 0)                 
        num_reg++;                                                              
    num_irq = of_irq_count(np);                                                 
                                                                                
    /* Populate the resource table */                                           
    if (num_irq || num_reg) {                                                   
        res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL);          
        if (!res) {                                                             
            platform_device_put(dev);                                           
            return NULL;                                                        
        }                                                                       
                                                                                
        dev->num_resources = num_reg + num_irq;                                 
        dev->resource = res;                                                    
        for (i = 0; i < num_reg; i++, res++) {                                  
            rc = of_address_to_resource(np, i, res);                            
            WARN_ON(rc);                                                        
        }                                                                       
        if (of_irq_to_resource_table(np, res, num_irq) != num_irq)              
            pr_debug("not all legacy IRQ resources mapped for %s\n",            
                 np->name);                                                     
    }                                                                           
                                                                                
    dev->dev.of_node = of_node_get(np);                                         
    dev->dev.fwnode = &np->fwnode;                                              
    dev->dev.parent = parent ? : &platform_bus;                                 
                                                                                
    if (bus_id)                                                                 
        dev_set_name(&dev->dev, "%s", bus_id);                                  
    else                                                                        
        of_device_make_bus_id(&dev->dev);                                       
                                                                                
    return dev;                                                                 
}                                                                               
EXPORT_SYMBOL(of_device_alloc);

플랫폼 디바이스를 생성하고 디바이스 트리로부터 플랫폼 리소스를 읽어 지정한 후 플랫폼 디바이스로 등록한다.

  • 코드 라인 15~17에서 플랫폼 디바이스를 할당하되 id는 주어지지 않게 한다.
  • 코드 라인 20~22에서 io 및 irq 리소스의 수를 파악하기 위해 임시로 읽어온다.
    • num_reg에는 노드에서 읽어온 io 주소 수가 담긴다.
    • num_irq에는 노드에서 읽어온 irq 수가 담긴다.
  • 코드 라인 25~41에서 io 및 irq 리소스가 존재하는 경우 그 수 만큼 리소스를 생성하고 노드에서 다시 리소스 정보를 정식으로 읽어 저장한다.
  • 코드 라인 43~45에서 디바이스에 노드 정보와 부모 디바이스 정보를 대입한다. 부모 디바이스가 없는 경우 플랫폼 버스를 지정한다.
  • 코드 라인 47~50에서 디바이스 이름을 bus_id로 설정한다. bus_id가 없는 경우 디바이스 트리 노드를 읽어 지정한다.
    • 디바이스 트리 노드를 읽어 사용한 예) 3f000000.pcie,   platform@c000000,  psci

 

디바이스명 생성 규칙 (for 디바이스 트리)

of_device_make_bus_id()

drivers/of/platform.c

/**                                                                             
 * of_device_make_bus_id - Use the device node data to assign a unique name     
 * @dev: pointer to device structure that is linked to a device tree node       
 *                                                                              
 * This routine will first try using the translated bus address to              
 * derive a unique name. If it cannot, then it will prepend names from          
 * parent nodes until a unique name can be derived.                             
 */                                                                             
static void of_device_make_bus_id(struct device *dev)                           
{                                                                               
    struct device_node *node = dev->of_node;                                    
    const __be32 *reg;                                                          
    u64 addr;                                                                   
                                                                                
    /* Construct the name, using parent nodes if necessary to ensure uniqueness */
    while (node->parent) {                                                      
        /*                                                                      
         * If the address can be translated, then that is as much               
         * uniqueness as we need. Make it the first component and return        
         */                                                                     
        reg = of_get_property(node, "reg", NULL);                               
        if (reg && (addr = of_translate_address(node, reg)) != OF_BAD_ADDR) {   
            dev_set_name(dev, dev_name(dev) ? "%llx.%s:%s" : "%llx.%s",         
                     (unsigned long long)addr, node->name,                      
                     dev_name(dev));                                            
            return;                                                             
        }                                                                       
                                                                                
        /* format arguments only used if dev_name() resolves to NULL */         
        dev_set_name(dev, dev_name(dev) ? "%s:%s" : "%s",                       
                 kbasename(node->full_name), dev_name(dev));                    
        node = node->parent;                                                    
    }                                                                           
}

디바이스 이름을 다음 규칙에 의해 이름을 만들어 지정한다.

1) 노드에 reg 속성이 있는 경우
  • 주어진 디바이스 명 존재 시: <주소>.<노드명>:<주어진 디바이스명>
  • 주어진 디바이스 명 없을 때: <주소>.<노드명>

 

다음 예와 같이 reg 속성이 있고, 주어진 디바이스명이 없는 경우 최종 디바이스명은 “9020000.fw-cfg”가 된다.

fw-cfg@9020000 {                                                            
        dma-coherent;                                                           
        reg = <0x0 0x9020000 0x0 0x18>;                                         
        compatible = "qemu,fw-cfg-mmio";                                        
};  

 

2) 노드에 reg 속성이 없는 경우
  • 주어진 디바이스 명 존재 시: <full_노드명>:<주어진 디바이스명>
  • 주어진 디바이스 명 없을 때: <full_노드명>

 

다음 예와 같이 reg 속성이 없고, 주어진 디바이스명이 없는 경우 최종 디바이스명은 “platform@c000000″가 된다.

platform@c000000 { 
        interrupt-parent = <0x8001>; 
        ranges = <0x0 0x0 0xc000000 0x2000000>; 
        #address-cells = <0x1>; 
        #size-cells = <0x1>; 
        compatible = "qemu,platform", "simple-bus"; 
}; 
 

Dynamic 디바이스 트리

Dynamic하게 디바이스 트리의 정보를 변경 시키고 플랫폼 디바이스의 등록 및 해제 시 호출되는 후크 함수이다. 보통 특정 아키텍처의 테스트 환경을 지원하기 위해 사용된다.

of_platform_notify()

drivers/of/platform.c

static int of_platform_notify(struct notifier_block *nb,                        
                unsigned long action, void *arg)                                
{                                                                               
    struct of_reconfig_data *rd = arg;                                          
    struct platform_device *pdev_parent, *pdev;                                 
    bool children_left;                                                         
                                                                                
    switch (of_reconfig_get_state_change(action, rd)) {                         
    case OF_RECONFIG_CHANGE_ADD:                                                
        /* verify that the parent is a bus */                                   
        if (!of_node_check_flag(rd->dn->parent, OF_POPULATED_BUS))              
            return NOTIFY_OK;   /* not for us */                                
                                                                                
        /* already populated? (driver using of_populate manually) */            
        if (of_node_check_flag(rd->dn, OF_POPULATED))                           
            return NOTIFY_OK;                                                   
                                                                                
        /* pdev_parent may be NULL when no bus platform device */               
        pdev_parent = of_find_device_by_node(rd->dn->parent);                   
        pdev = of_platform_device_create(rd->dn, NULL,                          
                pdev_parent ? &pdev_parent->dev : NULL);                        
        of_dev_put(pdev_parent);                                                
                                                                                
        if (pdev == NULL) {                                                     
            pr_err("%s: failed to create for '%pOF'\n",                         
                    __func__, rd->dn);                                          
            /* of_platform_device_create tosses the error code */               
            return notifier_from_errno(-EINVAL);                                
        }                                                                       
        break;                                                                  
                                                                                
    case OF_RECONFIG_CHANGE_REMOVE:                                             
                                                                                
        /* already depopulated? */                                              
        if (!of_node_check_flag(rd->dn, OF_POPULATED))                          
            return NOTIFY_OK;                                                   
                                                                                
        /* find our device by node */                                           
        pdev = of_find_device_by_node(rd->dn);                                  
        if (pdev == NULL)                                                       
            return NOTIFY_OK;   /* no? not meant for us */                      
                                                                                
        /* unregister takes one ref away */                                     
        of_platform_device_destroy(&pdev->dev, &children_left);                 
                                                                                
        /* and put the reference of the find */                                 
        of_dev_put(pdev);                                                       
        break;                                                                  
    }                                                                           
                                                                                
    return NOTIFY_OK;                                                           
}

디바이스 트리를 통해 플랫폼 디바이스가 등록되거나 호출될 때 사용되는 후크 함수이다.

  • 코드 라인 9~30에서 디바이스 트리에 플랫폼 디바이스가 발견되는 경우 등록한다.
  • 코드 라인 32~48에서 디바이스 트리로부터 등록된 플랫폼 디바이스를 등록 해제한다.

 

of_platform_device_create()

drivers/of/platform.c

/**                                                                             
 * of_platform_device_create - Alloc, initialize and register an of_device      
 * @np: pointer to node to create device for                                    
 * @bus_id: name to assign device                                               
 * @parent: Linux device model parent device.                                   
 *                                                                              
 * Returns pointer to created platform device, or NULL if a device was not      
 * registered.  Unavailable devices will not get registered.                    
 */                                                                             
struct platform_device *of_platform_device_create(struct device_node *np,       
                        const char *bus_id,                                     
                        struct device *parent)                                  
{                                                                               
    return of_platform_device_create_pdata(np, bus_id, NULL, parent);           
}                                                                               
EXPORT_SYMBOL(of_platform_device_create);

플랫폼 디바이스를 생성하고 디바이스 트리로부터 msi 정보 및 플랫폼 리소스를 읽어 저장한 후 플랫폼 디바이스로 등록한다.

  • platform_data만 null로 지정하여 호출한다.

 

플랫폼 드라이버 등록

 

드라이버 모듈 진입부

플랫폼 드라이버 모듈의 마지막에 다음과 같은 매크로 함수 중 하나를 사용하여 플랫폼 드라이버의 등록부 코드를 준비한다.

  • 모든 디바이스 및 드라이버 공통 진입부
    • device_initcall(foo_init)
      • 커널에 임베드하여 빌드한 경우 부트업 시 동작하며 모든 디바이스 및 드라이버의 진입부에 사용된다.
      • 커널에 임베드하지 않고 모듈로 빌드한 경우 module_init()과 동일하게 동작한다.
    • module_init(foo_init) & module_exit(foo_exit)
      • insmod를 사용한 모듈 방식으로 모든 디바이스 및 드라이버의 진입부에 사용된다.
  • 플랫폼 드라이버용 진입부
    • builtin_platform_driver(foo_driver)
      • 커널에 임베드하여 빌드한 경우 부트업 시 동작하며 플랫폼 드라이버 진입부에 사용된다.
      • 커널에 임베드하지 않고 모듈로 빌드한 경우 module_platform_driver()와 동일하게 동작한다.
    • module_platform_driver(foo_driver)
      • insmod를 사용한 모듈 방식으로 플랫폼 드라이버용 진입부에 사용된다.
  • 플랫폼 디바이스 생성 및 플랫폼 드라이버용 진입부
    • builtin_platform_driver_probe(foo_driver, foo_probe)
      • 커널에 임베드하여 빌드한 경우 부트업 시 동작하며 플랫폼 디바이스를 생성하면서 플랫폼 드라이버 진입에 사용된다.
      • 커널에 임베드하지 않고 모듈로 빌드한 경우 module_platform_driver_probe()와 동일하게 동작한다.
    • module_platform_driver_probe(foo_driver, foo_probe)
      • insmod를 사용한 모듈 방식으로 플랫폼 디바이스를 생성하면서 플랫폼 드라이버 진입에 사용된다.

 

플랫폼 드라이버를 등록하기 위해 다음을 준비한다.

  • probe() 함수
  • platform_device_id 구조체
  • platform_driver 구조체
#include <linux/module.h>
#include <linux/of.h>                                                           
#include <linux/of_device.h>
#include <linux/platform_device.h>

static int foo_probe(struct platform_device *pdev)                         
{
        ...
        return 0;
}

static const struct platform_device_id foo_id_table[] = {                      
    { "foo",    (unsigned long) &foo_id },                            
    { },                                                                        
};                                                                              

static const struct of_device_id foo_of_match_table[] = { 
    { 
        .compatible = "foo,foo", 
    }, 
    { /* sentinel */ }, 
}; 
MODULE_DEVICE_TABLE(of, foo_of_match_table);
                                                                                
static struct platform_driver foo_driver = {                               
    .probe      = foo_probe,                                               
    .driver     = {                                                             
        .name   = "foo",                                                   
        .of_match_table = foo_of_match_table,
    },                                                                          
    .id_table   = foo_id_table,                                                
};

 

1) device_initcall() 매크로 함수 사용 시

device_initcall() 함수의 동작은 다음과 같이 내부적으로 두 가지 상황으로 처리된다.

  • 이 드라이버가 커널에 임베드되어(커널의 menuconfig에서 ‘*’ 선택) 로드하는 경우
    • 커널의 부트업 시 initcall 호출 과정에서 foo_driver를 등록한다.
    • 그리고 이에 대한 디바이스도 이미 로드되어 매치 가능한 경우 bind하여 곧바로 foo_probe() 함수를 probe한다.
  • 커널에 임베드되지 않고 모듈 형태로 빌드한(커널의 menuconfig에서 ‘m’ 선택) 후 사용자 영역에서 insmod 명령에 의해 로드하는 경우
    • insmod 명령에 의해 드라이버 모듈을 로드하고 foo_init() 함수를 호출하여 foo_driver를 등록한다.
    • 역시 이에 대한 디바이스도 이미 로드되어 매치 가능한 경우 bind하여 곧바로 foo_probe() 함수를 probe한다.
static int __init foo_init(void)                                        
{                                                                               
        return platform_driver_register(&foo_driver);
}
device_initcall(foo_init);

 

2) module_init() & module_exit() 매크로 함수 사용 시

드라이버를 항상 모듈로 만들어 insmod에 의해서만 드라이버를 로드하려면 device_initcall() 대신 module_init()을 사용한다.

static int __init foo_init(void) { 
        return platform_driver_register(&foo_driver); 
} 
static void __exit foo_exit(void) { 
        platform_driver_unregister(&foo_driver);
} 
module_init(foo_init);
module_exit(foo_exit);

 

3) builtin_platform_driver() 매크로 함수 사용 시

커널 부트업 시 사용할 드라이버를 등록한다. device_initcall()보다 더 심플하게 코딩할 수 있다.

builtin_platform_driver(&foo_driver);

 

/* builtin_platform_driver() - Helper macro for builtin drivers that
 * don't do anything special in driver init.  This eliminates some
 * boilerplate.  Each driver may only use this macro once, and
 * calling it replaces device_initcall().  Note this is meant to be
 * a parallel of module_platform_driver() above, but w/o _exit stuff.
 */
#define builtin_platform_driver(__platform_driver) \
        builtin_driver(__platform_driver, platform_driver_register)

 

/*
 * builtin_driver() - Helper macro for drivers that don't do anything
 * special in init and have no exit. This eliminates some boilerplate.
 * Each driver may only use this macro once, and calling it replaces
 * device_initcall (or in some cases, the legacy __initcall).  This is
 * meant to be a direct parallel of module_driver() above but without
 * the __exit stuff that is not used for builtin cases.
 *
 * @__driver: driver name
 * @__register: register function for this driver type
 * @...: Additional arguments to be passed to __register
 *
 * Use this macro to construct bus specific macros for registering
 * drivers, and do not use it on its own.
 */
#define builtin_driver(__driver, __register, ...) \
static int __init __driver##_init(void) \
{ \
        return __register(&(__driver) , ##__VA_ARGS__); \
} \
device_initcall(__driver##_init);

 

4) module_platform_driver() 매크로 함수 사용 시

위의 module_init() 및 module_exit() 함수를 아래 매크로로 유사하게 만들어 사용하는 방법으로 매우 심플하게 코딩할 수 있다.

module_platform_driver(&foo_driver);

 

include/linux/platform_device.h

/* module_platform_driver() - Helper macro for drivers that don't do            
 * anything special in module init/exit.  This eliminates a lot of              
 * boilerplate.  Each module may only use this macro once, and                  
 * calling it replaces module_init() and module_exit()                          
 */                                                                             
#define module_platform_driver(__platform_driver) \                             
    module_driver(__platform_driver, platform_driver_register, \                
            platform_driver_unregister)

 

module_driver() 매크로 함수
/**                                                                             
 * module_driver() - Helper macro for drivers that don't do anything            
 * special in module init/exit. This eliminates a lot of boilerplate.           
 * Each module may only use this macro once, and calling it replaces            
 * module_init() and module_exit().                                             
 *                                                                              
 * @__driver: driver name                                                       
 * @__register: register function for this driver type                          
 * @__unregister: unregister function for this driver type                      
 * @...: Additional arguments to be passed to __register and __unregister.      
 *                                                                              
 * Use this macro to construct bus specific macros for registering              
 * drivers, and do not use it on its own.                                       
 */                                                                             
#define module_driver(__driver, __register, __unregister, ...) \                
static int __init __driver##_init(void) \                                       
{ \                                                                             
    return __register(&(__driver) , ##__VA_ARGS__); \                           
} \                                                                             
module_init(__driver##_init); \                                                 
static void __exit __driver##_exit(void) \                                      
{ \                                                                             
    __unregister(&(__driver) , ##__VA_ARGS__); \                                
} \                                                                             
module_exit(__driver##_exit);

 

5) builtin_platform_driver_probe() 매크로 함수 사용 시

builtin_platform_driver() 매크로 함수와 유사한 방식이다. device_driver 구조체에서 probe 함수 및 id_table을 지정하지 않은 것이 다른 점이다. 역시 아래와 같이 매우 심플하게 코딩할 수 있다.

static struct platform_driver foo_driver = {
        .remove = __exit_p(foo_remove),
        .driver = {
            .name = "foo",
        },
};
builtin_platform_driver_probe(foo_driver, foo_probe);

 

/* builtin_platform_driver_probe() - Helper macro for drivers that don't do
 * anything special in device init.  This eliminates some boilerplate.  Each
 * driver may only use this macro once, and using it replaces device_initcall.
 * This is meant to be a parallel of module_platform_driver_probe above, but
 * without the __exit parts.
 */
#define builtin_platform_driver_probe(__platform_driver, __platform_probe) \
static int __init __platform_driver##_init(void) \
{ \
        return platform_driver_probe(&(__platform_driver), \
                                     __platform_probe);    \
} \
device_initcall(__platform_driver##_init);

 

6) module_platform_driver_probe() 매크로 함수 사용 시

module_platform_driver() 매크로 함수와 유사한 방식이다. device_driver 구조체에서 probe 함수 및 id_table을 지정하지 않은 것이 다른 점이다. 역시 아래와 같이 매우 심플하게 코딩할 수 있다.

static struct platform_driver foo_driver = {
        .remove = __exit_p(foo_remove),
        .driver = {
            .name = "foo",
        },
};
module_platform_driver_probe(foo_driver, foo_probe);

 

/* module_platform_driver_probe() - Helper macro for drivers that don't do      
 * anything special in module init/exit.  This eliminates a lot of              
 * boilerplate.  Each module may only use this macro once, and                  
 * calling it replaces module_init() and module_exit()                          
 */                                                                             
#define module_platform_driver_probe(__platform_driver, __platform_probe) \     
static int __init __platform_driver##_init(void) \                              
{ \                                                                             
    return platform_driver_probe(&(__platform_driver), \                        
                     __platform_probe);    \                                    
} \                                                                             
module_init(__platform_driver##_init); \                                        
static void __exit __platform_driver##_exit(void) \                             
{ \                                                                             
    platform_driver_unregister(&(__platform_driver)); \                         
} \                                                                             
module_exit(__platform_driver##_exit);

 

플랫폼 드라이버 등록부

__platform_driver_register()

include/linux/platform_device.h

/*
 * use a macro to avoid include chaining to get THIS_MODULE
 */
#define platform_driver_register(drv) \
        __platform_driver_register(drv, THIS_MODULE)

플랫폼 드라이버를 등록한다.

 

__platform_driver_register()

drivers/base/platform.c

/**
 * __platform_driver_register - register a driver for platform-level devices
 * @drv: platform driver structure
 * @owner: owning module/driver
 */
int __platform_driver_register(struct platform_driver *drv,
                                struct module *owner)
{               
        drv->driver.owner = owner;
        drv->driver.bus = &platform_bus_type;
        drv->driver.probe = platform_drv_probe;
        drv->driver.remove = platform_drv_remove;
        drv->driver.shutdown = platform_drv_shutdown;

        return driver_register(&drv->driver);
}
EXPORT_SYMBOL_GPL(__platform_driver_register);

플랫폼 드라이버를 등록한다.

  • 버스 타입 및 probe, remove, shutdown 함수는 플랫폼 드라이버 core가 제공하는 함수를 사용한다.

 

플랫폼 버스 타입

struct bus_type platform_bus_type = {
        .name           = "platform",
        .dev_groups     = platform_dev_groups,
        .match          = platform_match,
        .uevent         = platform_uevent,
        .pm             = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);

 

플랫폼 디바이스 속성

플랫폼 디바이스에 부여되는 속성들은 다음과 같다.

  • drriver_override
  • modalias

 

플랫폼 디바이스 및 드라이버 매치 확인

platform_match()

drivers/base/platform.c

/**
 * platform_match - bind platform device to platform driver.
 * @dev: device.
 * @drv: driver.
 *
 * Platform device IDs are assumed to be encoded like this:
 * "<name><instance>", where <name> is a short description of the type of
 * device, like "pci" or "floppy", and <instance> is the enumerated
 * instance of the device, like '0' or '42'.  Driver IDs are simply
 * "<name>".  So, extract the <name> from the platform_device structure,
 * and compare it against the name of the driver. Return whether they match
 * or not.
 */
static int platform_match(struct device *dev, struct device_driver *drv)
{
        struct platform_device *pdev = to_platform_device(dev);
        struct platform_driver *pdrv = to_platform_driver(drv);

        /* When driver_override is set, only bind to the matching driver */
        if (pdev->driver_override)
                return !strcmp(pdev->driver_override, drv->name);

        /* Attempt an OF style match first */
        if (of_driver_match_device(dev, drv))
                return 1;

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

        /* Then try to match against the id table */
        if (pdrv->id_table)
                return platform_match_id(pdrv->id_table, pdev) != NULL;

        /* fall-back to driver name match */
        return (strcmp(pdev->name, drv->name) == 0);
}

플랫폼 디바이스와 플랫폼 드라이버의 매치여부를 반환한다. (1=매치, 0=매치되지 않음)

  • 코드 라인 20~21에서 플랫폼 디바이스의 driver_override가 드라이버 명으로 설정된 경우 함수를 성공(1)을 반환한다.
  • 코드 라인 24~25에서 디바이스와 드라이버가 매치된 경우 성공(1)을 반환한다.
  • 코드 라인 28~29에서 ACPI 스타일로 디바이스와 드라이버가 매치되는 경우 성공(1)을 반환한다.
  • 코드 라인 32~33에서 플랫폼 디바이스에 있는 id_table과 플랫폼 드라이버에 있는 id_table이 매치되는 경우 성공(1)을 반환한다.
  • 코드 라인 36에서 마지막으로 플랫폼 디바이스명과 드라이버의 이름이 동일한 경우 성공(1)을 반환한다. 그렇지 않은 경우 0을 반환한다.

 

플랫폼 드라이버 등록과 동기 방법으로 probe

platform_driver_probe()

include/linux/platform_device.h

/* non-hotpluggable platform devices may use this so that probe() and           
 * its support may live in __init sections, conserving runtime memory.          
 */                                                                             
#define platform_driver_probe(drv, probe) \                                     
    __platform_driver_probe(drv, probe, THIS_MODULE)

플랫폼 디바이스 드라이버를 probe한다.

 

__platform_driver_probe()

drivers/base/platform.c

/**                                                                             
 * __platform_driver_probe - register driver for non-hotpluggable device        
 * @drv: platform driver structure                                              
 * @probe: the driver probe routine, probably from an __init section            
 * @module: module which will be the owner of the driver                        
 *                                                                              
 * Use this instead of platform_driver_register() when you know the device      
 * is not hotpluggable and has already been registered, and you want to         
 * remove its run-once probe() infrastructure from memory after the driver      
 * has bound to the device.                                                     
 *                                                                              
 * One typical use for this would be with drivers for controllers integrated    
 * into system-on-chip processors, where the controller devices have been       
 * configured as part of board setup.                                           
 *                                                                              
 * Note that this is incompatible with deferred probing.                        
 *                                                                              
 * Returns zero if the driver registered and bound to a device, else returns    
 * a negative error code and with the driver not registered.                    
 */
int __init_or_module __platform_driver_probe(struct platform_driver *drv,       
        int (*probe)(struct platform_device *), struct module *module)          
{                                                                               
    int retval, code;                                                           
                                                                                
    if (drv->driver.probe_type == PROBE_PREFER_ASYNCHRONOUS) {                  
        pr_err("%s: drivers registered with %s can not be probed asynchronously\n",
             drv->driver.name, __func__);                                       
        return -EINVAL;                                                         
    }                                                                           
                                                                                
    /*                                                                          
     * We have to run our probes synchronously because we check if              
     * we find any devices to bind to and exit with error if there              
     * are any.                                                                 
     */                                                                         
    drv->driver.probe_type = PROBE_FORCE_SYNCHRONOUS;                           
                                                                                
    /*                                                                          
     * Prevent driver from requesting probe deferral to avoid further           
     * futile probe attempts.                                                   
     */                                                                         
    drv->prevent_deferred_probe = true;                                         
                                                                                
    /* make sure driver won't have bind/unbind attributes */                    
    drv->driver.suppress_bind_attrs = true;                                     
                                                                                
    /* temporary section violation during probe() */                            
    drv->probe = probe;                                                         
    retval = code = __platform_driver_register(drv, module);                    
                                                                                
    /*                                                                          
     * Fixup that section violation, being paranoid about code scanning         
     * the list of drivers in order to probe new devices.  Check to see         
     * if the probe was successful, and make sure any forced probes of          
     * new devices fail.                                                        
     */                                                                         
    spin_lock(&drv->driver.bus->p->klist_drivers.k_lock);                       
    drv->probe = NULL;                                                          
    if (code == 0 && list_empty(&drv->driver.p->klist_devices.k_list))          
        retval = -ENODEV;                                                       
    drv->driver.probe = platform_drv_probe_fail;                                
    spin_unlock(&drv->driver.bus->p->klist_drivers.k_lock);                     
                                                                                
    if (code != retval)                                                         
        platform_driver_unregister(drv);                                        
    return retval;                                                              
}                                                                               
EXPORT_SYMBOL_GPL(__platform_driver_probe);

플랫폼 드라이버를 등록하고 동기 방법으로 probe한다.

  • 코드 라인 6~10에서 드라이버의 probe 타입이 PROBE_PREFER_ASYNCHRONOUS인 경우 에러를 출력하고 -EINVAL 에러를 반환한다. 이 함수는 비동기 probe를 지원하지 않는다.
    • PROBE_PREFER_ASYNCHRONOUS는 mtd 드라이버나 i2c 등에서 probing이 저속으로 동작하는 장치등에서 사용한다.
  • 코드 라인 17에서 드라이버의 probe 타입을 PROBE_FORCE_SYNCHRONOUS로 고정한다.
  • 코드 라인 23에서 deferred probe가 동작하지 못하게 금지시킨다.
  • 코드 라인 26에서 드라이버에 suppress_bind_attrs가 설정된 경우 sysfs에서 bind/unbind 속성 파일을 생성하지 못하게 한다.
  • 코드 라인 29~30에서 드라이버에 인자로 전달 받은 probe 함수를 지정하고 플랫폼 드라이버를 등록한다.

 

subsystem 등록

subsys_system_register()

/**                                                                             
 * subsys_system_register - register a subsystem at /sys/devices/system/        
 * @subsys: system subsystem                                                    
 * @groups: default attributes for the root device                              
 *                                                                              
 * All 'system' subsystems have a /sys/devices/system/<name> root device        
 * with the name of the subsystem. The root device can carry subsystem-         
 * wide attributes. All registered devices are below this single root           
 * device and are named after the subsystem with a simple enumeration           
 * number appended. The registered devices are not explicitly named;            
 * only 'id' in the device needs to be set.                                     
 *                                                                              
 * Do not use this interface for anything new, it exists for compatibility      
 * with bad ideas only. New subsystems should use plain subsystems; and         
 * add the subsystem-wide attributes should be added to the subsystem           
 * directory itself and not some create fake root-device placed in              
 * /sys/devices/system/<name>.                                                  
 */                                                                             
int subsys_system_register(struct bus_type *subsys,                             
               const struct attribute_group **groups)                           
{                                                                               
    return subsys_register(subsys, groups, &system_kset->kobj);                 
}                                                                               
EXPORT_SYMBOL_GPL(subsys_system_register);

서브 시스템을 “/sys/devices/system” 디렉토리 아래에 등록한다. (이하 코드는 생략)

 

기타 플랫폼 디바이스 & 드라이버 관련 API들

  • platform_get_resource()
    • 플랫폼 디바이스로부터 리소스를 가져온다.
  • platform_get_irq()
    • 플랫폼 디바이스로부터 irq 번호를 가져온다.
  • platform_irq_count()
    • 플랫폼 디바이스에있는 irq 수를 알아온다.
  • platform_get_resource_byname()
    • 플랫폼 디바이스에서 요청한 이름과 타입의 리소스를 알아온다.
  • platform_get_irq_byname()
    • 플랫폼 디바이스에서 요청한 irq 이름에 해당하는 irq 번호를 가져온다.
  • platform_device_put()
    • 플랫폼 디바이스를 destroy한다.
  • platform_device_add_resources()
    • 플랫폼 디바이스에 리소스들을 추가한다.
  • platform_device_add_data()
    • 플랫폼 디바이스에 플랫폼 데이터를 지정한다.
  • platform_device_add_properties()
    • 내장 속성을 플랫폼 디바이스에 추가한다.
  • platform_device_del()
    • 플랫폼 디바이스를 제거한다.
  • platform_device_unregister()
    • 플랫폼 디바이스를 등록 해제한다.
  • platform_driver_unregister()
    • 플랫폼 드라이버를 등록 해제한다.
  • __platform_create_bundle()
    • 플랫폼 드라이버를 등록하고 이에 대응하는 플랫폼 디바이스를 생성한다.
  • platform_unregister_drivers()
    • 복수의 플랫폼 드라이버들을 등록 해제한다.
  • platform_get_device_id()
  • to_platform_device()
    • 디바이스를 임베드한 플랫폼 디바이스를 반환한다.
  • to_platform_driver()
    • 드라이버를 임베드한 플랫폼 드라이버를 반환한다.

 

참고