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() 후크 함수를 호출하게 한다.
- 1인 경우 i2c_mux_lock_ops를 사용한다.
- 다음 드라이버들이 지원한다.
- 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
- mux-lock 오퍼레이션을 선택사용하게 한다.
- 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", ®)) {
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", ®);
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 << 채널 번호
- i2c 멀티 플렉서
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 -1- (Basic) | 문c
- I2C Subsystem -2- (Core) | 문c
- I2C Subsystem -3- (Transfer) | 문c
- i2C Subsystem -4- (I2C-Mux) | 문c – 현재 글
- Documentation/i2c/ – kernel.org
- Documentation/i2c/dev-interface.txt – kernel.org
- I2C | sparkfun
- I2C – What’s That? | i2c-bus.org
- What is I2C (inter-‐IC) | Piazza – 다운로드 pdf
- I2C manual – NXP Semiconductors | NXP – 다운로드 pdf
- I2C-bus specification and user manual Rev. 6 — 4 April 2014 | NXP – 다운로드 pdf
- PCA954X FAMILY OF I²C / SMBus MULTIPLEXERS and SWITCHES | Philips – 다운로드 pdf









