当前位置: 首页 > news >正文

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

分析几个大体结构函数:

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

http://www.mrgr.cn/news/33042.html

相关文章:

  • 暴力枚举算法
  • 嵌入式入门小工程
  • Impala如何使用
  • 刷题训练之栈
  • 面向对象设计原则例题
  • Go websocket
  • 怎么让Nginx可以访问某一IP的每个后台controller接口
  • 【IEEE 独立出版,快速EI检索】第四届人工智能、虚拟现实与可视化国际学术会议(AIVRV 2024)
  • [JavaEE] TCP协议
  • 有什么行为习惯昭示着你是个编程大佬?
  • 大语言模型的发展-OPENBMB
  • 2409js,学习js2
  • 推荐几本值得阅读的书籍!
  • 职业技能大赛-自动化测试笔记分享-2
  • 从零开始:在VSCode中打造完美的C++开发环境
  • mysql学习教程,从入门到精通,SQL 删除表(DROP TABLE 语句)(21)
  • 深耕电通二十年,崔光荣升电通中国首席执行官
  • Linux基础---13三剑客及正则表达式
  • 网络丢包定位记录(二)
  • AI一点通: 简化大数据与深度学习工作流程, Apache Spark、PyTorch 和 Mosaic Streaming