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번 채널을 선택하는 모습을 보여준다.

 

참고

 

댓글 남기기