I2C 多路复用器和复杂拓扑

构建比具有一个适配器和一个或多个设备的简单 I2C 总线更复杂的 I2C 拓扑结构有两个原因。

一些示例用例包括

  1. 可能需要在总线上使用多路复用器以防止地址冲突。

  2. 总线可能可以从一些外部总线主控器访问,并且可能需要仲裁来确定是否可以访问总线。

  3. 设备(特别是 RF 调谐器)可能希望避免来自 I2C 总线的数字噪声,至少在大多数情况下,并且位于一个门后面,该门必须在访问设备之前操作。

I2C 多路复用器、I2C 门和 I2C 仲裁器等几种类型的硬件组件允许处理此类需求。

这些组件在 Linux 中表示为 I2C 适配器树,其中每个适配器都有一个父适配器(根适配器除外)和零个或多个子适配器。 根适配器是实际发出 I2C 传输的适配器,并且所有具有父适配器的适配器都是“i2c-mux”对象的一部分(带引号,因为它也可以是仲裁器或门)。

根据特定的多路复用器驱动程序,当在其子适配器之一上进行 I2C 传输时,会发生一些事情。 多路复用器驱动程序显然可以操作多路复用器,但它也可以与外部总线主控器进行仲裁或打开一个门。 多路复用器驱动程序有两个操作可以执行此操作:select 和 deselect。 select 在传输之前调用,可选的 deselect 在传输之后调用。

锁定

I2C 多路复用器有两种锁定变体,它们可以是多路复用器锁定或父锁定多路复用器。

多路复用器锁定多路复用器

多路复用器锁定多路复用器不会在完整的 select-transfer-deselect 事务期间锁定整个父适配器,只会锁定父适配器上的多路复用器。 如果 select 和/或 deselect 操作必须使用 I2C 传输才能完成其任务,则多路复用器锁定多路复用器最有趣。 由于父适配器在整个事务期间未完全锁定,因此不相关的 I2C 传输可能会交错事务的不同阶段。 这有一个好处,即多路复用器驱动程序可能更容易且更干净地实现,但它有一些注意事项。

多路复用器锁定示例

               .----------.     .--------.
.--------.     |   mux-   |-----| dev D1 |
|  root  |--+--|  locked  |     '--------'
'--------'  |  |  mux M1  |--.  .--------.
            |  '----------'  '--| dev D2 |
            |  .--------.       '--------'
            '--| dev D3 |
               '--------'

当访问 D1 时,会发生以下情况

  1. 有人向 D1 发出 I2C 传输。

  2. M1 锁定其父级(在本例中为根适配器)上的多路复用器。

  3. M1 调用 ->select 以准备好多路复用器。

  4. M1(大概)进行一些 I2C 传输作为其 select 的一部分。 这些传输是锁定父适配器的正常 I2C 传输。

  5. M1 将步骤 1 中的 I2C 传输作为锁定父适配器的正常 I2C 传输馈送到其父适配器。

  6. M1 调用 ->deselect,如果它有一个。

  7. 与步骤 4 中相同的规则,但对于 ->deselect。

  8. M1 解锁其父级上的多路复用器。

这意味着对 D2 的访问在整个操作的整个期间都被锁定。 但是对 D3 的访问可能在任何时候交错。

多路复用器锁定注意事项

使用多路复用器锁定多路复用器时,请注意以下限制

[ML1]

如果您构建的拓扑结构中,多路复用器锁定多路复用器是父锁定多路复用器的父级,则可能会破坏父锁定多路复用器的期望,即在事务期间根适配器被锁定。

[ML2]

当这些非同级多路复用器的子适配器上的设备之间存在地址冲突时,构建具有两个(或更多)非同级多路复用器锁定的多路复用器的任意拓扑结构是不安全的。

即,以例如设备地址 0x42 为目标的多路复用器一后面的 select-transfer-deselect 事务可能会与以多路复用器二后面的设备地址 0x42 为目标的类似操作交错。 在这种假设示例中,这种拓扑结构的目的是多路复用器一和多路复用器二不应同时被选中,但多路复用器锁定多路复用器不能保证在所有拓扑结构中都满足这一点。

[ML3]

多路复用器锁定多路复用器不能被驱动程序用于自动关闭门/多路复用器,即在给定数量(在大多数情况下为一个)I2C 传输后自动关闭的东西。 不相关的 I2C 传输可能会爬入并过早关闭。

[ML4]

如果多路复用器驱动程序中的任何非 I2C 操作更改了 I2C 多路复用器状态,则驱动程序必须在该操作期间锁定根适配器。 否则,当不相关的 I2C 传输正在进行时,从多路复用器后面的设备来看,总线上可能会出现垃圾。

父锁定多路复用器

父锁定多路复用器在完整的 select-transfer-deselect 事务期间锁定父适配器。 这意味着多路复用器驱动程序必须确保在事务期间通过该父适配器的任何和所有 I2C 传输都是未锁定的 I2C 传输(例如使用 __i2c_transfer),否则将发生死锁。

父锁定示例

               .----------.     .--------.
.--------.     |  parent- |-----| dev D1 |
|  root  |--+--|  locked  |     '--------'
'--------'  |  |  mux M1  |--.  .--------.
            |  '----------'  '--| dev D2 |
            |  .--------.       '--------'
            '--| dev D3 |
               '--------'

当访问 D1 时,会发生以下情况

  1. 有人向 D1 发出 I2C 传输。

  2. M1 锁定其父级(在本例中为根适配器)上的多路复用器。

  3. M1 锁定其父适配器。

  4. M1 调用 ->select 以准备好多路复用器。

  5. 如果 M1 作为其 select 的一部分进行任何 I2C 传输(在此根适配器上),则这些传输必须是未锁定的 I2C 传输,这样它们就不会使根适配器死锁。

  6. M1 将步骤 1 中的 I2C 传输作为未锁定的 I2C 传输馈送到根适配器,这样它就不会使父适配器死锁。

  7. M1 调用 ->deselect,如果它有一个。

  8. 与步骤 5 中相同的规则,但对于 ->deselect。

  9. M1 解锁其父适配器。

  10. M1 解锁其父级上的多路复用器。

这意味着对 D2 和 D3 的访问在整个操作的整个期间都被锁定。

父锁定注意事项

使用父锁定多路复用器时,请注意以下限制

[PL1]

如果您构建的拓扑结构中,父锁定多路复用器是另一个多路复用器的子级,则可能会破坏子多路复用器的一个可能假设,即根适配器在其 select 操作和实际传输之间未使用(例如,如果子多路复用器是自动关闭的,并且父多路复用器作为其 select 的一部分发出 I2C 传输)。 如果父多路复用器被多路复用器锁定,则尤其如此,但如果父多路复用器被父锁定,也可能会发生这种情况。

[PL2]

如果 select/deselect 调用了其他子系统,例如 gpio、pinctrl、regmap 或 iio,则必须解锁由这些子系统引起的任何 I2C 传输。 完成此操作可能很复杂,如果寻求可接受的干净解决方案,甚至可能无法实现。

复杂示例

父锁定多路复用器作为父锁定多路复用器的父级

这是一个有用的拓扑结构,但它可能很糟糕

               .----------.     .----------.     .--------.
.--------.     |  parent- |-----|  parent- |-----| dev D1 |
|  root  |--+--|  locked  |     |  locked  |     '--------'
'--------'  |  |  mux M1  |--.  |  mux M2  |--.  .--------.
            |  '----------'  |  '----------'  '--| dev D2 |
            |  .--------.    |  .--------.       '--------'
            '--| dev D4 |    '--| dev D3 |
               '--------'       '--------'

当访问任何设备时,所有其他设备都会在操作的整个期间被锁定(两个多路复用器都会锁定它们的父级,特别是当 M2 请求其父级锁定时,M1 会将责任传递给根适配器)。

如果 M2 是一个自动关闭的多路复用器,并且 M1->select 在根适配器上发出任何可能泄漏并通过 M2 适配器看到的未锁定 I2C 传输,从而过早关闭 M2,则此拓扑结构很糟糕。

多路复用器锁定多路复用器作为多路复用器锁定多路复用器的父级

这是一个很好的拓扑结构

               .----------.     .----------.     .--------.
.--------.     |   mux-   |-----|   mux-   |-----| dev D1 |
|  root  |--+--|  locked  |     |  locked  |     '--------'
'--------'  |  |  mux M1  |--.  |  mux M2  |--.  .--------.
            |  '----------'  |  '----------'  '--| dev D2 |
            |  .--------.    |  .--------.       '--------'
            '--| dev D4 |    '--| dev D3 |
               '--------'       '--------'

当访问设备 D1 时,对 D2 的访问在操作的整个期间被锁定(M1 的顶部子适配器上的多路复用器被锁定)。 但是对 D3 和 D4 的访问可能在任何时候交错。

对 D3 的访问会锁定 D1 和 D2,但对 D4 的访问仍然可能交错。

多路复用器锁定多路复用器作为父锁定多路复用器的父级

这可能是一个糟糕的拓扑结构

               .----------.     .----------.     .--------.
.--------.     |   mux-   |-----|  parent- |-----| dev D1 |
|  root  |--+--|  locked  |     |  locked  |     '--------'
'--------'  |  |  mux M1  |--.  |  mux M2  |--.  .--------.
            |  '----------'  |  '----------'  '--| dev D2 |
            |  .--------.    |  .--------.       '--------'
            '--| dev D4 |    '--| dev D3 |
               '--------'       '--------'

当访问设备 D1 时,对 D2 和 D3 的访问在操作的整个期间被锁定(M1 锁定根适配器上的子多路复用器)。 但是对 D4 的访问可能在任何时候交错。

这种拓扑结构通常不合适,可能应该避免。 原因在于,M2 可能假设在其 ->select 和 ->deselect 调用期间不会有 I2C 传输,并且如果有,任何此类传输都可能在 M2 的从属端显示为部分 I2C 传输,即垃圾或更糟。 这可能会导致设备锁定和/或其他问题。

如果 M2 是一个自动关闭的多路复用器,则拓扑结构尤其麻烦。 在这种情况下,对 D4 的任何交错访问都可能过早关闭 M2,M1->select 的任何 I2C 传输也可能导致这种情况。

但是,如果 M2 没有做出上述假设,并且如果 M2 不是自动关闭的,则拓扑结构很好。

父锁定多路复用器作为多路复用器锁定多路复用器的父级

这是一个很好的拓扑结构

               .----------.     .----------.     .--------.
.--------.     |  parent- |-----|   mux-   |-----| dev D1 |
|  root  |--+--|  locked  |     |  locked  |     '--------'
'--------'  |  |  mux M1  |--.  |  mux M2  |--.  .--------.
            |  '----------'  |  '----------'  '--| dev D2 |
            |  .--------.    |  .--------.       '--------'
            '--| dev D4 |    '--| dev D3 |
               '--------'       '--------'

当访问 D1 时,对 D2 的访问在操作的整个期间被锁定(M1 的顶部子适配器上的多路复用器被锁定)。 对 D3 和 D4 的访问可能在任何时候交错,正如多路复用器锁定多路复用器所期望的那样。

当访问 D3 或 D4 时,所有其他设备都会被锁定。 对于 D3 访问,M1 锁定根适配器。 对于 D4 访问,直接锁定根适配器。

两个多路复用器锁定的同级多路复用器

这是一个很好的拓扑结构

                                .--------.
               .----------.  .--| dev D1 |
               |   mux-   |--'  '--------'
            .--|  locked  |     .--------.
            |  |  mux M1  |-----| dev D2 |
            |  '----------'     '--------'
            |  .----------.     .--------.
.--------.  |  |   mux-   |-----| dev D3 |
|  root  |--+--|  locked  |     '--------'
'--------'  |  |  mux M2  |--.  .--------.
            |  '----------'  '--| dev D4 |
            |  .--------.       '--------'
            '--| dev D5 |
               '--------'

当访问 D1 时,对 D2、D3 和 D4 的访问被锁定。 但是对 D5 的访问可能在任何时候交错。

两个父锁定同级多路复用器

这是一个很好的拓扑结构

                                .--------.
               .----------.  .--| dev D1 |
               |  parent- |--'  '--------'
            .--|  locked  |     .--------.
            |  |  mux M1  |-----| dev D2 |
            |  '----------'     '--------'
            |  .----------.     .--------.
.--------.  |  |  parent- |-----| dev D3 |
|  root  |--+--|  locked  |     '--------'
'--------'  |  |  mux M2  |--.  .--------.
            |  '----------'  '--| dev D4 |
            |  .--------.       '--------'
            '--| dev D5 |
               '--------'

当访问任何设备时,对所有其他设备的访问都会被锁定。

多路复用器锁定和父锁定同级多路复用器

这是一个很好的拓扑结构

                                .--------.
               .----------.  .--| dev D1 |
               |   mux-   |--'  '--------'
            .--|  locked  |     .--------.
            |  |  mux M1  |-----| dev D2 |
            |  '----------'     '--------'
            |  .----------.     .--------.
.--------.  |  |  parent- |-----| dev D3 |
|  root  |--+--|  locked  |     '--------'
'--------'  |  |  mux M2  |--.  .--------.
            |  '----------'  '--| dev D4 |
            |  .--------.       '--------'
            '--| dev D5 |
               '--------'

当访问 D1 或 D2 时,对 D3 和 D4 的访问会被锁定,而对 D5 的访问可能会交错。 当访问 D3 或 D4 时,对所有其他设备的访问都会被锁定。

现有设备驱动程序的多路复用器类型

设备是被多路复用器锁定还是父锁定取决于其实现。 以下列表在编写时是正确的

在 drivers/i2c/muxes/ 中

i2c-arb-gpio-challenge

父锁定

i2c-mux-gpio

通常是父锁定,如果所有涉及的 gpio 引脚都由它们复用的同一个 I2C 根适配器控制,则为多路复用器锁定。

i2c-mux-gpmux

通常是父锁定,如果在设备树中指定,则为多路复用器锁定。

i2c-mux-ltc4306

多路复用器锁定

i2c-mux-mlxcpld

父锁定

i2c-mux-pca9541

父锁定

i2c-mux-pca954x

父锁定

i2c-mux-pinctrl

通常是父锁定,如果所有涉及的 pinctrl 设备都由它们复用的同一个 I2C 根适配器控制,则为多路复用器锁定。

i2c-mux-reg

父锁定

在 drivers/iio/ 中

gyro/mpu3050

多路复用器锁定

imu/inv_mpu6050/

多路复用器锁定

在 drivers/media/ 中

dvb-frontends/lgdt3306a

多路复用器锁定

dvb-frontends/m88ds3103

父锁定

dvb-frontends/rtl2830

父锁定

dvb-frontends/rtl2832

多路复用器锁定

dvb-frontends/si2168

多路复用器锁定

usb/cx231xx/

父锁定