BMC 虚拟i2c访问PCA9545(switch芯片)后面的设备,为什么找不到PCA9545?

news2025/1/20 22:06:09

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

分析几个大体结构函数:

    1. 注册设备,加载驱动, 结构体匹配: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),PCA9545parent设备是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_chanpca954x_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", &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 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:

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-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这就是说明地址其实是扫描到了!!!

继续使用命令:

# 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 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仍然是忙碌的。

另外:

# 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 -- -- -- -- -- -- --

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2153459.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Mudslide

作者未提供代码

Qt/C++ TCP调试助手V1.1 新增图像传输与接收功能(附发布版下载链接)

发布版本链接 通过百度网盘分享的文件&#xff1a;TCP调试助手V1.zip&#xff08;含客户端与服务器&#xff09; 链接&#xff1a;https://pan.baidu.com/s/14LTRPChPhYdwp_s6KeyBiA?pwdcedu 提取码&#xff1a;cedu 基于Qt/C实现了一款功能丰富的TCP服务器与客户端调试助手…

HT876 带任意限幅的10.9Wx2高保真音频功放

特点 可任意配置的限幅功能 自由选择音频限制幅度&#xff0c;使输出音频信号限制在固定 失真水平内 内置自动限温控制功能 支持AB类与D类切换 THDN:0.02%(VDD8.4V, RL 4Ω, fIN 1kHz, Po 2x1.0W, BTL) 输出功率(fIN1kHZ,THDN10%) 2x10.9W (VDD9.0V, RL4Ω, BTL) VDD供电范围:2…

【C++】模拟实现二叉搜索(排序)树

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:实战项目集 ⚙️操作环境:Visual Studio 2022 目录 一.了解项目功能 二.逐步实现项目功能模块及其逻辑详解 &#x1f4cc;实现BSTreeNode类模板 &#x1f38f;构造BSTreeNode类成员变量 &#x1f38f;实现BSTreeNode类构…

空间解析几何2:空间中两线段/直线的距离【附MATLAB代码】

目录 理论公式 MATLAB代码 理论公式 MATLAB代码 公式实现 function [dis,P,Q,t1,s1]line2LineDistance(A1,B1,C1,D1) %求两线段的最短距离 % input % A1,B1为线段一的两端点 C1,D1为线段二的两端点 % output % dis,为两线段的最短距离&#xff0c;P,Q为距离最短时在两线段上…

10.2 溪降技术:双重检查

目录 10.2 双重检查概览观看视频课程电子书&#xff1a;双重检查场景场景 1场景 2 个人责任示例 1示例 2 总结 10.2 双重检查 概览 俗话说&#xff1a;“江山易改&#xff0c;本性难移”。在我们开始体验峡谷探险时&#xff0c;培养良好的习惯对我们的进一步发展至关重要。在所…

Spring AOP的应用

目录 1、maven坐标配置与xml头配置 2、代理方式的选择与配置 3、AOP的三种配置方式 3.1、XML模式 3.1.1 创建目标类和方法 3.1.2 创建切面 3.1.3 切面xml配置与表达式说明 3.1.4 单测 3.2 纯注解模式 3.2.1 开启注解相关配置 3.2.2 创建目标类和方法 3.2.3 创建切面…

ChatGPT 4o 使用指南 (9月更新)

首先基础知识还是要介绍得~ 一、模型知识&#xff1a; GPT-4o&#xff1a;最新的版本模型&#xff0c;支持视觉等多模态&#xff0c;OpenAI 文档中已经更新了 GPT-4o 的介绍&#xff1a;128k 上下文&#xff0c;训练截止 2023 年 10 月&#xff08;作为对比&#xff0c;GPT-4…

java之斗地主部分功能的实现

今天我们要实现斗地主中发牌和洗牌这两个功能&#xff0c;该如何去实现呢&#xff1f; 1.创建牌类&#xff1a;52张牌每一张牌包含两个属性:牌的大小和牌的花色。 故我们优先创建一个牌的类(Card)&#xff1a;包含大小和花色。 public class Card { //单张牌的大小及类型/…

20240921在友善之臂的NanoPC-T6开发板上使用Rockchip原厂的Android12适配宸芯的数传模块CX6602N

127|console:/ # uname -a console:/ # ifconfig console:/ # ifconfig -a console:/ # ifconfig -a 130|console:/ # ifconfig usb0 192.168.42.130 console:/ # console:/ # ifconfig console:/ # iperf3 -s & iperf3 -c 192.168.42.130 -i 1 -t 30 20240921在友善之臂的…

828华为云征文|华为云Flexus云服务器X实例之openEuler系统下部署Grav内容管理系统

828华为云征文&#xff5c;华为云Flexus云服务器X实例之openEuler系统下部署Grav内容管理系统 前言一、Flexus云服务器X实例介绍1.1 Flexus云服务器X实例简介1.2 Flexus云服务器X实例特点1.3 Flexus云服务器X实例使用场景 二、Grav介绍2.1 CMS介绍2.2 Grav简介2.3 Grav特点2.4 …

TinyML-On-The-Fly: 实时、低功耗、低成本的微控制器嵌入式设备内计算机视觉技术用于无人机图像分类

这篇论文的标题是《TinyML-On-The-Fly: Real-Time Low-Power and Low-Cost MCU-Embedded On-Device Computer Vision for Aerial Image Classification》&#xff0c;作者是 Riya Samanta, Bidyut Saha, Soumya K. Ghosh&#xff0c;来自印度理工学院克勒格布尔分校。论文主要研…

电子元器件之MOS管,附上几个常用MOS管电路和仿真。

MOS管是一种常用的电子元器件。 1.MOS管的类别 MOSFET简称MOS&#xff0c;是一种绝缘栅型场效应管。按照类别可以分为增强型mos管和耗尽型mos管。 导电沟道的形成方式‌ 增强型MOS管&#xff1a;在没有外加电压时&#xff0c;源极和漏极之间没有导电沟道存在。只有当栅极电…

【玉米田】

题目 代码 #include <bits/stdc.h> using namespace std; typedef long long LL;const int mod 1e8; const int M 1 << 12; LL f[13][M]; int g[13]; vector<int> state; vector<int> p[M]; int n, m; bool check(int x) {return !(x & x <&…

攻防世界---->Windows_Reverse1(补)

做题笔记。 做题回顾。 假设&#xff0c;我们不知道地址随机怎么办&#xff1f;不能动调&#xff0c;只能静态分析。 下载 查壳 upx脱壳。 32ida打开。 动调报错。 重新打开&#xff0c;静态分析。 跟进关键函数。 不明白可以反汇编和汇编一起看。 溯源。 *decode 取值等于 by…

分布式锁之 防误删(优化之UUID防误删)

文章目录 1、AlbumInfoApiController --》testLock()2、AlbumInfoServiceImpl --》testLock()3、问题&#xff1a;删除操作缺乏原子性。 实现如下&#xff1a; 1、AlbumInfoApiController --》testLock() Tag(name "专辑管理") RestController RequestMapping(&quo…

【计网】从零开始掌握序列化与反序列化 --- 基础知识储备与程序重构

从零开始掌握序列化与反序列化 1 初识序列化与反序列化2 再谈Tcp协议3 程序重构3.1 Socket类3.2 回调函数设计3.3 最终的Tcp服务器类 1 初识序列化与反序列化 在刚学习计算机网络时&#xff0c;我们谈到过网络协议栈&#xff0c;其中最上层的就是应用层&#xff0c;那么这个应…

Qt圆角窗口

Qt圆角窗口 问题&#xff1a;自己重写了一个窗口&#xff0c;发现用qss设置圆角了&#xff0c;但是都不生效&#xff0c;不过子窗口圆角都生效了。 无边框移动窗口 bool eventFilter(QObject *watched, QEvent *evt) {static QPoint mousePoint;static bool mousePressed f…

开源、极简的B站第三方,建议所有人收藏

很多人说B站落寞了&#xff0c;但我觉得不是B站落寞&#xff0c;而是长视频落寞了。现代人已经没有充足的耐心&#xff0c;刷完一个十分钟的视频。毕竟&#xff0c;短视频可以把这十分钟切成50份&#xff0c;让我们开心50次。 可怕的是&#xff0c;B站即使落寞&#xff0c;在长…

继承的例题

答案&#xff1a;D 解析&#xff1a;C允许一个子类继承多个父类 知识点&#xff1a; 子类是父类的特殊化&#xff0c;父类是子类的泛化 解析&#xff1a;子类可以共享父类的属性和方法&#xff0c;选项A正确 面向对象关系中&#xff0c;类与类的关系包含继承&#xff0c;包…