I2C 多路复用器和复杂拓扑结构

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

一些示例用例包括

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

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

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

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

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

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

锁定

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

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

多路复用器锁定的多路复用器在完整的选择-传输-取消选择事务期间不会锁定整个父适配器,只会锁定父适配器上的多路复用器。如果选择和/或取消选择操作必须使用 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 传输作为其选择的一部分。这些传输是正常的 I2C 传输,它会锁定父适配器。

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

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

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

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

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

多路复用器锁定的注意事项

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

[ML1]

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

[ML2]

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

即,以多路复用器一背后的设备地址 0x42 为目标的“选择-传输-取消选择”事务可能会与以多路复用器二背后的设备地址 0x42 为目标的类似操作交错。 在这种假设的示例中,此拓扑结构的意图是多路复用器一和多路复用器二不应同时选择,但多路复用器锁定的多路复用器不能在所有拓扑结构中保证这一点。

[ML3]

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

[ML4]

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

父锁定的多路复用器

父锁定的多路复用器在完整的选择-传输-取消选择事务期间锁定父适配器。这意味着多路复用器驱动程序必须确保在事务期间通过该父适配器的任何和所有 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 作为其选择的一部分执行任何 I2C 传输(在此根适配器上),则这些传输必须是未锁定的 I2C 传输,以便它们不会使根适配器死锁。

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

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

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

  9. M1 解锁其父适配器。

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

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

父锁定的注意事项

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

[PL1]

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

[PL2]

如果选择/取消选择调用其他子系统,如 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 传输,并且如果有任何此类传输,任何此类传输都可能以部分 I2C 传输的形式出现在 M2 的从设备端,即垃圾或更糟。 这可能会导致设备锁定和/或其他问题。

如果 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/

父级锁定