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)
next prev 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®