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