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 -- -- -- -- -- -- --
相关推荐
白掰虾2 分钟前
STM32N6&AI资料汇总
人工智能·stm32·嵌入式硬件·stm32n6·stm32ai
Aczone2829 分钟前
硬件(十)IMX6ULL 中断与时钟配置
arm开发·单片机·嵌入式硬件·fpga开发
烟雨书信37 分钟前
LINUX中Docker Swarm的介绍和使用
java·linux·docker
代码的余温1 小时前
Linux内核调优实战指南
linux·服务器·数据库
机器视觉知识推荐、就业指导1 小时前
单片机关于中断的理解
单片机·嵌入式硬件
is08151 小时前
NFS 服务器 使用
服务器·网络
星空的资源小屋1 小时前
Digital Clock 4,一款免费的个性化桌面数字时钟
stm32·单片机·嵌入式硬件·电脑·excel
m0_694845572 小时前
教你使用服务器如何搭建数据库
linux·运维·服务器·数据库·云计算
空灵之海2 小时前
Ubuntu Server 22.04.5系统安装教程
linux·运维·ubuntu
gamers2 小时前
rock linux 9 安装mysql 5.7.44
linux·mysql·adb