BMC 虚拟i2c访问PCA9545(switch芯片)后面的设备,为什么找不到PCA9545?
1.说明
1.1 背景
无意中看到PCA9545(switch芯片)
后面有设备,但是PCA9545
设备本身是连接到物理设备i2c
上的,然而扫描该物理i2c bus
,却找不到该设备。此篇文章主要找一下该原因的。
1.2 参考代码
当前使用的是ast2600
芯片,可参考openbmc
代码:
- build/ast2600-default/workspace/sources/linux-aspeed/drivers/i2c/muxes/i2c-mux-pca954x.c
2.分析内核代码
2.1 内核打印信息
首先查看内核dmesg
打印出来的数据:
[ 2.490009] i2c /dev entries driver
[ 2.495203] i2c_ast2600 1e78a080.i2c-bus: i2c-bus [0]: adapter [100 khz] mode [1]
[ 2.504611] i2c_ast2600 1e78a100.i2c-bus: i2c-bus [1]: adapter [100 khz] mode [1]
[ 2.514101] i2c_ast2600 1e78a180.i2c-bus: i2c-bus [2]: adapter [100 khz] mode [1]
[ 2.535765] i2c_ast2600 1e78a200.i2c-bus: i2c-bus [3]: adapter [100 khz] mode [1]
[ 2.545117] i2c_ast2600 1e78a280.i2c-bus: i2c-bus [4]: adapter [100 khz] mode [1]
[ 2.554377] i2c_ast2600 1e78a300.i2c-bus: i2c-bus [5]: adapter [100 khz] mode [1]
[ 2.563776] i2c_ast2600 1e78a380.i2c-bus: i2c-bus [6]: adapter [100 khz] mode [1]
[ 2.573184] i2c_ast2600 1e78a400.i2c-bus: i2c-bus [7]: adapter [100 khz] mode [1]
[ 2.582600] i2c_ast2600 1e78a480.i2c-bus: i2c-bus [8]: adapter [100 khz] mode [1]
[ 2.592084] i2c_ast2600 1e78a500.i2c-bus: i2c-bus [9]: adapter [100 khz] mode [1]
[ 2.600821] there is no bus-mode property. use bye-mode as default.
[ 2.601329] i2c_ast2600 1e78a580.i2c-bus: i2c-bus [10]: adapter [100 khz] mode [0]
[ 2.617876] i2c_ast2600 1e78a600.i2c-bus: i2c-bus [11]: adapter [100 khz] mode [1]
[ 2.627276] i2c_ast2600 1e78a680.i2c-bus: i2c-bus [12]: adapter [100 khz] mode [1]
[ 2.636707] i2c_ast2600 1e78a700.i2c-bus: i2c-bus [13]: adapter [100 khz] mode [1]
[ 2.646044] i2c_ast2600 1e78a780.i2c-bus: i2c-bus [14]: adapter [100 khz] mode [1]
[ 2.655432] i2c_ast2600 1e78a800.i2c-bus: i2c-bus [15]: adapter [100 khz] mode [1]
[ 2.664806] i2c i2c-2: Added multiplexed i2c bus 16
[ 2.670590] i2c i2c-2: Added multiplexed i2c bus 17
[ 2.676041] pca954x 2-0070: registered 2 multiplexed busses for I2C switch pca9543
[ 2.685155] i2c i2c-6: Added multiplexed i2c bus 18
[ 2.690889] i2c i2c-6: Added multiplexed i2c bus 19
[ 2.696578] i2c i2c-6: Added multiplexed i2c bus 20
[ 2.702307] i2c i2c-6: Added multiplexed i2c bus 62
[ 2.707771] pca954x 6-0070: registered 4 multiplexed busses for I2C switch pca9545
[ 2.716915] i2c i2c-7: Added multiplexed i2c bus 30
[ 2.722695] i2c i2c-7: Added multiplexed i2c bus 31
[ 2.728394] i2c i2c-7: Added multiplexed i2c bus 32
[ 2.734103] i2c i2c-7: Added multiplexed i2c bus 33
[ 2.739568] pca954x 7-0071: registered 4 multiplexed busses for I2C switch pca9545
[ 2.748287] pca954x 8-0071: probe failed
[ 3.847738] pca954x 21-0071: probe failed
[ 3.852260] i2c i2c-9: Added multiplexed i2c bus 21
[ 3.947749] pca954x 22-0071: probe failed
[ 3.952271] i2c i2c-9: Added multiplexed i2c bus 22
[ 4.224326] pca954x 23-0071: probe failed
[ 4.228875] i2c i2c-9: Added multiplexed i2c bus 23
[ 4.324321] pca954x 24-0071: probe failed
[ 4.328856] i2c i2c-9: Added multiplexed i2c bus 24
[ 4.334303] pca954x 9-0070: registered 4 multiplexed busses for I2C switch pca9545
[ 4.343467] i2c i2c-11: Added multiplexed i2c bus 25
[ 4.349304] i2c i2c-11: Added multiplexed i2c bus 26
[ 4.355103] i2c i2c-11: Added multiplexed i2c bus 27
[ 4.360925] i2c i2c-11: Added multiplexed i2c bus 63
[ 4.366462] pca954x 11-0070: registered 4 multiplexed busses for I2C switch pca9545
[ 4.375162] pca954x 21-0071: probe failed
[ 4.379727] pca954x 22-0071: probe failed
[ 4.384263] pca954x 23-0071: probe failed
[ 4.699666] pca954x 24-0071: probe failed
2.2 内核代码分析
2.2.1 linux/drivers/i2c/muxes/i2c-mux-pca954x.c
分析几个大体结构函数:
-
- 注册设备,加载驱动, 结构体匹配:
pca954x_of_match
.
- 注册设备,加载驱动, 结构体匹配:
在如下结构体中:
static struct i2c_driver pca954x_driver = {.driver = {.name = "pca954x",.pm = &pca954x_pm,.of_match_table = of_match_ptr(pca954x_of_match),},.probe = pca954x_probe,.remove = pca954x_remove,.id_table = pca954x_id,
};
找到匹配结构体:
static const struct of_device_id pca954x_of_match[] = {
...{ .compatible = "nxp,pca9545", .data = &chips[pca_9545] },
...
}
芯片的描述信息:
static const struct chip_desc chips[] = {...[pca_9545] = {.nchans = 4, //有4个通道.has_irq = 1,.muxtype = pca954x_isswi,.id = { .manufacturer_id = I2C_DEVICE_ID_NONE },},
}
因此,在dts
中,如果添加一行描述:
compatible = "nxp,pca9545";
则会执行相应的驱动调用。
- 2.
probe
调用,加载基本的设备信息,函数pca954x_probe
第一步的信息匹配后,执行函数pca954x_probe
调用,调用关系如下:
static int pca954x_probe(struct i2c_client *client,const struct i2c_device_id *id)
---> struct i2c_mux_core *muxc;
---> struct pca954x *data;
---> bool idle_disconnect_dt;
---> if (!i2c_check_functionality(adap, I2C_FUNC_SMBUS_BYTE)) return -ENODEV;
---> muxc = i2c_mux_alloc(adap, dev, PCA954X_MAX_NCHANS, sizeof(*data), 0,pca954x_select_chan, pca954x_deselect_mux); //选择通道和释放通道
---> i2c_set_clientdata(client, muxc);
---> if (i2c_smbus_write_byte(client, 0) < 0) //通过写数据判断switch是否实际存在与不选择通道
---> data->last_chan = 0;
---> data->idle_state = MUX_IDLE_AS_IS;
---> idle_disconnect_dt = np &&of_property_read_bool(np, "i2c-mux-idle-disconnect"); //获取空闲不选择通道,dts配置
---> ctrl_disconnect_dt = np &&of_property_read_bool(np, "i2c-mux-ctrl-disconnect");
---> ret = pca954x_irq_setup(muxc);
---> for (num = 0; num < data->chip->nchans; num++) {---> ret = i2c_mux_add_adapter(muxc, 0, num, 0);---> ...---> }
---> device_create_file(dev, &dev_attr_idle_state);
---> dev_info(dev, "registered %d multiplexed busses for I2C %s %s\n",num, data->chip->muxtype == pca954x_ismux? "mux" : "switch", client->name);
还需要继续分析文件linux/drivers/i2c/i2c-mux.c
中的函数i2c_mux_alloc()
定义:
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, struct_size(muxc, adapter, max_adapters)+ sizeof_priv, GFP_KERNEL);
---> if (sizeof_priv)---> muxc->priv = &muxc->adapter[max_adapters];
---> muxc->parent = parent;
---> muxc->dev = dev;
---> muxc->select = select;
---> muxc->deselect = deselect;
---> muxc->max_adapters = max_adapters;
也就是说,switch(mux),PCA9545
的parent
设备是i2c_adapter
,自己其实是一个client
,因为i2c_mux_alloc()
调用下一步就是有函数调用:
i2c_set_clientdata(client, muxc);
probe
调用的主要内容就这么多,但是涉及到几个函数,需要在下面继续讲。
- 3.
sysfs
文件系统
在文件中定义了:
static DEVICE_ATTR_RW(idle_state);
在文件:linux/include/linux/device.h
能找到其宏定义展开形式:
#define DEVICE_ATTR_RW(_name) \struct device_attribute dev_attr_##_name = __ATTR_RW(_name)
也就是实际定义的内容如:
static struct device_attribute dev_attr_idle_state = __ATTR_RW(idle_state);
另外,在文件:linux/include/linux/sysfs.h
中定义了:
#define __ATTR_RW(_name) __ATTR(_name, 0644, _name##_show, _name##_store)
#define __ATTR(_name, _mode, _show, _store) { \.attr = {.name = __stringify(_name), \.mode = VERIFY_OCTAL_PERMISSIONS(_mode) }, \.show = _show, \.store = _store, \
}
在文件:linux/include/linux/stringify.h
中定义了:
#define __stringify_1(x...) #x
#define __stringify(x...) __stringify_1(x)
在文件:workspace/Build/kernel/linux/include/linux/kernel.h
中定义了:
/* Permissions on a sysfs file: you didn't miss the 0 prefix did you? */
#define VERIFY_OCTAL_PERMISSIONS(perms) \(BUILD_BUG_ON_ZERO((perms) < 0) + \BUILD_BUG_ON_ZERO((perms) > 0777) + \/* USER_READABLE >= GROUP_READABLE >= OTHER_READABLE */ \BUILD_BUG_ON_ZERO((((perms) >> 6) & 4) < (((perms) >> 3) & 4)) + \BUILD_BUG_ON_ZERO((((perms) >> 3) & 4) < ((perms) & 4)) + \/* USER_WRITABLE >= GROUP_WRITABLE */ \BUILD_BUG_ON_ZERO((((perms) >> 6) & 2) < (((perms) >> 3) & 2)) + \/* OTHER_WRITABLE? Generally considered a bad idea. */ \BUILD_BUG_ON_ZERO((perms) & 2) + \(perms))
#endif
总体而言,针对sysfs
,定义了一个结构体:
static struct device_attribute dev_attr_idle_state =
{.attr = {.name = "idle_state", .mode = VERIFY_OCTAL_PERMISSIONS(_mode) },.show = idle_state_show,.store = idle_state_store,
}
查看函数:idle_state_show
的定义:
static ssize_t idle_state_show(struct device *dev,struct device_attribute *attr,char *buf)
---> struct pca954x *data = i2c_mux_priv(muxc);
---> return sprintf(buf, "%d\n", READ_ONCE(data->idle_state));
这是可以直接使用cat
命令在应用层获取到状态的。返回值在文件:linux/include/dt-bindings/mux/mux.h
中有定义:
#define MUX_IDLE_AS_IS (-1)
#define MUX_IDLE_DISCONNECT (-2)
接着查看函数:idle_state_store
的定义:
static ssize_t idle_state_store(struct device *dev,struct device_attribute *attr,const char *buf, size_t count)
---> ret = kstrtoint(buf, 0, &val);
---> i2c_lock_bus(muxc->parent, I2C_LOCK_SEGMENT);
---> WRITE_ONCE(data->idle_state, val);
---> if (data->last_chan || val != MUX_IDLE_DISCONNECT)---> ret = pca954x_deselect_mux(muxc, 0);
---> i2c_unlock_bus(muxc->parent, I2C_LOCK_SEGMENT);
---> return ret < 0 ? ret : count;
从上面的代码里面可以了解到一个简单的sysfs
结点的创建方法。
1.定义: static DEVICE_ATTR_RW(idle_state);
2.创建: device_create_file(dev, &dev_attr_idle_state);
3.定义函数暴漏给用户空间: idle_state_store, idle_state_show
- 选择通道与释放通道函数
pca954x_select_chan
,pca954x_deselect_mux
选择通道函数:pca954x_select_chan
:
static int pca954x_select_chan(struct i2c_mux_core *muxc, u32 chan)
---> u8 regval;
---> regval = 1 << chan;
---> if ((data->last_chan != regval) ) ---> ret = pca954x_reg_write(muxc->parent, client, regval);---> data->last_chan = ret < 0 ? 0 : regval;
如何对芯片发生写命令?查看函数调用pca954x_reg_write()
:
static int pca954x_reg_write(struct i2c_adapter *adap,struct i2c_client *client, u8 val)
---> union i2c_smbus_data dummy;
---> return __i2c_smbus_xfer(adap, client->addr, client->flags,I2C_SMBUS_WRITE, val,I2C_SMBUS_BYTE, &dummy);
这一个函数有一个明确的说明:
/* Write to mux register. Don't use i2c_transfer()/i2c_smbus_xfer()for this as they will try to lock adapter a second time */
释放通道函数:pca954x_deselect_mux()
,定义如下:
static int pca954x_deselect_mux(struct i2c_mux_core *muxc, u32 chan)
---> idle_state = READ_ONCE(data->idle_state);
---> ctrl_state = READ_ONCE(data->ctrl_state);
---> if (idle_state >= 0) return pca954x_select_chan(muxc, idle_state);
---> if (idle_state == MUX_IDLE_DISCONNECT || \(ctrl_state && ((root = i2c_root_adapter(&muxc->parent->dev))?g_enable_mux_disconnect[root->nr]:FALSE))) ---> data->last_chan = 0;---> return pca954x_reg_write(muxc->parent, client,data->last_chan);
因此,如果在dts
中定义了:i2c-mux-idle-disconnect
,实际的变量赋值将为:
data->idle_state = MUX_IDLE_DISCONNECT;
也就是说释放switch
结果即为将switch
所有通道关闭,switch
芯片后面的设备将不会被扫描到。
另外一点:idle_state
其实也有一个等同于chan
的变量的意思。
- 检查
switch
设备存在性
在函数pca954x_probe()
中调用了i2c_smbus_write_byte()
判断设备存在性。可以参考文档:https://www.kernel.org/doc/html/v6.11/i2c/smbus-protocol.html中的说明。函数定义在文件:
linux/drivers/i2c/i2c-core-smbus.c
中:
/*** i2c_smbus_write_byte - SMBus "send byte" protocol* @client: Handle to slave device* @value: Byte to be sent** This executes the SMBus "send byte" protocol, returning negative errno* else zero on success.*/
s32 i2c_smbus_write_byte(const struct i2c_client *client, u8 value)
{return i2c_smbus_xfer(client->adapter, client->addr, client->flags,I2C_SMBUS_WRITE, value, I2C_SMBUS_BYTE, NULL);
}
EXPORT_SYMBOL(i2c_smbus_write_byte);
即通过关闭switch
所有通道,寄存器写0
,如果函数返回小于0
,认为switch
不存在。
- 注册到
i2c
管理系统中,函数:i2c_mux_add_adapter()
函数:i2c_mux_add_adapter()
定义在文件:linux/drivers/i2c/i2c-mux.c
中:
int i2c_mux_add_adapter(struct i2c_mux_core *muxc,u32 force_nr, u32 chan_id,unsigned int class)
---> struct i2c_mux_priv *priv;
---> priv = kzalloc(sizeof(*priv), GFP_KERNEL);
---> priv->muxc = muxc;
---> priv->chan_id = chan_id;
---> priv->algo.functionality = i2c_mux_functionality;
---> snprintf(priv->adap.name, sizeof(priv->adap.name),"i2c-%d-mux (chan_id %d)", i2c_adapter_id(parent), chan_id);
---> 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;
---> if (muxc->dev->of_node) {---> if (!child) {---> for_each_child_of_node(mux_node, child) {---> ret = of_property_read_u32(child, "reg", ®);---> if (chan_id == reg) //获取reg设置的值,PCA9545通道id---> break;
---> ret = i2c_add_adapter(&priv->adap);
---> sysfs_create_link(&priv->adap.dev.kobj, &muxc->dev->kobj,"mux_device")
---> snprintf(symlink_name, sizeof(symlink_name), "channel-%u", chan_id);
---> dev_info(&parent->dev, "Added multiplexed i2c bus %d\n",i2c_adapter_id(&priv->adap))
---> muxc->adapter[muxc->num_adapters++] = &priv->adap;
到这里,基本上整个的一个pca9545
的模块驱动就说的差不多了。
2.2.2 i2c bus
驱动与dts
分析
这一块的代码主要是查看2个文件:
linux/drivers/i2c/busses/i2c-ast2600.c
linux/arch/arm/boot/dts/aspeed-g6.dtsi
dts
文件内容如下:
i2c11: i2c-bus@600 {#address-cells = <1>;#size-cells = <0>;#interrupt-cells = <1>;reg = <0x600 0x80>, <0xd60 0x20>;compatible = "aspeed,ast2600-i2c-bus";clocks = <&syscon ASPEED_CLK_APB2>;resets = <&syscon ASPEED_RESET_I2C>;interrupts = <GIC_SPI 121 IRQ_TYPE_LEVEL_HIGH>;bus-frequency = <100000>;pinctrl-names = "default";pinctrl-0 = <&pinctrl_i2c12_default>;status = "disabled";bus-mode = <I2C_BUS11_MODE>;};
- 设备探测
在文件:linux/arch/arm/boot/dts/aspeed-g6.dtsi
中定义了compatible = "aspeed,ast2600-i2c-bus";
后,设备会被文件:linux/drivers/i2c/busses/i2c-ast2600.c
中的结构体:
static const struct of_device_id ast2600_i2c_bus_of_table[] = {{.compatible = "aspeed,ast2600-i2c-bus",},{}
};static struct platform_driver ast2600_i2c_bus_driver = {.probe = ast2600_i2c_probe,.remove = ast2600_i2c_remove,.driver = {.name = KBUILD_MODNAME,.of_match_table = ast2600_i2c_bus_of_table,},
};
module_platform_driver(ast2600_i2c_bus_driver);
匹配后,执行函数ast2600_i2c_probe()
,调用关系如下:
static int ast2600_i2c_probe(struct platform_device *pdev)
---> struct ast2600_i2c_bus *i2c_bus;
---> i2c_bus = devm_kzalloc(dev, sizeof(*i2c_bus), GFP_KERNEL);
---> regmap_read(i2c_bus->global_reg, AST2600_I2CG_CTRL, &global_ctrl); //0xc寄存器
---> ret = of_property_read_u32(pdev->dev.of_node, "bus-mode", &bus);//读取dts中的bus-mode
---> i2c_bus->clk = devm_clk_get(i2c_bus->dev, NULL);
---> i2c_bus->apb_clk = clk_get_rate(i2c_bus->clk);
---> ret = of_property_read_u32(pdev->dev.of_node, "bus-frequency", &i2c_bus->bus_frequency); //从dts中读取bus-frequency
---> i2c_bus->adap.algo = &i2c_ast2600_algorithm;
---> i2c_bus->adap.retries = retry;
---> i2c_bus->adap.dev.parent = i2c_bus->dev; //即struct device *dev = &pdev->dev;
---> i2c_bus->adap.dev.of_node = pdev->dev.of_node;
---> i2c_bus->adap.algo_data = i2c_bus;
---> strscpy(i2c_bus->adap.name, pdev->name, sizeof(i2c_bus->adap.name));
---> i2c_set_adapdata(&i2c_bus->adap, i2c_bus);
---> ast2600_i2c_init(i2c_bus);
---> ret = devm_request_irq(dev, i2c_bus->irq, ast2600_i2c_bus_irq, 0,dev_name(dev), i2c_bus);
---> ret = i2c_add_adapter(&i2c_bus->adap);
---> dev_info(dev, "%s [%d]: adapter [%d khz] mode [%d]\n",dev->of_node->name, i2c_bus->adap.nr, i2c_bus->bus_frequency / 1000,i2c_bus->mode);
其中,bus mode
,即:i2c_bus->mode
定义如下:
0: byte mode
1: Buff mode
2: DMA mode
- 初始化
aspeed i2c
硬件
初始化硬件,调用的函数为:ast2600_i2c_init()
,调用关系如下:
static void ast2600_i2c_init(struct ast2600_i2c_bus *i2c_bus)
---> u32 fun_ctrl = AST2600_I2CC_BUS_AUTO_RELEASE | AST2600_I2CC_MASTER_EN;
---> writel(0, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL);//写寄存器0x00
---> i2c_bus->multi_master = device_property_read_bool(&pdev->dev, "multi-master");//dts读取属性
---> if (!i2c_bus->multi_master)---> fun_ctrl |= AST2600_I2CC_MULTI_MASTER_DIS;
---> writel(fun_ctrl, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL);/* Enable Master Mode */
---> writel(0, i2c_bus->reg_base + AST2600_I2CS_ADDR_CTRL); /* disable slave address */
---> writel(ast2600_select_i2c_clock(i2c_bus), i2c_bus->reg_base + AST2600_I2CC_AC_TIMING); /* Set AC Timing */
---> writel(GENMASK(27, 0), i2c_bus->reg_base + AST2600_I2CM_ISR); /* Clear Interrupt */
---> 如果使能了i2c slave模式,使用的非dma模式
---> writel(GENMASK(27, 0), i2c_bus->reg_base + AST2600_I2CS_ISR);
---> writel(GENMASK(15, 0), i2c_bus->reg_base + AST2600_I2CS_IER);//byte模式
- 数据传输定义
数据传输的函数定义在结构体i2c_ast2600_algorithm
中,内容如下:
static struct i2c_algorithm i2c_ast2600_algorithm = {.master_xfer = ast2600_i2c_master_xfer,.master_xfer_atomic = ast2600_i2c_master_xfer,
#if IS_ENABLED(CONFIG_I2C_SLAVE).reg_slave = ast2600_i2c_reg_slave,.unreg_slave = ast2600_i2c_unreg_slave,
#endif.functionality = ast2600_i2c_functionality,
};
前面分析的PCA9545 switch
中使用的代码:
if (!i2c_check_functionality(adap, I2C_FUNC_SMBUS_BYTE))return -ENODEV;
其中i2c_check_functionality()
在文件linux/include/linux/i2c.h
定义:
/* Return the functionality mask */
static inline u32 i2c_get_functionality(struct i2c_adapter *adap)
{return adap->algo->functionality(adap);
}/* Return 1 if adapter supports everything we need, 0 if not. */
static inline int i2c_check_functionality(struct i2c_adapter *adap, u32 func)
{return (func & i2c_get_functionality(adap)) == func;
}
查看函数:ast2600_i2c_functionality()
定义:
static u32 ast2600_i2c_functionality(struct i2c_adapter *adap)
{return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_BLOCK_PROC_CALL;
}
在文件:linux/include/uapi/linux/i2c.h
中定义了:
#define I2C_FUNC_SMBUS_EMUL (I2C_FUNC_SMBUS_QUICK | \I2C_FUNC_SMBUS_BYTE | \I2C_FUNC_SMBUS_BYTE_DATA | \I2C_FUNC_SMBUS_WORD_DATA | \I2C_FUNC_SMBUS_PROC_CALL | \I2C_FUNC_SMBUS_WRITE_BLOCK_DATA | \I2C_FUNC_SMBUS_I2C_BLOCK | \I2C_FUNC_SMBUS_PEC)
- 注册到
i2c bus
架构,函数:i2c_add_adapter()
在文件:linux/drivers/i2c/i2c-core-base.c
中定义了函数i2c_add_adapter()
,调用关系如下:
int i2c_add_adapter(struct i2c_adapter *adapter)
---> if (dev->of_node) {---> id = of_alias_get_id(dev->of_node, "i2c"); //在dts属性mux下定义了i2cxx.对应alas i2cxx---> if (id >= 0) {---> adapter->nr = id;---> return __i2c_add_numbered_adapter(adapter);
---> ...
另外,函数:__i2c_add_numbered_adapter
定义如下:
static int __i2c_add_numbered_adapter(struct i2c_adapter *adap)
---> mutex_lock(&core_lock);
---> id = idr_alloc(&i2c_adapter_idr, adap, adap->nr, adap->nr + 1, GFP_KERNEL);
---> mutex_unlock(&core_lock);
---> return i2c_register_adapter(adap);
函数:i2c_register_adapter()
定义如下:
static int i2c_register_adapter(struct i2c_adapter *adap)
---> if (!adap->lock_ops)---> adap->lock_ops = &i2c_adapter_lock_ops;
---> if (adap->timeout == 0)---> adap->timeout = HZ;
---> dev_set_name(&adap->dev, "i2c-%d", adap->nr);
---> adap->dev.bus = &i2c_bus_type;
---> adap->dev.type = &i2c_adapter_type;
---> res = device_register(&adap->dev);
---> of_i2c_register_devices(adap);
---> if (adap->nr < __i2c_first_dynamic_bus_num)---> i2c_scan_static_board_info()
---> bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
函数:i2c_scan_static_board_info()
调用关系如下:
static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
---> list_for_each_entry(devinfo, &__i2c_board_list, list) {---> if (devinfo->busnum == adapter->nr && !i2c_new_device(adapter,&devinfo->board_info))---> ..---> }
另外,i2c_new_device()
调用关系如下:
struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
---> struct i2c_client *ret;
---> ret = i2c_new_client_device(adap, info);
比较重要的函数i2c_new_client_device()
定义如下:
struct i2c_client *
i2c_new_client_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
---> struct i2c_client *client;
---> client = kzalloc(sizeof *client, GFP_KERNEL);
---> client->adapter = adap;
---> client->addr = info->addr;
---> client->dev.parent = &client->adapter->dev;
---> client->dev.bus = &i2c_bus_type;
---> client->dev.type = &i2c_client_type;
---> i2c_dev_set_name(adap, client, info);
---> status = device_register(&client->dev);
还有,文件中linux/drivers/i2c/i2c-core-of.c
中定义了of_i2c_register_devices()
void of_i2c_register_devices(struct i2c_adapter *adap)
---> struct device_node *bus, *node;
---> bus = of_get_child_by_name(adap->dev.of_node, "i2c-bus");
---> for_each_available_child_of_node(bus, node) {---> client = of_i2c_register_device(adap, node);
另外,函数of_i2c_register_device()
定义如下:
static struct i2c_client *of_i2c_register_device(struct i2c_adapter *adap,struct device_node *node)
---> ret = of_i2c_get_board_info(&adap->dev, node, &info);
函数:of_i2c_get_board_info()
定义如下:
int of_i2c_get_board_info(struct device *dev, struct device_node *node,struct i2c_board_info *info)
---> ret = of_property_read_u32(node, "reg", &addr);
---> info->addr = addr; //i2c设备的地址,例如PCA9545值为0x70
---> info->of_node = node;
3.回到问题本身
3.1 i2cdetect -y i2cbus
找不到设备
这一篇文章主要是追问题,为什么在bus 12(实际是11)
找不到设备?截图如下:
# i2cdetect -y 110 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: UU -- -- -- -- -- -- --
3.2 简单分析i2c-tool
源码
下载源码:https://github.com/oudream/i2c-tools后,找到文件tools\i2cdetect.c
:
static int scan_i2c_bus(int file, int mode, unsigned long funcs,int first, int last)
---> if (ioctl(file, I2C_SLAVE, i+j) < 0) {---> if (errno == EBUSY) {---> printf("UU ");
追踪到底层驱动,实际调用的是文件:linux/drivers/i2c/i2c-dev.c
中的函数:
static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
---> case I2C_SLAVE:---> if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg))---> return -EBUSY;
分析函数:i2cdev_check_addr()
/* This address checking function differs from the one in i2c-corein that it considers an address with a registered device, but nodriver bound to it, as NOT busy. */
static int i2cdev_check_addr(struct i2c_adapter *adapter, unsigned int addr)
{struct i2c_adapter *parent = i2c_parent_is_i2c_adapter(adapter);int result = 0;if (parent)result = i2cdev_check_mux_parents(parent, addr);if (!result)result = device_for_each_child(&adapter->dev, &addr,i2cdev_check_mux_children);return result;
}/* recurse down mux tree */
static int i2cdev_check_mux_children(struct device *dev, void *addrp)
{int result;if (dev->type == &i2c_adapter_type)result = device_for_each_child(dev, addrp,i2cdev_check_mux_children);elseresult = i2cdev_check(dev, addrp);return result;
}static int i2cdev_check(struct device *dev, void *addrp)
{struct i2c_client *client = i2c_verify_client(dev);if (!client || client->addr != *(unsigned int *)addrp)return 0;return dev->driver ? -EBUSY : 0;
}
因此,对于实际的物理链路:i2c11
,发现如果一个设备地址是mux(switch)
,内核层就会返回-EBUSY
,对于i2cdetect -y
就会返回UU
。刚好可以对应到7bit
地址0x70
,即8bit
地址0xe0
,这就是说明地址其实是扫描到了!!!
继续使用命令:
# ls /sys/class/i2c-dev/i2c-11/device/
11-0070 i2c-26 i2c-dev of_node uevent
delete_device i2c-27 name power
i2c-25 i2c-63 new_device subsystem
可以看到物理i2c-11
下有一些虚拟的设备:
i2c-25
i2c-26
i2c-27
i2c-63
或者可以使用工具:
# i2cdetect -l
i2c-63 i2c i2c-11-mux (chan_id 3) I2C adapter
i2c-25 i2c i2c-11-mux (chan_id 0) I2C adapter
i2c-15 i2c 1e78a800.i2c-bus I2C adapter
i2c-3 i2c 1e78a200.i2c-bus I2C adapter
i2c-33 i2c i2c-7-mux (chan_id 3) I2C adapter
i2c-23 i2c i2c-9-mux (chan_id 2) I2C adapter
i2c-13 i2c 1e78a700.i2c-bus I2C adapter
i2c-1 i2c 1e78a100.i2c-bus I2C adapter
i2c-31 i2c i2c-7-mux (chan_id 1) I2C adapter
i2c-21 i2c i2c-9-mux (chan_id 0) I2C adapter
i2c-11 i2c 1e78a600.i2c-bus I2C adapter
i2c-8 i2c 1e78a480.i2c-bus I2C adapter
i2c-18 i2c i2c-6-mux (chan_id 0) I2C adapter
i2c-6 i2c 1e78a380.i2c-bus I2C adapter
i2c-26 i2c i2c-11-mux (chan_id 1) I2C adapter
i2c-16 i2c i2c-2-mux (chan_id 0) I2C adapter
i2c-4 i2c 1e78a280.i2c-bus I2C adapter
i2c-62 i2c i2c-6-mux (chan_id 3) I2C adapter
i2c-24 i2c i2c-9-mux (chan_id 3) I2C adapter
i2c-14 i2c 1e78a780.i2c-bus I2C adapter
i2c-2 i2c 1e78a180.i2c-bus I2C adapter
i2c-32 i2c i2c-7-mux (chan_id 2) I2C adapter
i2c-22 i2c i2c-9-mux (chan_id 1) I2C adapter
i2c-12 i2c 1e78a680.i2c-bus I2C adapter
i2c-0 i2c 1e78a080.i2c-bus I2C adapter
i2c-30 i2c i2c-7-mux (chan_id 0) I2C adapter
i2c-20 i2c i2c-6-mux (chan_id 2) I2C adapter
i2c-9 i2c 1e78a500.i2c-bus I2C adapter
i2c-10 i2c 1e78a580.i2c-bus I2C adapter
i2c-19 i2c i2c-6-mux (chan_id 1) I2C adapter
i2c-7 i2c 1e78a400.i2c-bus I2C adapter
i2c-27 i2c i2c-11-mux (chan_id 2) I2C adapter
i2c-17 i2c i2c-2-mux (chan_id 1) I2C adapter
i2c-5 i2c 1e78a300.i2c-bus I2C adapter
对应实际可以扫描到i2c-11
虚拟出来的i2c-27
外接的设备地址:
# i2cdetect -y 270 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: 20 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- 38 -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- 48 49 -- -- -- -- -- --
50: 50 -- -- -- -- -- -- -- 58 -- -- -- -- -- -- --
60: 60 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: UU -- -- -- -- -- -- --
对应,可以看到0x70
仍然是忙碌的。
另外:
# i2cdetect -y 250 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: UU -- -- -- -- -- -- --