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 <[email protected]> --- 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)

