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打印出来的数据:

c 复制代码
[    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

分析几个大体结构函数:

    1. 注册设备,加载驱动, 结构体匹配:pca954x_of_match.

在如下结构体中:

c 复制代码
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,
};

找到匹配结构体:

c 复制代码
static const struct of_device_id pca954x_of_match[] = {
...
	{ .compatible = "nxp,pca9545", .data = &chips[pca_9545] },
...
}

芯片的描述信息:

c 复制代码
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中,如果添加一行描述:

c 复制代码
compatible = "nxp,pca9545";

则会执行相应的驱动调用。

  • 2.probe调用,加载基本的设备信息,函数pca954x_probe
    第一步的信息匹配后,执行函数pca954x_probe调用,调用关系如下:
c 复制代码
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()定义:

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, 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),PCA9545parent设备是i2c_adapter ,自己其实是一个client,因为i2c_mux_alloc()调用下一步就是有函数调用:

c 复制代码
i2c_set_clientdata(client, muxc);

probe调用的主要内容就这么多,但是涉及到几个函数,需要在下面继续讲。

  • 3.sysfs文件系统

在文件中定义了:

c 复制代码
static DEVICE_ATTR_RW(idle_state);

在文件:linux/include/linux/device.h能找到其宏定义展开形式:

c 复制代码
#define DEVICE_ATTR_RW(_name) \
	struct device_attribute dev_attr_##_name = __ATTR_RW(_name)

也就是实际定义的内容如:

c 复制代码
static struct device_attribute dev_attr_idle_state = __ATTR_RW(idle_state);

另外,在文件:linux/include/linux/sysfs.h中定义了:

c 复制代码
#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中定义了:

c 复制代码
#define __stringify_1(x...)	#x
#define __stringify(x...)	__stringify_1(x)

在文件:workspace/Build/kernel/linux/include/linux/kernel.h中定义了:

c 复制代码
/* 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,定义了一个结构体:

c 复制代码
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的定义:

c 复制代码
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中有定义:

c 复制代码
#define MUX_IDLE_AS_IS      (-1)
#define MUX_IDLE_DISCONNECT (-2)

接着查看函数:idle_state_store的定义:

c 复制代码
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结点的创建方法。

c 复制代码
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_chanpca954x_deselect_mux

选择通道函数:pca954x_select_chan:

c 复制代码
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():

c 复制代码
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);			     

这一个函数有一个明确的说明:

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 */

释放通道函数:pca954x_deselect_mux(),定义如下:

c 复制代码
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,实际的变量赋值将为:

c 复制代码
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中:

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中:
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", &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个文件:

c 复制代码
linux/drivers/i2c/busses/i2c-ast2600.c
linux/arch/arm/boot/dts/aspeed-g6.dtsi

dts文件内容如下:

c 复制代码
	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中的结构体:
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(),调用关系如下:

c 复制代码
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定义如下:

c 复制代码
0: byte mode
1: Buff mode
2: DMA mode
  • 初始化aspeed i2c硬件
    初始化硬件,调用的函数为:ast2600_i2c_init(),调用关系如下:
c 复制代码
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中,内容如下:
c 复制代码
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中使用的代码:

c 复制代码
if (!i2c_check_functionality(adap, I2C_FUNC_SMBUS_BYTE))
	return -ENODEV;

其中i2c_check_functionality()在文件linux/include/linux/i2c.h定义:

c 复制代码
/* 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()定义:

c 复制代码
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中定义了:

c 复制代码
#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(),调用关系如下:

c 复制代码
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定义如下:

c 复制代码
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()定义如下:

c 复制代码
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()调用关系如下:

c 复制代码
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()调用关系如下:

c 复制代码
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()定义如下:

c 复制代码
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()

c 复制代码
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()定义如下:

c 复制代码
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()定义如下:

c 复制代码
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)找不到设备?截图如下:

c 复制代码
# i2cdetect -y 11
     0  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:

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中的函数:

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()

c 复制代码
/* This address checking function differs from the one in i2c-core
   in that it considers an address with a registered device, but no
   driver 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);
	else
		result = 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这就是说明地址其实是扫描到了!!!

继续使用命令:

c 复制代码
# 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下有一些虚拟的设备:

c 复制代码
i2c-25
i2c-26
i2c-27
i2c-63

或者可以使用工具:

c 复制代码
# 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外接的设备地址:

c 复制代码
# i2cdetect -y 27
     0  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仍然是忙碌的。

另外:

c 复制代码
# i2cdetect -y 25
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: UU -- -- -- -- -- -- --
相关推荐
blessing。。26 分钟前
I2C学习
linux·单片机·嵌入式硬件·嵌入式
2202_7544215441 分钟前
生成MPSOC以及ZYNQ的启动文件BOOT.BIN的小软件
java·linux·开发语言
运维&陈同学1 小时前
【zookeeper03】消息队列与微服务之zookeeper集群部署
linux·微服务·zookeeper·云原生·消息队列·云计算·java-zookeeper
旦沐已成舟2 小时前
DevOps-Jenkins-新手入门级
服务器
嵌新程2 小时前
day03(单片机高级)RTOS
stm32·单片机·嵌入式硬件·freertos·rtos·u575
Lin2012302 小时前
STM32 Keil5 attribute 关键字的用法
stm32·单片机·嵌入式硬件
电工小王(全国可飞)2 小时前
STM32 RAM在Memory Map中被分为3个区域
stm32·单片机·嵌入式硬件
周末不下雨2 小时前
win11+ubuntu22.04双系统 | 联想 24 y7000p | ubuntu 22.04 | 把ubuntu系统装到1T的移动固态硬盘上!!!
linux·运维·ubuntu
maxiumII2 小时前
Diving into the STM32 HAL-----DAC笔记
笔记·stm32·嵌入式硬件
软件技术员3 小时前
Let‘s Encrypt SSL证书:acmessl.cn申请免费3个月证书
服务器·网络协议·ssl