PCA9548/PCA954X配置方法及实现分析

14 篇文章 2 订阅

1 基础知识

    pca9548芯片系统框图如下图1.1所示:

图1.1

    如上图所示,通过配置pca9548的寄存器,即可切换开关完成i2c switch。在子总线下设备地址没有冲突的情况下,可以接通多个子总线,如同时接通子总线0和7。

    pca9548内核驱动支持自动切换i2c switch,前提是配置好设备树。设备树的配置示例如下图1.2所示:

图1.2

    通过上图1.2所示配置后,pca9548的内核驱动程序会注册虚拟的i2c总线16-23,这样一来,上层可使用这些虚拟总线来读取所挂i2c设备的信息。比如,虚拟总线16对应图1.1中子总线0,当上层使用虚拟总线16读时,内核驱动将自动接通图1.1子总线0对应的开关,并将其他开关关闭,也就是说,内核驱动会保证只有一个开关接通。

    通过这种方式,我们可以将实际物理总线3,扩展为8条虚拟总线16-23,虚拟总线之间互不干扰,可以解决相同i2c设备地址相同问题。

2 问题描述

以某主板为例(BMC为ARM芯片),其i2c部分拓扑如下图2.1所示。

图2.1

如上图2.1所示,通过配置设备树可将实际物理总线3(BMC为ARM芯片),扩展成24条虚拟总线16-39。现在分析一下多个pca9548并联之后会存在什么问题:

(1)依次读取16-39虚拟总线上0x9e地址对应设备上数据;

(2)在读16-23虚拟总线上0x9e设备时,pca9548内核驱动会依次打开对应通道开关并关闭其他通道开关(图1.1),数据读取正常;

(3)当读取完23号虚拟总线上0x9e设备数据后,便转而去读取24号虚拟总线上0x9e设备,内核驱动会打开0xE2地址对应pca9548的子总线0开关,但并不会去关闭0xE0地址对应pca9548的子总线7开关,此时,图2.1上红线1和红线2都接通,因此设备地址0x9e冲突了

    内核驱动不知道pca9548之间是并联关系,因此不会去关闭23号虚拟总线对应的开关,这便是问题所在。

    既然不能由内核自动切换i2c switch通路,那就只能手动来切i2c switch通路了,以往insyde BMC就是使用手动切换方式。

3 手动切换i2c switch

3.1 理论分析

    如果手动切i2c switch通道的代码,要实现代码通用,难点在于:如何处理i2c switch芯片之间并联关系?

    容易想到的方法是:用一张表描述pca9548芯片之间并联关系,由代码遍历这张表,从而知道读取某个i2c switch子总线前,是否需要关闭其他i2c switch芯片通道。这种方式需要考虑较多,比如第一层i2c switch之间并联可能个数、i2c switch的子总线下面是否有i2c switch并联、i2c switch芯片不同如何处理等。

    既然i2c switch芯片并联关系较难梳理清楚,那我们就通过串联方式去处理i2c设备地址冲突问题。一条虚拟i2c总线只有唯一一条通路,比如图2.1虚拟总线32上再接一个地址为0xE6的pca9848,又扩展出8条虚拟总线40-47,但47号虚拟总线对应的i2c switch通路是唯一的,即需要打开0xE4 pca9548子总线7对应开关,同时打开0xE6 pca9548子总线7对应开关,这样才能读取虚拟总线47上的i2c设备,如果在读取完i2c设备数据之后,又反向把通路上i2c switch芯片上开关关闭呢?

这样一来,每次读某个虚拟总线上i2c设备时,先切换i2c switch通路,然后读取i2c设备数据,最后又关闭i2c switch通路

3.2 代码实现

3.2.1 i2c switch描述结构

    首先需要定义一个通用结构,用来描述i2c switch信息,结构如下图3.1所示。

图 3.1

        u8HubAddr:i2c switch芯片的8位i2c地址;

        u8CloseFlag:此i2c switch是否需要执行关闭操作(有的i2c switch无法关闭);

        au8OpenReg:打开i2c子总线所需写的寄存器;

        au8CloseReg:关闭i2c子总线所需写的寄存器;

        u8RegSize:寄存器大小;

        au8OpenData:打开i2c子总线所写寄存器的数据;

        au8CloseData:关闭i2c子总线所写寄存器的数据;

        u8DataSize:数据大小。

3.2.2 i2c switch实现

i2c switch函数处理流程:

 

图3.2

i2c switch函数部分代码实现如下图3.3所示:

图3.3

    如图3.3所示,将i2c switch信息填充到发送Buf中,再通过i2c读写函数将数据写到到i2c switch寄存器中,进而完成通道切换,值得注意的是,当某一层i2c switch切换失败时,必须把之前所打开的i2c switch通道关闭。

3.2.3 i2c close实现

    i2c close函数处理流程图:

 

图3.4

    I2c close函数部分代码实现如下图所示:

图 3.5

    如图3.5所示,只有当前i2c switch层设置了关闭标志,才会去执行关闭动作。

    注意的是,真正手动切换i2c switch时,需要在切换前加锁,在对i2c设备读写完数据后,关闭i2c switch通路并解锁,以防止线程本次读写完成前,被其他线程切换i2c switch通道。

    既然手动切换i2c switch方式可以在读完数据后关闭通道,那自动切换i2c switch方式是否也可以?

4 自动切换i2c switch

4.1 pca954x驱动prebo

以pca954x驱动为例,内核版本为5.2.0,驱动代码简化后如下图4.1所示:

图4.1

    如上图所示,省略其他无关代码后,整个probe过程分为6个步骤:

        (1)检查当前i2c总线是否支持SMBUS_BYTE读写操作;

        (2)为pca954x分配私有数据结构,并填充pca954x_select_chan函数和pca954x_deselect_mux函数;

        (3)获取当前pca954x具体芯片的信息,比如pca9548信息内含多少个通道、是否支持中断、是否有使能引脚等;

        (4)尝试写一个数据到i2c switch中,以便确定i2c switch真实存在;

        (5)设置i2c switch芯片的空闲状态,在pca954x_deselect_mux函数中使用;

        (6)为每个i2c switch通道,也就是子总线注册虚拟的总线(adapter),虚拟总线使用的通信方法继承于上一级总线。

以pca9548芯片为例,多级pca9548物理与软件对应关系简图如下图4.2所示。

图4.2

    如上图4.2所示,虚拟总线40的传输函数,其实就是在虚拟总线16的传输函数前后增加了select和deselect函数,而虚拟总线16的传输同样是在物理总线3的传输函数前后增加了select和deselect函数,最终还是由物理总线3的传输函数实现对i2c设备读写数据。

4.2 pca954x通道切换

    select函数就是pca954x_select_chan,其代码实现如下:

图4.3

    Select函数用于在真正的i2c读写数据前,切换pca954x芯片的通道(仅通道不同时切),注意,select操作其实还是调用上一级i2c总线的传输函数(上一级总线可能还是虚拟总线)。

    Deselect函数就是pca954x_deselect_mux,其代码实现如下:

图4.4

    上图中idle_state默认情况下如图4.1所示为MUX_IDLE_AS_IS(-1),即保持当前通道状态,也就是说,执行pca954x_deselect_mux函数将不做任何改变,原来打开的通道依然保持,这样一来,若pca9548并联就会出现i2c地址冲突问题

4.3 读写实例

当满足idle_state == MUX_IDLE_DISCONNECT条件时,则执行pca954x_deselect_mux函数时,将会关闭当前pca954x芯片所有通道,下面以图4.5为例分析一下具体读写过程:

图4.5

    初始状态0xe0 pca9548通道0和0xe6 pca9548通道0都关闭。当对i2c虚拟总线40上的0x9e设备读写时,将调用40:i2c_transfer函数,该函数分三个步骤:

1.调用select片选函数,而片选的操作本质上就是调用上一级i2c传输函数(16:i2c_transfer)写地址为0xe6的pca9548芯片寄存器(箭头1)。16:i2c_transfer函数内部同样也分三个步骤:

(1)select调用3:i2c_transfer函数写0xe0 pca9548寄存器打开其通道0(箭头2);

(2)再调用3:i2c_transfer函数写0xe6 pca9548寄存器打开其通道0(箭头3);

(3)deselect调用3:i2c_transfer函数写0xe0 pca9548寄存器关闭其通道0(箭头4)。

此时,数据未读写,0xe0 pca9548通道0关闭,0xe6 pca9548通道0打开。

    2.调用传输函数读写0x9e设备,实际上是调用上一级i2c总线传输函数(箭头5),即16:i2c_transfer函数,内部又是三次调用:

(1)select调用3:i2c_transfer函数写0xe0 pca9548寄存器打开其通道0(箭头6);

(2)再调用3:i2c_transfer函数读写0x9e设备数据(箭头7);

(3)deselect调用3:i2c_transfer函数写0xe0 pca9548寄存器关闭其通道0(箭头8)。

此时,数据已读写,0xe0 pca9548通道0关闭,0xe6 pca9548通道0打开。

    3.调用deselect函数,关闭0xe6 pca9548通道0,同样还是调用上一级i2c总线传输函数(箭头9),即16:i2c_transfer函数,内部又是三次调用:

(1)select调用3:i2c_transfer函数写0xe0 pca9548寄存器打开其通道0(箭头10);

(2)再调用3:i2c_transfer函数写0xe6 pca9548寄存器关闭其通道0(箭头11);

(3)deselect调用3:i2c_transfer函数写0xe0 pca9548寄存器关闭其通道0(箭头12)。

此时,数据已读写0xe0 pca9548通道0关闭0xe6 pca9548通道0关闭。

    由上分析可知道,当满足idle_state == MUX_IDLE_DISCONNECT条件时,完成一次i2c设备读写操作后,路径上的i2c switch通道都关闭,因此不会出现i2c设备地址冲突问题

    由图4.1中第5步可配置idle_state == MUX_IDLE_DISCONNECT,前提是在设备树中配置“i2c-mux-idle-disconnect”属性。of_property_read_bool函数会去读pca954x设备节点中的“i2c-mux-idle-disconnect”属性,只要其存在就返回TRUE(不需要赋值),配置之后如下图4.6所示。

图 4.6

    通过上图所示配置后即可解决第二小节描述问题,并且设备树可灵活配置只让有并联关系pca954x获得“i2c-mux-idle-disconnect”属性(如图4.7红底pca9548),从而让有并联关系的pca954x保持常态关闭,而串联关系的pca954x保持上一次通道状态。

图4.7

5 总结

5.1 自动切换方案与手动切换方案对比

自动切换i2c switch优劣势:

优势:

    (1)内核驱动管理切换动作:上层可将扩展出来的虚拟i2c总线当物理i2c总线使用,完全不需要关心i2c switch是如何切换的;

    (2)不用考虑竞争关系:pca954x内核驱动带有锁机制;

    (3)执行效率高:由内核驱动完成切,不需要用户态与内核态频繁切换。

    劣势:

    (1)仅支持pca954x芯片:如混用其他i2c switch芯片,将无法配置;

    (2)不够灵活,部分情况无法使用:pca954x芯片需要通过设备树提前配置好,并需要在内核启动阶段probe成功,比如某个内含pca954x芯片扩展卡,在主机端上电情况下才会上电,那BMC启动时将probe失败,又比如一个PCIE卡槽既可以插扩展卡,又可以直插PCIE卡时,也无法配置设备树。

手动切换i2c switch优劣势:

    优势:

    (1)可以i2c switch芯片混用;

    (2)代码自己实现,可灵活适配各种场景;

    劣势:

    (1)需考虑竞争关系:需要在代码中实现锁机制;

    (2)执行效率比自动切换方式低:切i2c switch通道、读写i2c设备、关闭i2c switch通道,均由上层下发,因此执行效率不如自动切换方式;

    (3)代码复杂度增加。

    实际选择哪种方案需根据具体应用场景综合考虑。

5.2 总结

    Pca9548通过如下配置,即可扩展i2c总线,并且保证i2c switch常态关闭:

 

Pca9548下再连pca9548的配置示例如下:

 

评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值