mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Ahmad Byagowi <ahmadexp@gmail.com>
To: netdev@vger.kernel.org
Cc: Lee Jones <lee@kernel.org>, Pavel Machek <pavel@kernel.org>,
	Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	Conor Dooley <conor+dt@kernel.org>,
	Andi Shyti <andi.shyti@kernel.org>,
	Peter Rosin <peda@lysator.liu.se>,
	Nam Tran <trannamatk@gmail.com>,
	Vadim Fedorenko <vadim.fedorenko@linux.dev>,
	Richard Cochran <richardcochran@gmail.com>,
	Andrew Lunn <andrew+netdev@lunn.ch>,
	"David S . Miller" <davem@davemloft.net>,
	Eric Dumazet <edumazet@google.com>,
	Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
	Kees Cook <kees@kernel.org>,
	"Gustavo A . R . Silva" <gustavoars@kernel.org>,
	linux-leds@vger.kernel.org, devicetree@vger.kernel.org,
	linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-hardening@vger.kernel.org
Subject: [PATCH net-next v5 5/7] ptp: ocp: Add profile-driven I2C topology support
Date: Fri, 14 Aug 2026 16:10:53 -0700	[thread overview]
Message-ID: <f3570bdde36b104f5d0309f4f6514c76c44a5e92.1786747084.git.ahmadexp@gmail.com> (raw)
In-Reply-To: <cover.1786746757.git.ahmadexp@gmail.com>

Some Time Card variants route board peripherals through a PCA9546 mux,
but their peripheral layout depends on the physical board revision.

Add reusable software-node construction and client population helpers.
Use per-device I2C notifications and serialized delayed work to wait for
the root adapter, instantiate a selected profile, and retry dependencies
that are not ready yet.

Stop the work and remove the mux from a platform-bus unbind notifier
before the controller driver deletes its root adapter. This prevents a
new client from being created after i2c_del_adapter() has swept the
adapter children. Board profiles are added separately.

Signed-off-by: Ahmad Byagowi <ahmadexp@gmail.com>
---
 drivers/ptp/ptp_ocp.c | 718 +++++++++++++++++++++++++++++++++++++++---
 1 file changed, 681 insertions(+), 37 deletions(-)

diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c
index 31a4c9dfe391..444b26a5e2a9 100644
--- a/drivers/ptp/ptp_ocp.c
+++ b/drivers/ptp/ptp_ocp.c
@@ -23,9 +23,12 @@
 #include <linux/mtd/mtd.h>
 #include <linux/mutex.h>
 #include <linux/nvmem-consumer.h>
+#include <linux/property.h>
 #include <linux/crc16.h>
 #include <linux/dpll.h>
 
+#include <dt-bindings/leds/common.h>
+
 #define PCI_DEVICE_ID_META_TIMECARD		0x0400
 
 #define PCI_VENDOR_ID_CELESTICA			0x18d4
@@ -352,6 +355,70 @@ struct ptp_ocp_serial_port {
 #define OCP_SIGNAL_NUM			4
 #define OCP_FREQ_NUM			4
 
+#define OCP_I2C_MUX_CHANNELS		4
+#define OCP_I2C_MAX_SENSOR_COUNT	5
+#define OCP_I2C_MAX_LED_COUNT		6
+#define OCP_I2C_MAX_LED_COMPONENT_COUNT	(3 * OCP_I2C_MAX_LED_COUNT)
+#define OCP_I2C_MUX_NAME_LEN		32
+#define OCP_I2C_MUX_COMPATIBLE		"nxp,pca9546"
+#define OCP_I2C_MUX_TYPE		"pca9546"
+#define OCP_I2C_MUX_ADDRESS		0x70
+#define OCP_I2C_RETRY_MAX		10
+#define OCP_I2C_RECOVERY_SECS		30
+#define OCP_I2C_MAX_NODE_COUNT		(1 + OCP_I2C_MUX_CHANNELS + \
+					 OCP_I2C_MAX_SENSOR_COUNT + 1 + \
+					 OCP_I2C_MAX_LED_COUNT + \
+					 OCP_I2C_MAX_LED_COMPONENT_COUNT)
+
+struct ptp_ocp_i2c_device {
+	const char *node_name;
+	const char *compatible;
+	const char *type;
+	u8 channel;
+	u8 address;
+};
+
+struct ptp_ocp_led {
+	const char *node_name;
+	const char *function;
+	u8 function_enumerator;
+	bool has_function_enumerator;
+	u8 channel[3];
+};
+
+struct ptp_ocp_i2c_profile {
+	const char *name;
+	const struct ptp_ocp_i2c_device *sensors;
+	unsigned int sensor_count;
+	const struct ptp_ocp_led *leds;
+	unsigned int led_count;
+	const char *led_node_name;
+	u8 led_mux_channel;
+	u8 led_address;
+	u32 led_riset_ohms;
+	u32 led_max_microamp;
+};
+
+struct ptp_ocp_i2c_topology {
+	char mux_name[OCP_I2C_MUX_NAME_LEN];
+	struct software_node mux_node;
+	struct property_entry mux_props[6];
+	struct software_node channel_nodes[OCP_I2C_MUX_CHANNELS];
+	struct property_entry channel_props[OCP_I2C_MUX_CHANNELS][4];
+	struct software_node sensor_nodes[OCP_I2C_MAX_SENSOR_COUNT];
+	struct property_entry sensor_props[OCP_I2C_MAX_SENSOR_COUNT][3];
+	struct software_node led_node;
+	struct property_entry led_props[6];
+	struct software_node led_nodes[OCP_I2C_MAX_LED_COUNT];
+	struct property_entry led_group_props[OCP_I2C_MAX_LED_COUNT][7];
+	struct software_node component_nodes[OCP_I2C_MAX_LED_COMPONENT_COUNT];
+	struct property_entry
+		component_props[OCP_I2C_MAX_LED_COMPONENT_COUNT][4];
+	const struct software_node *node_group[OCP_I2C_MAX_NODE_COUNT + 1];
+	const struct ptp_ocp_i2c_profile *profile;
+	bool registered;
+};
+
 enum {
 	PORT_GNSS,
 	PORT_GNSS2,
@@ -422,6 +489,17 @@ struct ptp_ocp {
 	const struct ocp_sma_op *sma_op;
 	struct dpll_device *dpll;
 	dpll_tracker tracker;
+	const struct ptp_ocp_i2c_profile *i2c_profile;
+	struct ptp_ocp_i2c_topology *i2c_topology;
+	struct mutex i2c_topology_lock; /* Serializes topology updates. */
+	struct delayed_work i2c_work;
+	struct notifier_block i2c_notifier;
+	struct notifier_block i2c_ctrl_notifier;
+	unsigned int i2c_retry_count;
+	bool i2c_root_present;
+	bool i2c_resources_ready;
+	bool i2c_notifier_registered;
+	bool i2c_ctrl_notifier_registered;
 	int signals_nr;
 	int freq_in_nr;
 };
@@ -452,6 +530,10 @@ static int ptp_ocp_signal_from_perout(struct ptp_ocp *bp, int gen,
 				      struct ptp_perout_request *req);
 static int ptp_ocp_signal_enable(void *priv, u32 req, bool enable);
 static int ptp_ocp_sma_store(struct ptp_ocp *bp, const char *buf, int sma_nr);
+static int ptp_ocp_i2c_notifier_call(struct notifier_block *nb,
+				     unsigned long action, void *data);
+static int ptp_ocp_i2c_ctrl_notifier_call(struct notifier_block *nb,
+					  unsigned long action, void *data);
 
 static int ptp_ocp_art_board_init(struct ptp_ocp *bp, struct ocp_resource *r);
 
@@ -1998,7 +2080,7 @@ ptp_ocp_read_eeprom(struct ptp_ocp *bp, unsigned int fields)
 		fields &= ~OCP_EEPROM_SERIAL;
 	if (!fields)
 		goto out;
-	if (!bp->i2c_ctrl || !bp->eeprom_map) {
+	if (!READ_ONCE(bp->i2c_ctrl) || !bp->eeprom_map) {
 		ret = -ENODEV;
 		goto out;
 	}
@@ -2059,6 +2141,465 @@ ptp_ocp_read_eeprom(struct ptp_ocp *bp, unsigned int fields)
 	return ret;
 }
 
+static int
+ptp_ocp_i2c_adapter_match(struct device *dev, const void *data)
+{
+	return !!i2c_verify_adapter(dev);
+}
+
+static struct i2c_adapter *
+ptp_ocp_i2c_root_adapter(struct platform_device *i2c_ctrl)
+{
+	struct i2c_adapter *adapter;
+	struct device *dev;
+
+	dev = device_find_child(&i2c_ctrl->dev, NULL,
+				ptp_ocp_i2c_adapter_match);
+	if (!dev)
+		return NULL;
+
+	adapter = to_i2c_adapter(dev);
+	if (!try_module_get(adapter->owner)) {
+		put_device(dev);
+		return NULL;
+	}
+
+	/* The caller owns the reference returned by device_find_child(). */
+	return adapter;
+}
+
+static bool
+ptp_ocp_i2c_supported(struct ptp_ocp *bp)
+{
+	/* PCI IDs identify FPGA images, not a unique PCB revision. */
+	return (bp->pdev->vendor == PCI_VENDOR_ID_META &&
+		bp->pdev->device == PCI_DEVICE_ID_META_TIMECARD) ||
+	       (bp->pdev->vendor == PCI_VENDOR_ID_CELESTICA &&
+		bp->pdev->device == PCI_DEVICE_ID_CELESTICA_TIMECARD);
+}
+
+static int
+ptp_ocp_i2c_init_nodes(struct ptp_ocp *bp)
+{
+	static const char * const channel_names[] = {
+		"i2c@0", "i2c@1", "i2c@2", "i2c@3",
+	};
+	static const char * const output_names[] = {
+		"led@0", "led@1", "led@2", "led@3", "led@4", "led@5",
+		"led@6", "led@7", "led@8", "led@9", "led@a", "led@b",
+		"led@c", "led@d", "led@e", "led@f", "led@10", "led@11",
+	};
+	static const u32 colors[] = {
+		LED_COLOR_ID_RED, LED_COLOR_ID_GREEN, LED_COLOR_ID_BLUE,
+	};
+	const struct ptp_ocp_i2c_profile *profile = bp->i2c_profile;
+	struct device *dev = &bp->pdev->dev;
+	struct ptp_ocp_i2c_topology *topology;
+	unsigned int component = 0;
+	unsigned int node = 0;
+	unsigned int expected_nodes;
+	unsigned int i, j, prop;
+	int ret;
+
+	if (!profile)
+		return -EINVAL;
+	if (bp->i2c_topology && bp->i2c_topology->registered)
+		return 0;
+	if (bp->i2c_topology) {
+		topology = bp->i2c_topology;
+		goto register_nodes;
+	}
+	if (profile->sensor_count > OCP_I2C_MAX_SENSOR_COUNT ||
+	    profile->led_count > OCP_I2C_MAX_LED_COUNT ||
+	    profile->led_mux_channel >= OCP_I2C_MUX_CHANNELS)
+		return -EINVAL;
+
+	for (i = 0; i < profile->sensor_count; i++)
+		if (profile->sensors[i].channel >= OCP_I2C_MUX_CHANNELS)
+			return -EINVAL;
+
+	for (i = 0; i < profile->led_count; i++)
+		for (j = 0; j < ARRAY_SIZE(profile->leds[i].channel); j++)
+			if (profile->leds[i].channel[j] >=
+			    ARRAY_SIZE(output_names))
+				return -EINVAL;
+
+	topology = devm_kzalloc(dev, sizeof(*topology), GFP_KERNEL);
+	if (!topology)
+		return -ENOMEM;
+
+	topology->profile = profile;
+	snprintf(topology->mux_name, sizeof(topology->mux_name),
+		 "ocp%d-%s-mux", bp->id, profile->name);
+	topology->mux_node.name = topology->mux_name;
+	topology->mux_node.properties = topology->mux_props;
+	topology->mux_props[0] =
+		PROPERTY_ENTRY_STRING("compatible", OCP_I2C_MUX_COMPATIBLE);
+	topology->mux_props[1] =
+		PROPERTY_ENTRY_U32("reg", OCP_I2C_MUX_ADDRESS);
+	topology->mux_props[2] =
+		PROPERTY_ENTRY_BOOL("i2c-mux-idle-disconnect");
+	topology->mux_props[3] = PROPERTY_ENTRY_U32("#address-cells", 1);
+	topology->mux_props[4] = PROPERTY_ENTRY_U32("#size-cells", 0);
+	topology->node_group[node++] = &topology->mux_node;
+
+	for (i = 0; i < OCP_I2C_MUX_CHANNELS; i++) {
+		topology->channel_nodes[i].name = channel_names[i];
+		topology->channel_nodes[i].parent = &topology->mux_node;
+		topology->channel_nodes[i].properties =
+			topology->channel_props[i];
+		topology->channel_props[i][0] = PROPERTY_ENTRY_U32("reg", i);
+		topology->channel_props[i][1] =
+			PROPERTY_ENTRY_U32("#address-cells", 1);
+		topology->channel_props[i][2] =
+			PROPERTY_ENTRY_U32("#size-cells", 0);
+		topology->node_group[node++] = &topology->channel_nodes[i];
+	}
+
+	for (i = 0; i < profile->sensor_count; i++) {
+		const struct ptp_ocp_i2c_device *sensor;
+
+		sensor = &profile->sensors[i];
+		topology->sensor_nodes[i].name = sensor->node_name;
+		topology->sensor_nodes[i].parent =
+			&topology->channel_nodes[sensor->channel];
+		topology->sensor_nodes[i].properties =
+			topology->sensor_props[i];
+		prop = 0;
+		if (sensor->compatible)
+			topology->sensor_props[i][prop++] =
+				PROPERTY_ENTRY_STRING("compatible",
+						      sensor->compatible);
+		topology->sensor_props[i][prop] =
+			PROPERTY_ENTRY_U32("reg", sensor->address);
+		topology->node_group[node++] = &topology->sensor_nodes[i];
+	}
+
+	topology->led_node.name = profile->led_node_name;
+	topology->led_node.parent =
+		&topology->channel_nodes[profile->led_mux_channel];
+	topology->led_node.properties = topology->led_props;
+	topology->led_props[0] =
+		PROPERTY_ENTRY_STRING("compatible", "issi,is32fl3207");
+	topology->led_props[1] =
+		PROPERTY_ENTRY_U32("reg", profile->led_address);
+	topology->led_props[2] =
+		PROPERTY_ENTRY_U32("issi,riset-ohms",
+				   profile->led_riset_ohms);
+	topology->led_props[3] = PROPERTY_ENTRY_U32("#address-cells", 1);
+	topology->led_props[4] = PROPERTY_ENTRY_U32("#size-cells", 0);
+	topology->node_group[node++] = &topology->led_node;
+
+	for (i = 0; i < profile->led_count; i++) {
+		const struct ptp_ocp_led *led = &profile->leds[i];
+		u32 group_reg;
+
+		group_reg = min3(led->channel[0], led->channel[1],
+				 led->channel[2]);
+		topology->led_nodes[i].name = led->node_name;
+		topology->led_nodes[i].parent = &topology->led_node;
+		topology->led_nodes[i].properties =
+			topology->led_group_props[i];
+		prop = 0;
+		topology->led_group_props[i][prop++] =
+			PROPERTY_ENTRY_U32("reg", group_reg);
+		topology->led_group_props[i][prop++] =
+			PROPERTY_ENTRY_U32("color", LED_COLOR_ID_RGB);
+		topology->led_group_props[i][prop++] =
+			PROPERTY_ENTRY_STRING("function", led->function);
+		if (led->has_function_enumerator)
+			topology->led_group_props[i][prop++] =
+				PROPERTY_ENTRY_U32("function-enumerator",
+						   led->function_enumerator);
+		topology->led_group_props[i][prop++] =
+			PROPERTY_ENTRY_U32("#address-cells", 1);
+		topology->led_group_props[i][prop] =
+			PROPERTY_ENTRY_U32("#size-cells", 0);
+		topology->node_group[node++] = &topology->led_nodes[i];
+
+		for (j = 0; j < ARRAY_SIZE(led->channel); j++, component++) {
+			u8 channel = led->channel[j];
+
+			topology->component_nodes[component].name =
+				output_names[channel];
+			topology->component_nodes[component].parent =
+				&topology->led_nodes[i];
+			topology->component_nodes[component].properties =
+				topology->component_props[component];
+			topology->component_props[component][0] =
+				PROPERTY_ENTRY_U32("reg", channel);
+			topology->component_props[component][1] =
+				PROPERTY_ENTRY_U32("color", colors[j]);
+			topology->component_props[component][2] =
+				PROPERTY_ENTRY_U32("led-max-microamp",
+						   profile->led_max_microamp);
+			topology->node_group[node++] =
+				&topology->component_nodes[component];
+		}
+	}
+
+	expected_nodes = 1 + OCP_I2C_MUX_CHANNELS +
+		profile->sensor_count + 1 + profile->led_count +
+		3 * profile->led_count;
+	if (WARN_ON(node != expected_nodes)) {
+		devm_kfree(dev, topology);
+		return -EINVAL;
+	}
+
+	bp->i2c_topology = topology;
+
+register_nodes:
+	ret = software_node_register_node_group(topology->node_group);
+	if (ret)
+		return ret;
+
+	topology->registered = true;
+	return 0;
+}
+
+static int
+ptp_ocp_i2c_add_device(struct i2c_adapter *adapter,
+		       const struct software_node *node,
+		       const char *type, u16 address)
+{
+	struct fwnode_handle *fwnode = software_node_fwnode(node);
+	struct i2c_board_info info = { };
+	struct i2c_client *client;
+
+	client = i2c_find_device_by_fwnode(fwnode);
+	if (client) {
+		put_device(&client->dev);
+		return 0;
+	}
+
+	strscpy(info.type, type, sizeof(info.type));
+	info.addr = address;
+	info.fwnode = fwnode;
+	client = i2c_new_client_device(adapter, &info);
+	if (IS_ERR(client))
+		return PTR_ERR(client);
+
+	return 0;
+}
+
+static int
+ptp_ocp_i2c_populate_channel(struct ptp_ocp *bp, unsigned int channel)
+{
+	struct ptp_ocp_i2c_topology *topology = bp->i2c_topology;
+	const struct ptp_ocp_i2c_profile *profile = topology->profile;
+	const struct software_node *node;
+	struct fwnode_handle *fwnode;
+	struct i2c_adapter *adapter;
+	unsigned int i;
+	int err, ret = 0;
+
+	fwnode = software_node_fwnode(&topology->channel_nodes[channel]);
+	adapter = i2c_get_adapter_by_fwnode(fwnode);
+	if (!adapter)
+		return -EAGAIN;
+
+	for (i = 0; i < profile->sensor_count; i++) {
+		const struct ptp_ocp_i2c_device *sensor;
+
+		sensor = &profile->sensors[i];
+		if (sensor->channel != channel)
+			continue;
+
+		node = &topology->sensor_nodes[i];
+		err = ptp_ocp_i2c_add_device(adapter, node, sensor->type,
+					     sensor->address);
+		if (err && !ret)
+			ret = err;
+	}
+
+	if (channel == profile->led_mux_channel) {
+		err = ptp_ocp_i2c_add_device(adapter, &topology->led_node,
+					     "is32fl3207",
+					     profile->led_address);
+		if (err && !ret)
+			ret = err;
+	}
+
+	i2c_put_adapter(adapter);
+	return ret;
+}
+
+static void
+ptp_ocp_i2c_remove_mux(struct ptp_ocp *bp)
+{
+	struct fwnode_handle *fwnode;
+	struct i2c_client *client;
+
+	if (!bp->i2c_topology || !bp->i2c_topology->registered)
+		return;
+
+	fwnode = software_node_fwnode(&bp->i2c_topology->mux_node);
+	client = i2c_find_device_by_fwnode(fwnode);
+	if (!client)
+		return;
+
+	i2c_unregister_device(client);
+	put_device(&client->dev);
+}
+
+static void
+ptp_ocp_i2c_kick(struct ptp_ocp *bp)
+{
+	if (!ptp_ocp_i2c_supported(bp))
+		return;
+	if (!READ_ONCE(bp->i2c_resources_ready))
+		return;
+	if (!READ_ONCE(bp->i2c_root_present))
+		return;
+
+	mod_delayed_work(system_wq, &bp->i2c_work, 1);
+}
+
+static void
+ptp_ocp_i2c_retry(struct ptp_ocp *bp, int error)
+{
+	unsigned long delay = HZ;
+	unsigned int retries;
+	bool exhausted;
+
+	if (!READ_ONCE(bp->i2c_resources_ready))
+		return;
+	if (!READ_ONCE(bp->i2c_root_present))
+		return;
+
+	exhausted = bp->i2c_retry_count == OCP_I2C_RETRY_MAX - 1;
+	if (bp->i2c_retry_count < OCP_I2C_RETRY_MAX)
+		bp->i2c_retry_count++;
+	retries = bp->i2c_retry_count;
+	if (retries >= OCP_I2C_RETRY_MAX) {
+		if (exhausted)
+			dev_err(&bp->pdev->dev,
+				"I2C topology failed after %d attempts: %pe; "
+				"retrying every %d seconds\n",
+				OCP_I2C_RETRY_MAX, ERR_PTR(error),
+				OCP_I2C_RECOVERY_SECS);
+		delay = OCP_I2C_RECOVERY_SECS * HZ;
+	}
+
+	/* Preserve a faster rerun queued by an I2C bus notification. */
+	queue_delayed_work(system_wq, &bp->i2c_work, delay);
+}
+
+static int
+ptp_ocp_i2c_populate_topology(struct ptp_ocp *bp,
+			      struct platform_device *i2c_ctrl)
+{
+	const struct software_node *node;
+	struct i2c_adapter *adapter;
+	unsigned int channel;
+	int err, ret = 0;
+
+	if (!READ_ONCE(bp->i2c_root_present))
+		return 0;
+	if (!ptp_ocp_i2c_supported(bp) || !bp->i2c_profile)
+		return 0;
+
+	adapter = ptp_ocp_i2c_root_adapter(i2c_ctrl);
+	if (!adapter)
+		return -EAGAIN;
+
+	ret = ptp_ocp_i2c_init_nodes(bp);
+	if (ret)
+		goto out_put_adapter;
+
+	node = &bp->i2c_topology->mux_node;
+	ret = ptp_ocp_i2c_add_device(adapter, node, OCP_I2C_MUX_TYPE,
+				     OCP_I2C_MUX_ADDRESS);
+	if (ret)
+		goto out_put_adapter;
+
+	for (channel = 0; channel < OCP_I2C_MUX_CHANNELS; channel++) {
+		err = ptp_ocp_i2c_populate_channel(bp, channel);
+		if (err && !ret)
+			ret = err;
+	}
+
+out_put_adapter:
+	i2c_put_adapter(adapter);
+	return ret;
+}
+
+static void
+ptp_ocp_i2c_work(struct work_struct *work)
+{
+	struct ptp_ocp *bp = container_of(work, struct ptp_ocp,
+					  i2c_work.work);
+	struct platform_device *i2c_ctrl;
+	struct device *i2c_ctrl_dev;
+	unsigned int retries;
+	int ret = 0;
+
+	/* Pair with resource publication after registration. */
+	if (!smp_load_acquire(&bp->i2c_resources_ready))
+		return;
+	if (!ptp_ocp_i2c_supported(bp))
+		return;
+
+	mutex_lock(&bp->i2c_topology_lock);
+	if (!READ_ONCE(bp->i2c_resources_ready) ||
+	    !READ_ONCE(bp->i2c_root_present)) {
+		mutex_unlock(&bp->i2c_topology_lock);
+		return;
+	}
+
+	i2c_ctrl = READ_ONCE(bp->i2c_ctrl);
+	if (!i2c_ctrl) {
+		ret = -EAGAIN;
+		goto out_unlock;
+	}
+
+	i2c_ctrl_dev = get_device(&i2c_ctrl->dev);
+	ret = ptp_ocp_i2c_populate_topology(bp, i2c_ctrl);
+	put_device(i2c_ctrl_dev);
+
+out_unlock:
+	mutex_unlock(&bp->i2c_topology_lock);
+
+	if (ret) {
+		ptp_ocp_i2c_retry(bp, ret);
+		return;
+	}
+
+	retries = bp->i2c_retry_count;
+	bp->i2c_retry_count = 0;
+	if (retries >= OCP_I2C_RETRY_MAX)
+		dev_info(&bp->pdev->dev, "I2C topology setup recovered\n");
+}
+
+static void
+ptp_ocp_i2c_unregister(struct ptp_ocp *bp)
+{
+	struct ptp_ocp_i2c_topology *topology;
+
+	WRITE_ONCE(bp->i2c_resources_ready, false);
+	WRITE_ONCE(bp->i2c_root_present, false);
+	disable_delayed_work_sync(&bp->i2c_work);
+	mutex_lock(&bp->i2c_topology_lock);
+	topology = bp->i2c_topology;
+	if (!topology) {
+		bp->i2c_profile = NULL;
+		goto out;
+	}
+
+	if (topology->registered) {
+		ptp_ocp_i2c_remove_mux(bp);
+		software_node_unregister_node_group(topology->node_group);
+		topology->registered = false;
+	}
+
+	bp->i2c_topology = NULL;
+	bp->i2c_profile = NULL;
+
+out:
+	bp->i2c_retry_count = 0;
+	mutex_unlock(&bp->i2c_topology_lock);
+}
+
 static struct device *
 ptp_ocp_find_flash(struct ptp_ocp *bp)
 {
@@ -2330,7 +2871,7 @@ ptp_ocp_register_i2c(struct ptp_ocp *bp, struct ocp_resource *r)
 	if (IS_ERR(p))
 		return PTR_ERR(p);
 
-	bp_assign_entry(bp, r, p);
+	WRITE_ONCE(bp->i2c_ctrl, p);
 
 	return 0;
 }
@@ -4786,6 +5327,7 @@ ptp_ocp_device_init(struct ptp_ocp *bp, struct pci_dev *pdev)
 	bp->ptp_info = ptp_ocp_clock_info;
 	spin_lock_init(&bp->lock);
 	mutex_init(&bp->eeprom_lock);
+	mutex_init(&bp->i2c_topology_lock);
 
 	for (i = 0; i < __PORT_COUNT; i++)
 		bp->port[i].line = -1;
@@ -4915,6 +5457,7 @@ ptp_ocp_detach_sysfs(struct ptp_ocp *bp)
 {
 	struct device *dev = &bp->dev;
 
+	sysfs_remove_link(&dev->kobj, "i2c");
 	sysfs_remove_link(&dev->kobj, "ptp");
 	sysfs_remove_link(&dev->kobj, "pps");
 }
@@ -4922,8 +5465,19 @@ ptp_ocp_detach_sysfs(struct ptp_ocp *bp)
 static void
 ptp_ocp_detach(struct ptp_ocp *bp)
 {
+	struct platform_device *i2c_ctrl;
 	int i;
 
+	if (bp->i2c_ctrl_notifier_registered) {
+		bus_unregister_notifier(&platform_bus_type,
+					&bp->i2c_ctrl_notifier);
+		bp->i2c_ctrl_notifier_registered = false;
+	}
+	if (bp->i2c_notifier_registered) {
+		bus_unregister_notifier(&i2c_bus_type, &bp->i2c_notifier);
+		bp->i2c_notifier_registered = false;
+	}
+	ptp_ocp_i2c_unregister(bp);
 	ptp_ocp_debugfs_remove_device(bp);
 	ptp_ocp_detach_sysfs(bp);
 	ptp_ocp_attr_group_del(bp);
@@ -4955,8 +5509,13 @@ ptp_ocp_detach(struct ptp_ocp *bp)
 	for (i = 0; i < __PORT_COUNT; i++)
 		if (bp->port[i].line != -1)
 			serial8250_unregister_port(bp->port[i].line);
-	platform_device_unregister(bp->spi_flash);
-	platform_device_unregister(bp->i2c_ctrl);
+	if (bp->spi_flash)
+		platform_device_unregister(bp->spi_flash);
+	i2c_ctrl = READ_ONCE(bp->i2c_ctrl);
+	if (i2c_ctrl) {
+		WRITE_ONCE(bp->i2c_ctrl, NULL);
+		platform_device_unregister(i2c_ctrl);
+	}
 	if (bp->i2c_clk)
 		clk_hw_unregister_fixed_rate(bp->i2c_clk);
 	if (bp->n_irqs)
@@ -5138,6 +5697,7 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 		goto out_disable;
 
 	INIT_DELAYED_WORK(&bp->sync_work, ptp_ocp_sync_work);
+	INIT_DELAYED_WORK(&bp->i2c_work, ptp_ocp_i2c_work);
 
 	/* compat mode.
 	 * Older FPGA firmware only returns 2 irq's.
@@ -5152,9 +5712,32 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 	bp->n_irqs = err;
 	pci_set_master(pdev);
 
+	bp->i2c_notifier.notifier_call = ptp_ocp_i2c_notifier_call;
+	err = bus_register_notifier(&i2c_bus_type, &bp->i2c_notifier);
+	if (err) {
+		dev_err(&pdev->dev,
+			"failed to register I2C notifier: %d\n", err);
+		goto out;
+	}
+	bp->i2c_notifier_registered = true;
+	bp->i2c_ctrl_notifier.notifier_call =
+		ptp_ocp_i2c_ctrl_notifier_call;
+	err = bus_register_notifier(&platform_bus_type,
+				    &bp->i2c_ctrl_notifier);
+	if (err) {
+		dev_err(&pdev->dev,
+			"failed to register I2C controller notifier: %d\n",
+			err);
+		goto out;
+	}
+	bp->i2c_ctrl_notifier_registered = true;
+
 	err = ptp_ocp_register_resources(bp, id->driver_data);
 	if (err)
 		goto out;
+	/* Publish all board resources before I2C topology work can run. */
+	smp_store_release(&bp->i2c_resources_ready, true);
+	ptp_ocp_i2c_kick(bp);
 
 	bp->ptp = ptp_clock_register(&bp->ptp_info, &pdev->dev);
 	if (IS_ERR(bp->ptp)) {
@@ -5248,46 +5831,115 @@ static struct pci_driver ptp_ocp_driver = {
 	.shutdown	= ptp_ocp_remove,
 };
 
+static bool
+ptp_ocp_i2c_is_child(struct ptp_ocp *bp, struct device *child)
+{
+	struct device *dev = child;
+
+	while (dev) {
+		if (dev == &bp->pdev->dev)
+			return true;
+		dev = dev->parent;
+	}
+
+	return false;
+}
+
+static bool
+ptp_ocp_i2c_is_root(struct ptp_ocp *bp, struct i2c_adapter *adapter)
+{
+	struct platform_device *i2c_ctrl = READ_ONCE(bp->i2c_ctrl);
+	struct device *parent = adapter->dev.parent;
+
+	if (!parent)
+		return false;
+	if (i2c_ctrl && parent == &i2c_ctrl->dev)
+		return true;
+
+	/* The adapter can notify before ptp_ocp_register_i2c() stores it. */
+	return parent->parent == &bp->pdev->dev;
+}
+
+static int
+ptp_ocp_i2c_ctrl_notifier_call(struct notifier_block *nb,
+			       unsigned long action, void *data)
+{
+	struct ptp_ocp *bp = container_of(nb, struct ptp_ocp,
+					  i2c_ctrl_notifier);
+	struct platform_device *i2c_ctrl = READ_ONCE(bp->i2c_ctrl);
+	struct device *dev = data;
+
+	if (action != BUS_NOTIFY_UNBIND_DRIVER || !i2c_ctrl ||
+	    dev != &i2c_ctrl->dev)
+		return 0;
+
+	/*
+	 * Stop topology creation before the controller driver's remove path
+	 * deletes its root adapter. Otherwise a newly-created mux client can
+	 * pin that adapter after i2c_del_adapter() has swept its children.
+	 */
+	WRITE_ONCE(bp->i2c_root_present, false);
+	cancel_delayed_work_sync(&bp->i2c_work);
+	mutex_lock(&bp->i2c_topology_lock);
+	ptp_ocp_i2c_remove_mux(bp);
+	bp->i2c_retry_count = 0;
+	mutex_unlock(&bp->i2c_topology_lock);
+	sysfs_remove_link(&bp->dev.kobj, "i2c");
+
+	return 0;
+}
+
 static int
 ptp_ocp_i2c_notifier_call(struct notifier_block *nb,
 			  unsigned long action, void *data)
 {
-	struct device *dev, *child = data;
+	struct i2c_adapter *adapter;
+	struct i2c_client *client;
+	struct device *child = data;
 	struct ptp_ocp *bp;
-	bool add;
+
+	bp = container_of(nb, struct ptp_ocp, i2c_notifier);
+	if (!ptp_ocp_i2c_is_child(bp, child))
+		return 0;
 
 	switch (action) {
 	case BUS_NOTIFY_ADD_DEVICE:
-	case BUS_NOTIFY_DEL_DEVICE:
-		add = action == BUS_NOTIFY_ADD_DEVICE;
-		break;
-	default:
+		adapter = i2c_verify_adapter(child);
+		if (!adapter)
+			return 0;
+		if (ptp_ocp_i2c_is_root(bp, adapter)) {
+			WRITE_ONCE(bp->i2c_root_present, true);
+			ptp_ocp_symlink(bp, child, "i2c");
+		}
+		ptp_ocp_i2c_kick(bp);
 		return 0;
-	}
 
-	if (!i2c_verify_adapter(child))
+	case BUS_NOTIFY_DEL_DEVICE:
+		adapter = i2c_verify_adapter(child);
+		if (!adapter)
+			return 0;
+		if (ptp_ocp_i2c_is_root(bp, adapter)) {
+			WRITE_ONCE(bp->i2c_root_present, false);
+			sysfs_remove_link(&bp->dev.kobj, "i2c");
+		}
 		return 0;
 
-	dev = child;
-	while ((dev = dev->parent))
-		if (dev->driver && !strcmp(dev->driver->name, KBUILD_MODNAME))
-			goto found;
-	return 0;
+	case BUS_NOTIFY_BOUND_DRIVER:
+		client = i2c_verify_client(child);
+		if (!client || client->addr != 0x50)
+			return 0;
+		if (!ptp_ocp_i2c_is_root(bp, client->adapter))
+			return 0;
+		ptp_ocp_i2c_kick(bp);
+		return 0;
 
-found:
-	bp = dev_get_drvdata(dev);
-	if (add)
-		ptp_ocp_symlink(bp, child, "i2c");
-	else
-		sysfs_remove_link(&bp->dev.kobj, "i2c");
+	default:
+		return 0;
+	}
 
 	return 0;
 }
 
-static struct notifier_block ptp_ocp_i2c_notifier = {
-	.notifier_call = ptp_ocp_i2c_notifier_call,
-};
-
 static int __init
 ptp_ocp_init(void)
 {
@@ -5301,21 +5953,14 @@ ptp_ocp_init(void)
 	if (err)
 		goto out;
 
-	what = "i2c notifier";
-	err = bus_register_notifier(&i2c_bus_type, &ptp_ocp_i2c_notifier);
-	if (err)
-		goto out_notifier;
-
 	what = "ptp_ocp driver";
 	err = pci_register_driver(&ptp_ocp_driver);
 	if (err)
-		goto out_register;
+		goto out_class;
 
 	return 0;
 
-out_register:
-	bus_unregister_notifier(&i2c_bus_type, &ptp_ocp_i2c_notifier);
-out_notifier:
+out_class:
 	class_unregister(&timecard_class);
 out:
 	ptp_ocp_debugfs_fini();
@@ -5326,7 +5971,6 @@ ptp_ocp_init(void)
 static void __exit
 ptp_ocp_fini(void)
 {
-	bus_unregister_notifier(&i2c_bus_type, &ptp_ocp_i2c_notifier);
 	pci_unregister_driver(&ptp_ocp_driver);
 	class_unregister(&timecard_class);
 	ptp_ocp_debugfs_fini();
-- 
2.50.1 (Apple Git-155)


  parent reply	other threads:[~2026-08-14 23:11 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-14 23:10 [PATCH net-next v5 0/7] ptp: ocp: Add R4006 and V9 I2C peripheral support Ahmad Byagowi
2026-08-14 23:10 ` [PATCH net-next v5 1/7] dt-bindings: leds: Add IS32FL3207 controller Ahmad Byagowi
2026-08-17  6:41   ` Krzysztof Kozlowski
2026-08-17 18:06     ` Ahmad Byagowi
2026-08-14 23:10 ` [PATCH net-next v5 2/7] leds: is32fl3207: Add controller driver Ahmad Byagowi
2026-08-14 23:10 ` [PATCH net-next v5 3/7] i2c: mux: Propagate software nodes to channel adapters Ahmad Byagowi
2026-08-14 23:10 ` [PATCH net-next v5 4/7] ptp: ocp: Track EEPROM fields independently Ahmad Byagowi
2026-08-14 23:10 ` Ahmad Byagowi [this message]
2026-08-14 23:10 ` [PATCH net-next v5 6/7] ptp: ocp: Add R4006 I2C peripheral topology Ahmad Byagowi
2026-08-14 23:10 ` [PATCH net-next v5 7/7] ptp: ocp: Add Time Card V9 " Ahmad Byagowi

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=f3570bdde36b104f5d0309f4f6514c76c44a5e92.1786747084.git.ahmadexp@gmail.com \
    --to=ahmadexp@gmail.com \
    --cc=andi.shyti@kernel.org \
    --cc=andrew+netdev@lunn.ch \
    --cc=conor+dt@kernel.org \
    --cc=davem@davemloft.net \
    --cc=devicetree@vger.kernel.org \
    --cc=edumazet@google.com \
    --cc=gustavoars@kernel.org \
    --cc=kees@kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=kuba@kernel.org \
    --cc=lee@kernel.org \
    --cc=linux-hardening@vger.kernel.org \
    --cc=linux-i2c@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-leds@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=pavel@kernel.org \
    --cc=peda@lysator.liu.se \
    --cc=richardcochran@gmail.com \
    --cc=robh@kernel.org \
    --cc=trannamatk@gmail.com \
    --cc=vadim.fedorenko@linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®