ptp: ocp: Add R4006 and V9 I2C peripheral support
Subsystems (10)
v5 cover ptp: ocp: Add R4006 and V9 I2C peripheral support
Add the generic LED and I2C-mux support needed to describe peripherals behind the FPGA I2C controller, then use it to instantiate the R4006 and Time Card V9 board topologies from software nodes.
Patches 1 and 2 add the binding and LED-class driver for the Lumissil IS32FL3207. The driver supports individual and multicolor LEDs, enforces per-output current limits, and keeps the controller shut down until every channel has been configured.
Patch 3 propagates software-node channel descriptions to adapters created by i2c-mux. It preserves an ACPI primary firmware node, makes I2C adapter lookup recognize the attached secondary node, and keeps that node alive through child-client teardown.
Patch 4 tracks the fixed-width board ID and serial number independently. Profile selection can therefore proceed when the board ID is valid even if the separate serial EEPROM is absent.
Patch 5 adds the reusable profile-driven topology and recovery lifecycle. It stops topology work and removes the mux before the root I2C controller unbinds, preventing new child creation after adapter teardown has begun.
Patch 6 adds the R4006 temperature, humidity, pressure, GNSS LED, and four SMA LED groups. The BNO08x channel remains empty because no upstream driver binds that device. Production R4006 IDs may carry a revision suffix.
Patch 7 adds the V9 BME280, BNO055, two GNSS LEDs, and four SMA LEDs. It uses the exact fixed-width ID "TIMECARD-V9" because earlier revisions expose the same detectable I2C devices with different LED routing.
The prerequisite patches touch DT bindings, LED, and I2C code. Reviews or acks from those maintainers are requested so the complete dependency series can be carried through net-next.
Hardware and build validation:
- Exercised the R4006 LED routing on hardware.
- Checked the V9 controller address, 4.7 kohm RISET, sequential RGB routing, and second GNSS LED against the V9 schematic.
- Ran dt_binding_check for issi,is32fl3207.yaml.
- Built i2c-core-base.o, i2c-mux.o, leds-is32fl3207.o, and ptp_ocp.o with W=1 for x86-64 and arm64 using defconfig and allmodconfig.
- Ran strict checkpatch with an 80-column limit on all seven patches.
- Applied the generated mailbox to the stated base and compared the resulting tree with the source branch.
Changes since v4:
- Split EEPROM readiness, generic topology lifecycle, R4006, and V9 into separate patches.
- Track board-ID and serial-number readiness independently and publish each field only after a successful EEPROM read.
- Replace the atomic retry counter with worker-serialized state, bounded logging, and synchronized root-controller teardown.
- Support multicolor groups whose final components use outputs 16 and 17, and document current limits relative to the RISET-derived full scale.
- Serialize multicolor calculation, PWM updates, and controller shutdown.
- Match secondary software nodes while preserving an ACPI primary, and balance software-node ownership on failure and teardown paths.
- Clarify that the BNO08x and LM75B at address 0x4a occupy different mux channels.
- Rebase onto current net-next.
v4:
https://lore.kernel.org/r/[email protected]/
Review of v4 patch 4:
https://lore.kernel.org/r/[email protected]/
Ahmad Byagowi (7):
dt-bindings: leds: Add IS32FL3207 controller
leds: is32fl3207: Add controller driver
i2c: mux: Propagate software nodes to channel adapters
ptp: ocp: Track EEPROM fields independently
ptp: ocp: Add profile-driven I2C topology support
ptp: ocp: Add R4006 I2C peripheral topology
ptp: ocp: Add Time Card V9 I2C peripheral topology
.../bindings/leds/issi,is32fl3207.yaml | 281 +++++ MAINTAINERS | 7 + drivers/i2c/i2c-core-base.c | 24 +- drivers/i2c/i2c-mux.c | 77 +- drivers/leds/rgb/Kconfig | 11 + drivers/leds/rgb/Makefile | 1 + drivers/leds/rgb/leds-is32fl3207.c | 736 ++++++++++++ drivers/ptp/ptp_ocp.c | 1025 +++++++++++++++-- 8 files changed, 2083 insertions(+), 79 deletions(-)
base-commit: e6a5d573d24cd375e09d24f136523cb3cc85c9d3
Signature · 1 line
2.50.1 (Apple Git-155)
PATCH v5 1/7 dt-bindings: leds: Add IS32FL3207 controller
The IS32FL3207 is an 18-channel constant-current LED controller with per-output PWM and current scaling.
Describe I2C addressing, power and shutdown controls, RISET, and individual or multicolor output groupings. Require board current limits for each output and document their relationship to the RISET-derived full scale.
Allow dash-suffixed multicolor group names when a group begins at channel 16 or 17, which cannot be represented by the common schema unit-address form.
--- .../bindings/leds/issi,is32fl3207.yaml | 281 ++++++++++++++++++ MAINTAINERS | 6 + 2 files changed, 287 insertions(+) create mode 100644 Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml
Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml new file +281 -0
@@ -0,0 +1,281 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/leds/issi,is32fl3207.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Lumissil IS32FL3207 18-channel LED controller + +maintainers: + - Ahmad Byagowi <[email protected]> + +description: | + The IS32FL3207 is an I2C LED controller with 18 individually controlled + constant-current outputs. Each output has 8-bit current scaling. PWM + brightness control uses the controller's default 8-bit, 62 kHz mode. + Outputs may be exposed as individual LEDs or grouped as multicolor LEDs. + + The data sheet is available at: + https://www.lumissil.com/assets/pdf/core/IS32FL3207_DS.pdf + +properties: + compatible: + const: issi,is32fl3207 + + reg: + enum: [0x34, 0x35, 0x36, 0x37] + + vcc-supply: + description: Regulator providing power to the VCC pin. + + enable-gpios: + maxItems: 1 + description: GPIO connected to SDB, asserted to enable the controller. + + issi,riset-ohms: + minimum: 2000 + maximum: 76500000 + description: | + Resistance in ohms of the external RISET resistor. The resistor sets the + full-scale output current according to IOUT(MAX) = 76.5 / RISET amperes. + Each LED's led-max-microamp value selects a board-safe peak current no + greater than this full scale, using the per-output scaling register. + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + +patternProperties: + "^led@[0-9a-f]+$": + type: object + $ref: common.yaml# + unevaluatedProperties: false + + properties: + reg: + minimum: 0 + maximum: 17 + description: | + Zero-based LED output number. Values 0 through 17 correspond to + OUT1 through OUT18. + + led-max-microamp: + minimum: 1 + maximum: 38250 + description: | + Board-safe peak current for this output. The value must not exceed + the full-scale current derived from issi,riset-ohms. + + color: + oneOf: + - minimum: 0 + maximum: 7 + - minimum: 10 + maximum: 14 + + max-brightness: + minimum: 1 + maximum: 255 + + default-state: + description: + The driver performs a software reset and clears the PWM state during + initialization, so an existing state cannot be preserved. + enum: ["on", "off"] + + default-brightness: + maximum: 255 + description: | + Brightness used when default-state is "on". This value must not + exceed max-brightness when max-brightness is specified. + + anyOf: + - required: + - function + - required: + - color + + required: + - reg + - led-max-microamp + + "^multi-led(@[0-9a-f]|-[0-9]+)?$": + type: object + $ref: leds-class-multicolor.yaml# + unevaluatedProperties: false + + properties: + reg: + minimum: 0 + maximum: 15 + description: | + Optional zero-based lowest-numbered output used by a group named + multi-led@N. Values 0 through 15 correspond to OUT1 through OUT16. + For a multi-led-N group without reg, the driver derives the lowest + output from its component nodes. This form permits groups beginning + at channels 16 or 17 while following the common multicolor node-name + schema. + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + max-brightness: + minimum: 1 + maximum: 255 + + default-state: + description: + The driver performs a software reset and clears the PWM state during + initialization, so an existing state cannot be preserved. + enum: ["on", "off"] + + default-brightness: + maximum: 255 + description: | + Brightness used when default-state is "on". This value must not + exceed max-brightness when max-brightness is specified. + + patternProperties: + "^led@[0-9a-f]+$": + type: object + $ref: common.yaml# + unevaluatedProperties: false + + properties: + reg: + minimum: 0 + maximum: 17 + description: | + Zero-based LED output number. Values 0 through 17 correspond to + OUT1 through OUT18. + + led-max-microamp: + minimum: 1 + maximum: 38250 + description: | + Board-safe peak current for this output. The value must not + exceed the full-scale current derived from issi,riset-ohms. + + color: + oneOf: + - minimum: 0 + maximum: 7 + - minimum: 10 + maximum: 14 + + max-brightness: false + + default-state: false + + default-brightness: false + + retain-state-shutdown: false + + required: + - reg + - color + - led-max-microamp + + anyOf: + - required: [led@0] + - required: [led@1] + - required: [led@2] + - required: [led@3] + - required: [led@4] + - required: [led@5] + - required: [led@6] + - required: [led@7] + - required: [led@8] + - required: [led@9] + - required: [led@a] + - required: [led@b] + - required: [led@c] + - required: [led@d] + - required: [led@e] + - required: [led@f] + - required: [led@10] + - required: [led@11] + + required: + - "#address-cells" + - "#size-cells" + +required: + - compatible + - reg + - issi,riset-ohms + - "#address-cells" + - "#size-cells" + +additionalProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + #include <dt-bindings/leds/common.h> + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + led-controller@34 { + compatible = "issi,is32fl3207"; + reg = <0x34>; + #address-cells = <1>; + enable-gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>; + #size-cells = <0>; + vcc-supply = <&led_3v3>; + issi,riset-ohms = <4700>; + + multi-led@0 { + reg = <0>; + #address-cells = <1>; + color = <LED_COLOR_ID_RGB>; + function = LED_FUNCTION_STATUS; + #size-cells = <0>; + + led@0 { + reg = <0>; + color = <LED_COLOR_ID_RED>; + led-max-microamp = <8100>; + }; + + led@1 { + reg = <1>; + color = <LED_COLOR_ID_GREEN>; + led-max-microamp = <8100>; + }; + + led@2 { + reg = <2>; + color = <LED_COLOR_ID_BLUE>; + led-max-microamp = <8100>; + }; + }; + + multi-led-1 { + #address-cells = <1>; + color = <LED_COLOR_ID_MULTI>; + function = LED_FUNCTION_INDICATOR; + #size-cells = <0>; + + led@10 { + reg = <16>; + color = <LED_COLOR_ID_AMBER>; + led-max-microamp = <8100>; + }; + + led@11 { + reg = <17>; + color = <LED_COLOR_ID_WHITE>; + led-max-microamp = <8100>; + }; + }; + }; + }; +...
MAINTAINERS +6 -0
@@ -13827,6 +13827,12 @@ S: Maintained F: Documentation/filesystems/isofs.rst F: fs/isofs/ +ISSI IS32FL3207 LED DRIVER +M: Ahmad Byagowi <[email protected]> +L: [email protected] +S: Maintained +F: Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml + IT87 HARDWARE MONITORING DRIVER M: Jean Delvare <[email protected]> L: [email protected]
Signature · 1 line
2.50.1 (Apple Git-155)
[email protected]
AI reviewRe: [PATCH net-next v5 1/7] dt-bindings: leds: Add IS32FL3207 controller
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 8 lines
The IS32FL3207 is an 18-channel constant-current LED controller with per-output PWM and current scaling.
Describe I2C addressing, power and shutdown controls, RISET, and individual or multicolor output groupings. Require board current limits for each output and document their relationship to the RISET-derived full scale.
Quoted 5 lines
Allow dash-suffixed multicolor group names when a group begins at channel 16 or 17, which cannot be represented by the common schema unit-address form.
Signed-off-by Ahmad Byagowi <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
Re: [PATCH net-next v5 1/7] dt-bindings: leds: Add IS32FL3207 controller
In reply to Ahmad ByagowiQuoted 8 lines · Ahmad Byagowi
The IS32FL3207 is an 18-channel constant-current LED controller with per-output PWM and current scaling.
Describe I2C addressing, power and shutdown controls, RISET, and individual or multicolor output groupings. Require board current limits for each output and document their relationship to the RISET-derived full scale.
Quoted 7 lines
Allow dash-suffixed multicolor group names when a group begins at channel 16 or 17, which cannot be represented by the common schema unit-address form.
Signed-off-by Ahmad Byagowi <[email protected]>--- .../bindings/leds/issi,is32fl3207.yaml | 281 ++++++++++++++++++
I do not see any explanation why you dropped review.
We have enough of other patches to review, so if you ask us to do the same work twice, it's only fair if we just skip your patches.
<form letter>
This is a friendly reminder during the review process.
It looks like you received a tag and forgot to add it.
If you do not know the process, here is a short explanation:
Please add Acked-by/Reviewed-by/Tested-by tags when posting new versions of patchset, under or above your Signed-off-by tag, unless patch changed significantly (e.g. new properties added to the DT bindings). Tag is "received", when provided in a message replied to you on the mailing list. Tools like b4 can help here. However, there's no need to repost patches *only* to add the tags. The upstream maintainer will do that for tags received on the version they apply.
Please read:
https://elixir.bootlin.com/linux/v6.12-rc3/source/Documentation/process/submitting-patches.rst#L577
If a tag was not added on purpose, please state in the patch changelog or cover letter why and what changed.
</form letter>
Best regards,
Krzysztof
Re: [PATCH net-next v5 1/7] dt-bindings: leds: Add IS32FL3207 controller
Resending as plain text because the mailing lists rejected the previous HTML copy.
You are right, I should have explained this explicitly. I dropped your Reviewed-by because v5 materially changed the binding: it adds the dash-suffixed multicolor-group form, makes reg optional for that form so groups can begin at outputs 16 or 17, and documents the RISET/current-limit relationship.
I did not intend to ask you to repeat the review without explaining why. I will record this explicitly in the next revision and will not carry the tag unless you renew it. Sorry for the omission.
Quoted 47 lines · Krzysztof Kozlowski
Quoted 8 lines · Ahmad Byagowi
The IS32FL3207 is an 18-channel constant-current LED controller with per-output PWM and current scaling.
Describe I2C addressing, power and shutdown controls, RISET, and individual or multicolor output groupings. Require board current limits for each output and document their relationship to the RISET-derived full scale.
Quoted 7 lines
Allow dash-suffixed multicolor group names when a group begins at channel 16 or 17, which cannot be represented by the common schema unit-address form.
Signed-off-by Ahmad Byagowi <[email protected]>--- .../bindings/leds/issi,is32fl3207.yaml | 281 ++++++++++++++++++I do not see any explanation why you dropped review.
We have enough of other patches to review, so if you ask us to do the same work twice, it's only fair if we just skip your patches.
<form letter>
This is a friendly reminder during the review process.It looks like you received a tag and forgot to add it.
If you do not know the process, here is a short explanation:
Please add Acked-by/Reviewed-by/Tested-by tags when posting new versions of patchset, under or above your Signed-off-by tag, unless patch changed significantly (e.g. new properties added to the DT bindings). Tag is "received", when provided in a message replied to you on the mailing list. Tools like b4 can help here. However, there's no need to repost patches *only* to add the tags. The upstream maintainer will do that for tags received on the version they apply.Please read:
https://elixir.bootlin.com/linux/v6.12-rc3/source/Documentation/process/submitting-patches.rst#L577If a tag was not added on purpose, please state in the patch changelog or cover letter why and what changed.
</form letter>Best regards,
Krzysztof
Signature · 6 lines
73 With best wishes / Mit herzlichsten Grüßen Ahmad Byagowi, Ph.D., Dr. Techn., P.Eng. Phone: +1 (650) 924 6653 Please consider the environment before printing this e-mail.
Jakub Kicinski
maintainerRe: [PATCH net-next v5 1/7] dt-bindings: leds: Add IS32FL3207 controller
In reply to Ahmad ByagowiThis is an AI-generated review of your patch. The human sending this email has considered the AI review valid, or at least plausible.
---
dt-bindings: leds: Add IS32FL3207 controller
This adds a binding for the Lumissil IS32FL3207 18-channel constant-current LED controller, describing I2C addressing, VCC supply, the SDB enable GPIO, the RISET resistor and per-output current limits. Outputs may be described either as individual led@N nodes or grouped into multicolor nodes, where the group node name may use either a unit address or a dash suffix.
Quoted 6 lines
Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml new file +0
-0@@ -0,0 +1,281 @@
[ ... ]
Quoted 6 lines
Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml new file +6
-0+ "^multi-led(@[0-9a-f]|-[0-9]+)?$": + type: object + $ref: leds-class-multicolor.yaml# + unevaluatedProperties: false + + properties:
Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml line 119
Quoted 10 lines
Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml new file +10
-0+ reg: + minimum: 0 + maximum: 15 + description: | + Optional zero-based lowest-numbered output used by a group named + multi-led@N. Values 0 through 15 correspond to OUT1 through OUT16. + For a multi-led-N group without reg, the driver derives the lowest + output from its component nodes. This form permits groups beginning + at channels 16 or 17 while following the common multicolor node-name + schema.
[Severity: Low]
The single pattern here matches multi-led, multi-led@<hex> and multi-led-<dec>, and reg is declared unconditionally optional and unconditionally allowed for all three forms.
Since there is no allOf/if-then keyed on $nodename, can the schema still distinguish the two node-name forms? Two shapes appear to validate that the description says should not exist:
multi-led@5 { /* unit address, but no reg */
#address-cells = <1>;
#size-cells = <0>;
led@0 { ... };
};
multi-led-1 { /* no unit address, but has reg */
reg = <5>;
#address-cells = <1>;
#size-cells = <0>;
led@10 { ... };
};dtc's unit_address_vs_reg check rejects both ("node has a unit name, but no reg or ranges property" and "node has a reg or ranges property, but no unit name"), but that check is off for normal dtb builds:
scripts/Makefile.dtbs:
# Disable noisy checks by default
ifeq ($(findstring 1,$(KBUILD_EXTRA_WARN)),)
DTC_FLAGS += -Wno-unit_address_vs_reg \
...so for a board dts built without W=1 the schema is the only thing left to catch it.
For the first shape there is also nothing that ties the unit address to the channels actually used, because is32fl3207_register_multicolor() only compares reg against the derived first channel when reg is present:
if (has_group_reg && group_reg != first_channel)
return dev_err_probe(...);Would a node named multi-led@5 that in fact drives channels 0..2 be silently accepted, making the unit address meaningless?
Would something like this express the intent instead:
allOf:
- if:
properties:
$nodename:
pattern: '^multi-led@'
then:
required:
- reg
else:
properties:
reg: false
or alternatively splitting the two node-name forms into separate patternProperties entries?
[ ... ]
Signature · 1 line
pw-bot: cr
PATCH v5 2/7 leds: is32fl3207: Add controller driver
In reply to Ahmad ByagowiAdd an I2C driver for the Lumissil IS32FL3207 18-channel LED controller.
Expose individual and multicolor LEDs through the LED class. Use the default 8-bit, 62 kHz PWM mode, derive output current from RISET, and enforce each output current limit with the scaling registers. Serialize multicolor calculation, scaling, PWM, and controller-wide update operations.
Clear retained scaling while SDB holds the outputs disabled. Release SDB and briefly enable normal operation to issue the required software reset, then keep the controller in software shutdown while registering every LED. Enable outputs only after all limits and initial brightness values are programmed.
Handle an optional supply and enable GPIO. Serialize shutdown against pending brightness updates and honor retained shutdown state.
--- MAINTAINERS | 1 + drivers/leds/rgb/Kconfig | 11 + drivers/leds/rgb/Makefile | 1 + drivers/leds/rgb/leds-is32fl3207.c | 736 +++++++++++++++++++++++++++++ 4 files changed, 749 insertions(+) create mode 100644 drivers/leds/rgb/leds-is32fl3207.c
MAINTAINERS +1 -0
@@ -13832,6 +13832,7 @@ M: Ahmad Byagowi <[email protected]> L: [email protected] S: Maintained F: Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml +F: drivers/leds/rgb/leds-is32fl3207.c IT87 HARDWARE MONITORING DRIVER M: Jean Delvare <[email protected]>
drivers/leds/rgb/Kconfig +11 -0
@@ -14,6 +14,17 @@ config LEDS_GROUP_MULTICOLOR To compile this driver as a module, choose M here: the module will be called leds-group-multicolor. +config LEDS_IS32FL3207 + tristate "LED support for ISSI IS32FL3207" + depends on I2C + select REGMAP_I2C + help + Say Y here to include support for the Lumissil IS32FL3207 + 18-channel I2C LED controller. + + To compile this driver as a module, choose M here: the module will + be called leds-is32fl3207. + config LEDS_KTD202X tristate "LED support for KTD202x Chips" depends on I2C
drivers/leds/rgb/Makefile +1 -0
@@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_LEDS_GROUP_MULTICOLOR) += leds-group-multicolor.o +obj-$(CONFIG_LEDS_IS32FL3207) += leds-is32fl3207.o obj-$(CONFIG_LEDS_KTD202X) += leds-ktd202x.o obj-$(CONFIG_LEDS_LP5812) += leds-lp5812.o obj-$(CONFIG_LEDS_LP5860_CORE) += leds-lp5860-core.o
drivers/leds/rgb/leds-is32fl3207.c new file +736 -0
736 lines not on this page · raw message
Signature · 1 line
2.50.1 (Apple Git-155)
[email protected]
AI reviewRe: [PATCH net-next v5 2/7] leds: is32fl3207: Add controller driver
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] `is32fl3207_clear_retained_scaling` fails to latch the zeroed scaling registers, leading to a…
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] is32fl3207_clear_retained_scaling fails to latch the zeroed scaling registers, leading to a brief LED flash during hardware initialization. --
--- Patch [2]: [PATCH net-next v5 2/7] leds: is32fl3207: Add controller driver --- commit 63a2ff2e96d1f984d184b4b7a9733188419e971d
Author: Ahmad Byagowi <[email protected]>
leds: is32fl3207: Add controller driver
Add an I2C driver for the Lumissil IS32FL3207 18-channel LED controller. Individual and multicolor LEDs are exposed through the LED class, with support for brightness and channel scaling.
Quoted 5 lines
drivers/leds/rgb/leds-is32fl3207.c new file +0
-0
[ ... ]
drivers/leds/rgb/leds-is32fl3207.c line 480
Quoted 13 lines
drivers/leds/rgb/leds-is32fl3207.c new file +13
-0+static int is32fl3207_clear_retained_scaling (struct is32fl3207 *chip) +{ +u8 scaling[IS32FL3207_NUM_CHANNELS ] = { }; +int ret; + + ret =regmap_write (chip->regmap,IS32FL3207_REG_CONTROL ,0 ); +if (ret) +return ret; + +return regmap_bulk_write (chip->regmap, +IS32FL3207_REG_SCALING (0 ), scaling, +sizeof (scaling)); +}
[Severity: Medium]
Will this fail to latch the zeroed scaling registers, leading to a brief LED flash during hardware initialization?
The zeroing of the scaling registers is performed in software shutdown without entering normal operation or issuing a PWM_UPDATE, leaving the active hardware registers at their old values.
drivers/leds/rgb/leds-is32fl3207.c line 491
Quoted 11 lines
drivers/leds/rgb/leds-is32fl3207.c new file +11
-0+ +static int is32fl3207_hw_init (struct is32fl3207 *chip) +{ +u8 scaling[IS32FL3207_NUM_CHANNELS ] = { }; +u8 pwm[2 *IS32FL3207_NUM_CHANNELS ] = { }; +int disable_ret; +int ret; + +/* Software reset requires normal operation (SSD = 1). */ + ret =regmap_write (chip->regmap,IS32FL3207_REG_CONTROL , +IS32FL3207_CONTROL_ENABLE );
[Severity: Medium]
When normal operation is enabled here to issue the software reset, will the unlatched active registers drive the LEDs using their retained pre-boot values and cause a flash?
The controller requires normal operation and a write to the PWM_UPDATE register to latch new values into the active output stages. Because the zeroed scaling values remain unlatched in shadow registers, this write to enable normal operation causes the active registers to hold their pre-reboot retained values, briefly illuminating the LEDs until the reset is processed.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
Jakub Kicinski
maintainerRe: [PATCH net-next v5 2/7] leds: is32fl3207: Add controller driver
In reply to Ahmad ByagowiThis is an AI-generated review of your patch. The human sending this email has considered the AI review valid, or at least plausible.
---
leds: is32fl3207: Add controller driver
This adds an I2C driver for the Lumissil IS32FL3207 18-channel LED controller, exposing single and multicolor LEDs via the LED class. Per-output current limits are programmed into the scaling registers based on the RISET value and each LED's led-max-microamp property.
Quoted 6 lines
drivers/leds/rgb/leds-is32fl3207.c new file +0
-0@@ -0,0 +1,736 @@
[ ... ]
Quoted 4 lines
drivers/leds/rgb/leds-is32fl3207.c new file +4
-0+static int is32fl3207_configure_channel (struct is32fl3207 *chip, +struct fwnode_handle *fwnode, +unsigned int *channel) +{
[ ... ]
Quoted 10 lines
drivers/leds/rgb/leds-is32fl3207.c new file +10
-0+ ret =fwnode_property_read_u32 (fwnode,"led-max-microamp" , + &max_microamp); +if (ret) +return dev_err_probe (chip->dev, ret, +"failed to read current limit for channel %u\n" , + reg); + +if (!max_microamp || max_microamp > chip->output_max_microamp) + return dev_err_probe(chip->dev, -EINVAL, + "invalid current limit %u uA for channel %u\n",
drivers/leds/rgb/leds-is32fl3207.c line 248
Quoted 10 lines
drivers/leds/rgb/leds-is32fl3207.c new file +10
-0+ max_microamp, reg); + +/* GCC is fixed at 0xff, so use each output's scaling register. */ + scaling =div_u64 ((u64 )max_microamp *256 *256 , + (u64 )chip->output_max_microamp * +IS32FL3207_GLOBAL_CURRENT_MAX ); +if (!scaling) +return dev_err_probe (chip->dev, -EINVAL , +"current limit %u uA is below channel %u resolution\n" , + max_microamp, reg);
[Severity: Medium]
Can this reject device trees that are valid according to the binding?
Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml declares led-max-microamp with minimum: 1, both for the top-level led@ nodes and for the multi-led component nodes:
led-max-microamp:
minimum: 1
maximum: 38250
description: |
Board-safe peak current for this output. The value must not exceed the full-scale current derived from issi,riset-ohms.
In is32fl3207_configure_channel() the scaling quotient truncates to zero for small currents, and the !scaling case aborts probe with -EINVAL via is32fl3207_probe() -> is32fl3207_register_led() -> is32fl3207_register_single()/is32fl3207_register_multicolor() -> is32fl3207_configure_channel().
With the smallest RISET the binding permits, 2000 ohms, output_max_microamp is 76500000 / 2000 = 38250, so scaling stays zero for any led-max-microamp below 149 uA (38250 * 255 / 65536 = 148.8). With the RISET used in the binding example, 4700 ohms, output_max_microamp is 16276 and the cut-off is 64 uA.
So a device tree that passes dt_binding_check with, say, led-max-microamp = <50> fails to bind, and the actual lower bound is board-dependent and not documented anywhere.
Should the binding describe the RISET-dependent lower bound, or should the driver clamp sub-resolution values to a scaling of 1 instead of failing probe?
Cross-instance finding from sashiko-gemini (abacfa25c43c61e8a044d405172e276c795bb984d2cf45e93b49076c16419dfc):
[Severity: Medium]is32fl3207_clear_retained_scaling fails to latch the zeroed scaling registers, leading to a brief LED flash during hardware initialization.
PATCH v5 3/7 i2c: mux: Propagate software nodes to channel adapters
In reply to Ahmad ByagowiDevice Tree channel nodes are associated with the adapters created by i2c-mux, but equivalent software-node descriptions are not.
Find the software-node child whose reg value matches the channel and attach it to the new adapter. Preserve an ACPI primary node when present and extend I2C firmware-node adapter lookup to match the attached secondary node.
Track attachment ownership so failure cleanup cannot detach a pre-existing node. Keep the owned node attached through child-client removal, then release its reference after adapter deletion.
--- drivers/i2c/i2c-core-base.c | 24 +++++++++--- drivers/i2c/i2c-mux.c | 77 ++++++++++++++++++++++++++++++++++++- 2 files changed, 93 insertions(+), 8 deletions(-)
drivers/i2c/i2c-core-base.c +18 -6
@@ -1876,12 +1876,22 @@ int devm_i2c_add_adapter(struct device *dev, struct i2c_adapter *adapter) }EXPORT_SYMBOL_GPL (devm_i2c_add_adapter); -static int i2c_dev_or_parent_fwnode_match (struct device *dev,const void *data) +static bool i2c_device_match_fwnode (struct device *dev,const void *data) { +struct fwnode_handle *fwnode =dev_fwnode (dev); +if (device_match_fwnode (dev, data)) +return true; + +return !IS_ERR_OR_NULL (fwnode) && fwnode->secondary == data; +} + +static int i2c_dev_or_parent_fwnode_match (struct device *dev,const void *data) +{ +if (i2c_device_match_fwnode (dev, data))return 1 ; -if (dev->parent &&device_match_fwnode (dev->parent, data)) +if (dev->parent &&i2c_device_match_fwnode (dev->parent, data))return 1 ;return 0 ;
@@ -1891,8 +1901,9 @@ static int i2c_dev_or_parent_fwnode_match(struct device *dev, const void *data)* i2c_find_adapter_by_fwnode() - find an i2c_adapter for the fwnode * @fwnode: &struct fwnode_handle corresponding to the &struct i2c_adapter * -* Look up and return the &struct i2c_adapter corresponding to the @fwnode. -* If no adapter can be found, or @fwnode is NULL, this returns NULL. +* Look up and return the &struct i2c_adapter corresponding to the @fwnode, +* including a secondary firmware node. If no adapter can be found, or +* @fwnode is NULL, this returns NULL. * * The user must call put_device(&adapter->dev) once done with the i2c adapter. */
@@ -1922,8 +1933,9 @@ EXPORT_SYMBOL(i2c_find_adapter_by_fwnode);* @fwnode: &struct fwnode_handle corresponding to the &struct i2c_adapter * * Look up and return the &struct i2c_adapter corresponding to the @fwnode, -* and increment the adapter module's use count. If no adapter can be found, -* or @fwnode is NULL, this returns NULL. +* including a secondary firmware node, and increment the adapter module's +* use count. If no adapter can be found, or @fwnode is NULL, this returns +* NULL. * * The user must call i2c_put_adapter(adapter) once done with the i2c adapter. * Note that this is different from i2c_find_adapter_by_node().
drivers/i2c/i2c-mux.c +75 -2
135 lines not on this page · raw message
Signature · 1 line
2.50.1 (Apple Git-155)
[email protected]
AI reviewRe: [PATCH net-next v5 3/7] i2c: mux: Propagate software nodes to channel adapters
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 8 lines
Device Tree channel nodes are associated with the adapters created by i2c-mux, but equivalent software-node descriptions are not.
Find the software-node child whose reg value matches the channel and attach it to the new adapter. Preserve an ACPI primary node when present and extend I2C firmware-node adapter lookup to match the attached secondary node.
Quoted 5 lines
Track attachment ownership so failure cleanup cannot detach a pre-existing node. Keep the owned node attached through child-client removal, then release its reference after adapter deletion.
Signed-off-by Ahmad Byagowi <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
Jakub Kicinski
maintainerRe: [PATCH net-next v5 3/7] i2c: mux: Propagate software nodes to channel adapters
In reply to Ahmad ByagowiThis is an AI-generated review of your patch. The human sending this email has considered the AI review valid, or at least plausible.
---
i2c: mux: Propagate software nodes to channel adapters
This attaches the matching software-node child of the mux device to each i2c-mux channel adapter, tracks whether the mux owns that attachment for cleanup, and extends the I2C firmware-node adapter lookup so it also matches a device's secondary fwnode.
Quoted 8 lines
drivers/i2c/i2c-core-base.c +0
-0@@ -1876,12 +1876,22 @@ int devm_i2c_add_adapter(struct device *dev, struct i2c_adapter *adapter) }EXPORT_SYMBOL_GPL (devm_i2c_add_adapter);
drivers/i2c/i2c-core-base.c int devm_i2c_add_adapter(struct device *dev, struct i2c_adapter *adapter) · line 1887
Quoted 10 lines
drivers/i2c/i2c-core-base.c +7
-1-static int i2c_dev_or_parent_fwnode_match (struct device *dev,const void *data) +static bool i2c_device_match_fwnode (struct device *dev,const void *data) { +struct fwnode_handle *fwnode =dev_fwnode (dev); +if (device_match_fwnode (dev, data)) +return true; + +return !IS_ERR_OR_NULL (fwnode) && fwnode->secondary == data; +}
[Severity: Medium]
Is the new fwnode->secondary dereference safe here? The old code only used device_match_fwnode(), which compares pointers and never dereferences the fwnode.
i2c_dev_or_parent_fwnode_match() runs as a bus_find_device() match callback from i2c_find_adapter_by_fwnode()/i2c_get_adapter_by_fwnode(), so it is invoked for every device on i2c_bus_type, including clients, with no device_lock held and nothing pinning dev->fwnode.
Meanwhile i2c_unregister_device() detaches and frees the software node while the client is still in the bus klist:
drivers/i2c/i2c-core-base.c:i2c_unregister_device() {
...
device_remove_software_node(&client->dev);
device_unregister(&client->dev);
}For a client created with info->swnode and no primary fwnode, dev->fwnode == &swnode->fwnode, and device_remove_software_node() drops the last reference:
drivers/base/swnode.c:device_remove_software_node() {
...
set_secondary_fwnode(dev, NULL);
swnode_put(swnode);
}Can a lookup then observe fwnode = dev_fwnode(dev) before the free and read fwnode->secondary after it? The writers are unsynchronized too:
drivers/base/core.c:set_secondary_fwnode() {
if (fwnode_is_primary(dev->fwnode))
dev->fwnode->secondary = fwnode;
else
dev->fwnode = fwnode;
}There is no lock common to the reader and the writer and no WRITE_ONCE, so this also looks like a plain data race that could make the lookup return an adapter whose software node has already been detached.
Also, does the extra dereference help in that case at all? A software node installed in the primary slot always has secondary == ERR_PTR(-ENODEV), so it can never match through fwnode->secondary; only the window seems new.
Quoted 1 line
drivers/i2c/i2c-core-base.c +0
-0@@ -1891,8 +1901,9 @@ static int i2c_dev_or_parent_fwnode_match(struct device *dev, const void *data)
[ ... ]
Quoted 4 lines
drivers/i2c/i2c-mux.c +0
-0
[ ... ]
Quoted 9 lines
drivers/i2c/i2c-mux.c +2
-0@@ -364,6 +417,8 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc, priv->adap.dev.of_node = child;of_node_put (mux_node); + }else { + channel_node =i2c_mux_get_channel_swnode (muxc, chan_id); }/*
[ ... ]
drivers/i2c/i2c-mux.c int i2c_mux_add_adapter(struct i2c_mux_core *muxc, · line 480
Quoted 9 lines
drivers/i2c/i2c-mux.c +2
-0@@ -408,6 +473,8 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc,return 0 ; err_free_priv: +if (priv->swnode) +device_remove_software_node (&priv->adap.dev);kfree (priv);return ret; }
[Severity: Medium]
This isn't a bug introduced by this patch, but since err_free_priv is being extended here, should it also drop the DT channel node reference?
In the DT branch of i2c_mux_add_adapter(), priv->adap.dev.of_node holds a reference obtained either from the for_each_child_of_node() early break, or from of_node_get(dev_node)/of_node_get(mux_node). When i2c_add_adapter()/i2c_add_numbered_adapter() fails, control reaches err_free_priv, which only does kfree(priv).
That the mux owns that reference is visible in the success-path teardown, which saves np = adap->dev.of_node and puts it after i2c_del_adapter():
drivers/i2c/i2c-mux.c void i2c_mux_del_adapters(struct i2c_mux_core *muxc) · line 506
Quoted 12 lines
drivers/i2c/i2c-mux.c +6
-0@@ -429,7 +496,13 @@ void i2c_mux_del_adapters(struct i2c_mux_core *muxc)sysfs_remove_link (&priv->adap.dev.kobj,"mux_device" ); +/* +* Keep the software node through child removal. The adapter +* device is cleared on deletion, so release the software-node +* attachment reference via the saved handle afterwards. +*/ i2c_del_adapter (adap); +fwnode_handle_put (priv->swnode);of_node_put (np);kfree (priv); }
Neither i2c_register_adapter()'s error labels nor the driver core drops a device's of_node reference when device_add() fails, so does the failure path leak that struct device_node reference? Would adding of_node_put(priv->adap.dev.of_node) at err_free_priv be appropriate while this label is being touched?
PATCH v5 4/7 ptp: ocp: Track EEPROM fields independently
In reply to Ahmad ByagowiThe board ID and serial number can reside in separate EEPROM devices. Requiring both reads to succeed before publishing either field makes a missing serial EEPROM block board profile selection and hides an otherwise valid board ID.
Serialize EEPROM discovery, read each field into temporary storage, and publish its readiness only after a successful read. Let devlink report whichever fields are available and make the serial-number sysfs attribute propagate read failures.
--- drivers/ptp/ptp_ocp.c | 129 ++++++++++++++++++++++++++++++------------ 1 file changed, 93 insertions(+), 36 deletions(-)
drivers/ptp/ptp_ocp.c +93 -36
@@ -21,6 +21,7 @@#include <net/devlink.h> #include <linux/i2c.h> #include <linux/mtd/mtd.h> +#include <linux/mutex.h> #include <linux/nvmem-consumer.h> #include <linux/crc16.h> #include <linux/dpll.h>
@@ -343,6 +344,10 @@ struct ptp_ocp_serial_port {#define OCP_BOARD_ID_LEN 13#define OCP_SERIAL_LEN 6 +#define OCP_EEPROM_BOARD_ID BIT(0) +#define OCP_EEPROM_SERIAL BIT(1) +#define OCP_EEPROM_ALL (OCP_EEPROM_BOARD_ID | \ + OCP_EEPROM_SERIAL)#define OCP_SMA_NUM 4#define OCP_SIGNAL_NUM 4#define OCP_FREQ_NUM 4
@@ -403,7 +408,10 @@ struct ptp_ocp {u16 fw_version;u8 board_id[OCP_BOARD_ID_LEN ];u8 serial[OCP_SERIAL_LEN ]; -bool has_eeprom_data; +struct mutex eeprom_lock;/* Serializes EEPROM discovery. */ +bool has_board_id; +bool has_serial; +bool eeprom_error_reported;u32 pps_req_map;int flash_start;u32 utc_tai_offset;
@@ -1969,21 +1977,50 @@ ptp_ocp_nvmem_device_put(struct nvmem_device **nvmemp) *nvmemp =NULL ; } -static void -ptp_ocp_read_eeprom (struct ptp_ocp *bp) +static int +ptp_ocp_read_eeprom (struct ptp_ocp *bp,unsigned int fields) {const struct ptp_ocp_eeprom_map *map;struct nvmem_device *nvmem; +u8 data[OCP_BOARD_ID_LEN ]; +const char *field_name ="data" ;const void *tag; -int ret; - -if (!bp->i2c_ctrl) -return ; +unsigned int field; +int ret =0 ; tag =NULL ; nvmem =NULL ; +mutex_lock (&bp->eeprom_lock); + fields &=OCP_EEPROM_ALL ; +if (bp->has_board_id) + fields &= ~OCP_EEPROM_BOARD_ID ; +if (bp->has_serial) + fields &= ~OCP_EEPROM_SERIAL ; +if (!fields) + goto out; +if (!bp->i2c_ctrl || !bp->eeprom_map) { + ret = -ENODEV ; + goto out; + }for (map = bp->eeprom_map; map->len; map++) { +if (map->bp_offset == offsetof(struct ptp_ocp , board_id)) { + field =OCP_EEPROM_BOARD_ID ; + field_name ="board ID" ; +if (bp->has_board_id) +continue ; + }else if (map->bp_offset == offsetof(struct ptp_ocp , serial)) { + field =OCP_EEPROM_SERIAL ; + field_name ="serial number" ; +if (bp->has_serial) +continue ; + }else { +continue ; + } + +if (!(fields & field)) +continue ; +if (map->tag != tag) { tag = map->tag;ptp_ocp_nvmem_device_put (&nvmem);
@@ -1992,24 +2029,34 @@ ptp_ocp_read_eeprom(struct ptp_ocp *bp) nvmem =ptp_ocp_nvmem_device_get (bp, tag);if (IS_ERR (nvmem)) { ret =PTR_ERR (nvmem); - goto fail; + goto out; } } - ret =nvmem_device_read (nvmem, map->off, map->len, -BP_MAP_ENTRY_ADDR (bp, map)); -if (ret != map->len) - goto fail; + ret =nvmem_device_read (nvmem, map->off, map->len, data); +if (ret <0 ) + goto out; + +memcpy (BP_MAP_ENTRY_ADDR (bp, map), data, map->len); +if (field ==OCP_EEPROM_BOARD_ID ) { +/* Publish the field before marking it ready. */ +smp_store_release (&bp->has_board_id, true); + }else { +/* Publish the field before marking it ready. */ +smp_store_release (&bp->has_serial, true); + } } - bp->has_eeprom_data = true; + ret =0 ; out: +if (ret && ret != -EPROBE_DEFER && !bp->eeprom_error_reported) { +dev_err (&bp->pdev->dev,"failed to read EEPROM %s: %pe\n" , + field_name,ERR_PTR (ret)); + bp->eeprom_error_reported = true; + }ptp_ocp_nvmem_device_put (&nvmem); -return ; - -fail: -dev_err (&bp->pdev->dev,"could not read eeprom: %d\n" , ret); - goto out; +mutex_unlock (&bp->eeprom_lock); +return ret; } static struct device *
@@ -2156,6 +2203,7 @@ ptp_ocp_devlink_info_get(struct devlink *devlink, struct devlink_info_req *req, struct netlink_ext_ack *extack) {struct ptp_ocp *bp =devlink_priv (devlink); +const char *board_id_key =DEVLINK_INFO_VERSION_GENERIC_BOARD_ID ;const char *fw_image;char buf[32 ];int err;
@@ -2166,24 +2214,24 @@ ptp_ocp_devlink_info_get(struct devlink *devlink, struct devlink_info_req *req,if (err)return err; -if (!bp->has_eeprom_data) { -ptp_ocp_read_eeprom (bp); -if (!bp->has_eeprom_data) -return 0 ; - } +ptp_ocp_read_eeprom (bp,OCP_EEPROM_ALL ); -sprintf (buf,"%pM" , bp->serial); - err =devlink_info_serial_number_put (req, buf); -if (err) -return err; +/* Pairs with field publication in ptp_ocp_read_eeprom(). */ +if (smp_load_acquire (&bp->has_serial)) { +sprintf (buf,"%pM" , bp->serial); + err =devlink_info_serial_number_put (req, buf); +if (err) +return err; + } -snprintf (buf,sizeof (buf),"%.*s" ,OCP_BOARD_ID_LEN , - (const char *)bp->board_id); - err =devlink_info_version_fixed_put (req, -DEVLINK_INFO_VERSION_GENERIC_BOARD_ID , - buf); -if (err) -return err; +/* Pairs with field publication in ptp_ocp_read_eeprom(). */ +if (smp_load_acquire (&bp->has_board_id)) { +snprintf (buf,sizeof (buf),"%.*s" ,OCP_BOARD_ID_LEN , + (const char *)bp->board_id); + err =devlink_info_version_fixed_put (req, board_id_key, buf); +if (err) +return err; + }return 0 ; }
@@ -3757,9 +3805,17 @@ static ssize_t serialnum_show(struct device *dev, struct device_attribute *attr, char *buf) { struct ptp_ocp *bp = dev_get_drvdata(dev); +int ret; -if (!bp->has_eeprom_data) -ptp_ocp_read_eeprom (bp); +/* Pairs with field publication in ptp_ocp_read_eeprom(). */ +if (!smp_load_acquire (&bp->has_serial)) { + ret =ptp_ocp_read_eeprom (bp,OCP_EEPROM_SERIAL ); +if (ret) +return ret; +/* Pairs with field publication in ptp_ocp_read_eeprom(). */ +if (!smp_load_acquire (&bp->has_serial)) +return -ENODATA ; + }return sysfs_emit (buf,"%pM\n" , bp->serial); }
@@ -4729,6 +4785,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);for (i =0 ; i < __PORT_COUNT; i++) bp->port[i].line =-1 ;
Signature · 1 line
2.50.1 (Apple Git-155)
[email protected]
AI reviewRe: [PATCH net-next v5 4/7] ptp: ocp: Track EEPROM fields independently
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [High] Kernel stack memory disclosure to userspace due to uninitialized buffer used with short reads.…
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Kernel stack memory disclosure to userspace due to uninitialized buffer used with short reads.
- [Medium] The EEPROM discovery loop aborts on the first error, contradicting the commit's intent to track fields independently.
- [Medium] Internal -EPROBE_DEFER error code is leaked to userspace via the serialnum_show sysfs attribute. --
--- Patch [4]: [PATCH net-next v5 4/7] ptp: ocp: Track EEPROM fields independently --- commit c7acafdfcb181ccb357d00ccd6cff4bb48838592
Author: Ahmad Byagowi <[email protected]>
ptp: ocp: Track EEPROM fields independently
This patch updates the EEPROM discovery logic to serialize reads and track the availability of the board ID and serial number independently. It aims to ensure that failure to read one field does not prevent the discovery or publication of the other field.
Quoted 4 lines
drivers/ptp/ptp_ocp.c +0
-0
[ ... ]
Quoted 23 lines
drivers/ptp/ptp_ocp.c +6
-6@@ -1969,21 +1977,50 @@ ptp_ocp_nvmem_device_put(struct nvmem_device **nvmemp) *nvmemp =NULL ; } -static void -ptp_ocp_read_eeprom (struct ptp_ocp *bp) +static int +ptp_ocp_read_eeprom (struct ptp_ocp *bp,unsigned int fields) {const struct ptp_ocp_eeprom_map *map;struct nvmem_device *nvmem; +u8 data[OCP_BOARD_ID_LEN ]; +const char *field_name ="data" ;const void *tag; -int ret; - -if (!bp->i2c_ctrl) -return ; +unsigned int field; +int ret =0 ; tag =NULL ; nvmem =NULL ;
[ ... ]
Quoted 1 line
for (map =
bp->eeprom_map;map->len; map++) {
[ ... ]
drivers/ptp/ptp_ocp.c ptp_ocp_read_eeprom(struct ptp_ocp *bp) · line 2032
Quoted 10 lines
drivers/ptp/ptp_ocp.c +4
-1+if (map->tag != tag) { tag = map->tag;ptp_ocp_nvmem_device_put (&nvmem); + } +if (!nvmem) { nvmem =ptp_ocp_nvmem_device_get (bp, tag);if (IS_ERR (nvmem)) { ret =PTR_ERR (nvmem); - goto fail; + goto out;
[Severity: Medium]
Since the commit message states the intent is to avoid hiding an otherwise valid board ID when the serial EEPROM block is missing, does aborting the loop here on the first error contradict that?
If nvmem_device_get() fails for the first entry in bp->eeprom_map, this goto out will skip reading any subsequent entries.
drivers/ptp/ptp_ocp.c ptp_ocp_read_eeprom(struct ptp_ocp *bp) · line 2037
Quoted 9 lines
drivers/ptp/ptp_ocp.c +3
-4} } - ret =nvmem_device_read (nvmem, map->off, map->len, -BP_MAP_ENTRY_ADDR (bp, map)); -if (ret != map->len) - goto fail; + ret =nvmem_device_read (nvmem, map->off, map->len, data); +if (ret <0 ) + goto out;
[Severity: Medium]
Similarly, if nvmem_device_read() fails for an entry, won't this goto out also prevent subsequent fields from being discovered?
drivers/ptp/ptp_ocp.c ptp_ocp_read_eeprom(struct ptp_ocp *bp) · line 2039
Quoted 2 lines
drivers/ptp/ptp_ocp.c +2
-0+ +memcpy (BP_MAP_ENTRY_ADDR (bp, map), data, map->len);
[Severity: High]
Does this allow uninitialized stack memory to be copied to the persistent device structure?
The data buffer is allocated uninitialized on the stack. If nvmem_device_read() returns a short read (where 0 <= ret < map->len), the error check above (ret < 0) will pass. The memcpy will then copy the entire map->len bytes, including uninitialized kernel stack data, which could later be disclosed to userspace via sysfs attributes like serialnum_show or the devlink interface.
Quoted 1 line
drivers/ptp/ptp_ocp.c +1
-0+if (field ==OCP_EEPROM_BOARD_ID ) {
[ ... ]
drivers/ptp/ptp_ocp.c static ssize_t · line 3814
Quoted 13 lines
drivers/ptp/ptp_ocp.c +6
-2@@ -3757,9 +3805,17 @@ static ssize_t serialnum_show(struct device *dev, struct device_attribute *attr, char *buf) { struct ptp_ocp *bp = dev_get_drvdata(dev); +int ret; -if (!bp->has_eeprom_data) -ptp_ocp_read_eeprom (bp); +/* Pairs with field publication in ptp_ocp_read_eeprom(). */ +if (!smp_load_acquire (&bp->has_serial)) { + ret =ptp_ocp_read_eeprom (bp,OCP_EEPROM_SERIAL ); +if (ret) +return ret;
[Severity: Medium]
Can this leak internal kernel error codes to userspace?
If nvmem_device_get() is not yet ready, ptp_ocp_read_eeprom() legitimately returns -EPROBE_DEFER. Returning this directly from the sysfs show handler will expose -EPROBE_DEFER (-517) to userspace applications, breaking the POSIX ABI. Should this be translated to -ENODATA or -EAGAIN?
Quoted 7 lines
drivers/ptp/ptp_ocp.c +4
-0+/* Pairs with field publication in ptp_ocp_read_eeprom(). */ +if (!smp_load_acquire (&bp->has_serial)) +return -ENODATA ; + }return sysfs_emit (buf,"%pM\n" , bp->serial); }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=4
Jakub Kicinski
maintainerRe: [PATCH net-next v5 4/7] ptp: ocp: Track EEPROM fields independently
In reply to Ahmad ByagowiThis is an AI-generated review of your patch. The human sending this email has considered the AI review valid, or at least plausible.
---
ptp: ocp: Track EEPROM fields independently
This replaces the single has_eeprom_data flag with per-field has_board_id and has_serial flags, serializes EEPROM discovery under a new eeprom_lock mutex, and makes ptp_ocp_read_eeprom() take a field mask and return an error. devlink now publishes whichever field is available and serialnum_show() propagates read errors.
No Link: tags in the commit header.
The commit message describes fixing a user-visible defect:
"Requiring both reads to succeed before publishing either field makes a missing serial EEPROM block board profile selection and hides an otherwise valid board ID."
[Severity: Low]
Should this carry a Fixes: tag? The condition described is reachable on shipping hardware: fb_eeprom_map places board_id on the untagged '24c02' client and serial on the "mac" tagged '24mac402' client, so one of the two can be absent while the other works, and the old code gated both ptp_ocp_devlink_info_get() and serialnum_show() on has_eeprom_data. The all-or-nothing reader, the two-entry map with the "mac" tag and the devlink publication all came in with commit 0cfcdd1ebcfe ("ptp: ocp: add nvmem interface for accessing eeprom"), which would be the Fixes: target.
If this is instead meant as preparatory work and not stable material (the patch does change sysfs error semantics), could the changelog say so explicitly?
Quoted 4 lines
drivers/ptp/ptp_ocp.c +0
-0
[ ... ]
Quoted 62 lines
drivers/ptp/ptp_ocp.c +35
-7@@ -1969,21 +1977,50 @@ ptp_ocp_nvmem_device_put(struct nvmem_device **nvmemp) *nvmemp =NULL ; } -static void -ptp_ocp_read_eeprom (struct ptp_ocp *bp) +static int +ptp_ocp_read_eeprom (struct ptp_ocp *bp,unsigned int fields) {const struct ptp_ocp_eeprom_map *map;struct nvmem_device *nvmem; +u8 data[OCP_BOARD_ID_LEN ]; +const char *field_name ="data" ;const void *tag; -int ret; - -if (!bp->i2c_ctrl) -return ; +unsigned int field; +int ret =0 ; tag =NULL ; nvmem =NULL ; +mutex_lock (&bp->eeprom_lock); + fields &=OCP_EEPROM_ALL ; +if (bp->has_board_id) + fields &= ~OCP_EEPROM_BOARD_ID ; +if (bp->has_serial) + fields &= ~OCP_EEPROM_SERIAL ; +if (!fields) + goto out; +if (!bp->i2c_ctrl || !bp->eeprom_map) { + ret = -ENODEV ; + goto out; + }for (map = bp->eeprom_map; map->len; map++) { +if (map->bp_offset == offsetof(struct ptp_ocp , board_id)) { + field =OCP_EEPROM_BOARD_ID ; + field_name ="board ID" ; +if (bp->has_board_id) +continue ; + }else if (map->bp_offset == offsetof(struct ptp_ocp , serial)) { + field =OCP_EEPROM_SERIAL ; + field_name ="serial number" ; +if (bp->has_serial) +continue ; + }else { +continue ; + } + +if (!(fields & field)) +continue ; +if (map->tag != tag) { tag = map->tag;ptp_ocp_nvmem_device_put (&nvmem);@@ -1992,24 +2029,34 @@ ptp_ocp_read_eeprom(struct ptp_ocp *bp) nvmem =ptp_ocp_nvmem_device_get (bp, tag);if (IS_ERR (nvmem)) { ret =PTR_ERR (nvmem); - goto fail;
drivers/ptp/ptp_ocp.c ptp_ocp_read_eeprom(struct ptp_ocp *bp) · line 2037
Quoted 10 lines
drivers/ptp/ptp_ocp.c +4
-4+ goto out; } } - ret =nvmem_device_read (nvmem, map->off, map->len, -BP_MAP_ENTRY_ADDR (bp, map)); -if (ret != map->len) - goto fail; + ret =nvmem_device_read (nvmem, map->off, map->len, data); +if (ret <0 ) + goto out;
[Severity: Medium]
Are the two fields really independent in both directions here? Both failure paths inside the loop, the ptp_ocp_nvmem_device_get() failure and this nvmem_device_read() failure, leave the map walk entirely rather than recording a failure for that one field and moving on to the next requested entry.
fb_eeprom_map lists the board ID first, and it lives on a different I2C client than the serial number:
drivers/ptp/ptp_ocp.c:
static struct ptp_ocp_eeprom_map fb_eeprom_map[] = {
{ EEPROM_ENTRY(0x43, board_id) },
{ EEPROM_ENTRY(0x00, serial), .tag = "mac" },
{ }
};So if the '24c02' board-ID EEPROM is missing or unreadable while the '24mac402' MAC EEPROM is fine, ptp_ocp_devlink_info_get() calls ptp_ocp_read_eeprom(bp, OCP_EEPROM_ALL), the walk bails on the first entry and the serial entry is never attempted. has_serial stays false, so devlink_info_serial_number_put() is skipped on every subsequent request.
Isn't that the mirror image of the case the changelog says it removes, "Let devlink report whichever fields are available"? Would a per-field error that continues to the next map entry be closer to the intent?
The serialnum sysfs path escapes this only because it asks for OCP_EEPROM_SERIAL and skips the board-ID entry.
Quoted 29 lines
drivers/ptp/ptp_ocp.c +17
-6+ +memcpy (BP_MAP_ENTRY_ADDR (bp, map), data, map->len); +if (field ==OCP_EEPROM_BOARD_ID ) { +/* Publish the field before marking it ready. */ +smp_store_release (&bp->has_board_id, true); + }else { +/* Publish the field before marking it ready. */ +smp_store_release (&bp->has_serial, true); + } } - bp->has_eeprom_data = true; + ret =0 ; out: +if (ret && ret != -EPROBE_DEFER && !bp->eeprom_error_reported) { +dev_err (&bp->pdev->dev,"failed to read EEPROM %s: %pe\n" , + field_name,ERR_PTR (ret)); + bp->eeprom_error_reported = true; + }ptp_ocp_nvmem_device_put (&nvmem); -return ; - -fail: -dev_err (&bp->pdev->dev,"could not read eeprom: %d\n" , ret); - goto out; +mutex_unlock (&bp->eeprom_lock); +return ret; }
[ ... ]
Quoted 10 lines
drivers/ptp/ptp_ocp.c +1
-5@@ -2166,24 +2214,24 @@ ptp_ocp_devlink_info_get(struct devlink *devlink, struct devlink_info_req *req,if (err)return err; -if (!bp->has_eeprom_data) { -ptp_ocp_read_eeprom (bp); -if (!bp->has_eeprom_data) -return 0 ; - } +ptp_ocp_read_eeprom (bp,OCP_EEPROM_ALL );
[ ... ]
drivers/ptp/ptp_ocp.c static ssize_t · line 3814
Quoted 13 lines
drivers/ptp/ptp_ocp.c +6
-2@@ -3757,9 +3805,17 @@ static ssize_t serialnum_show(struct device *dev, struct device_attribute *attr, char *buf) { struct ptp_ocp *bp = dev_get_drvdata(dev); +int ret; -if (!bp->has_eeprom_data) -ptp_ocp_read_eeprom (bp); +/* Pairs with field publication in ptp_ocp_read_eeprom(). */ +if (!smp_load_acquire (&bp->has_serial)) { + ret =ptp_ocp_read_eeprom (bp,OCP_EEPROM_SERIAL ); +if (ret) +return ret;
[Severity: Medium]
Can this hand -EPROBE_DEFER to userspace? ptp_ocp_read_eeprom() gets the nvmem device through ptp_ocp_nvmem_device_get() -> nvmem_device_find() -> __nvmem_device_get(), and when bus_find_device() finds no matching nvmem device that returns -EPROBE_DEFER:
drivers/nvmem/core.c:__nvmem_device_get() {
...
if (!nvmem)
return ERR_PTR(-EPROBE_DEFER);
...
}ptp_ocp_read_eeprom() stores that with "ret = PTR_ERR(nvmem); goto out;" and returns it unchanged; the "ret != -EPROBE_DEFER" test at the out: label only suppresses the dev_err(), not the return value. serialnum_show() then returns it directly, so reading /sys/class/timecard/ocpN/serialnum with the serial EEPROM's nvmem device not registered (no 24mac402, at24 not bound yet) gives read(2) errno 517.
That is exactly the "serial EEPROM absent" case this patch targets, and the attribute is world readable. Would translating internal codes to something like -ENODEV, -EAGAIN or the -ENODATA already used below be preferable before crossing the syscall boundary?
Quoted 7 lines
drivers/ptp/ptp_ocp.c +4
-0+/* Pairs with field publication in ptp_ocp_read_eeprom(). */ +if (!smp_load_acquire (&bp->has_serial)) +return -ENODATA ; + }return sysfs_emit (buf,"%pM\n" , bp->serial); }
[ ... ]
PATCH v5 5/7 ptp: ocp: Add profile-driven I2C topology support
In reply to Ahmad ByagowiSome 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.
--- drivers/ptp/ptp_ocp.c | 718 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 681 insertions(+), 37 deletions(-)
drivers/ptp/ptp_ocp.c +681 -37
@@ -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 __exitptp_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 ();
Signature · 1 line
2.50.1 (Apple Git-155)
[email protected]
AI reviewRe: [PATCH net-next v5 5/7] ptp: ocp: Add profile-driven I2C topology support
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 8 lines
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.
Quoted 6 lines
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]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=5
Jakub Kicinski
maintainerRe: [PATCH net-next v5 5/7] ptp: ocp: Add profile-driven I2C topology support
In reply to Ahmad ByagowiThis is an AI-generated review of your patch. The human sending this email has considered the AI review valid, or at least plausible.
---
ptp: ocp: Add profile-driven I2C topology support
This adds software-node based construction of a PCA9546 mux topology plus per-device i2c and platform bus notifiers and a delayed work item that waits for the root adapter and instantiates the selected profile. The platform bus unbind notifier is meant to stop the work before the i2c controller driver deletes its root adapter.
Quoted 4 lines
drivers/ptp/ptp_ocp.c +0
-0
[ ... ]
Quoted 4 lines
drivers/ptp/ptp_ocp.c +0
-0@@ -2059,6 +2141,465 @@ ptp_ocp_read_eeprom(struct ptp_ocp *bp, unsigned int fields)return ret; }
[ ... ]
Quoted 41 lines
drivers/ptp/ptp_ocp.c +41
-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; +}
[ ... ]
drivers/ptp/ptp_ocp.c ptp_ocp_read_eeprom(struct ptp_ocp *bp, unsigned int fields) · line 2456
Quoted 12 lines
drivers/ptp/ptp_ocp.c +12
-0+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 ); +}
[Severity: High]
The commit message says the platform bus unbind notifier "prevents a new client from being created after i2c_del_adapter() has swept the adapter children". Does that barrier cover the channel adapters too?
ptp_ocp_i2c_populate_channel() creates clients on the mux channel adapters, and those adapters are deleted by the mux client's own remove path, which never touches bp->i2c_root_present, bp->i2c_work or bp->i2c_topology_lock:
pca954x_remove()
pca954x_cleanup()
i2c_mux_del_adapters()
i2c_del_adapter() /* channel adapter */
In i2c_del_adapter() the child sweep happens before the unbounded wait:
drivers/i2c/i2c-core-base.c:i2c_del_adapter() {
...
i2c_deregister_clients(adap);
...
init_completion(&adap->dev_released);
device_unregister(&adap->dev);
wait_for_completion(&adap->dev_released);
...
}So if the work is between i2c_get_adapter_by_fwnode() and i2c_new_client_device() while the sweep runs, can the added client keep the reference that i2c_adapter_dev_release() is waiting for, leaving the unbind thread stuck in wait_for_completion() forever?
The i2c bus notifier added here only handles ADD_DEVICE, DEL_DEVICE and BOUND_DRIVER. BUS_NOTIFY_DEL_DEVICE for an adapter is emitted from device_del(), which runs after i2c_deregister_clients(), so can it act as a barrier at all? Would handling BUS_NOTIFY_UNBIND_DRIVER for the mux client (and taking bp->i2c_topology_lock there) close that window?
Two related windows look open as well:
The ADD_DEVICE branch calls ptp_ocp_i2c_kick(bp) from inside device_add() of the root adapter, i.e. from the middle of i2c_register_adapter(). With a one jiffy delay, can the work call i2c_new_client_device() on that adapter before registration finished? If i2c_setup_smbus_alert() then fails, i2c_register_adapter() takes its error path which also does put_device() plus wait_for_completion(&adap->dev_released), so a straggling child would block it the same way.
ptp_ocp_i2c_ctrl_notifier_call() reacts only to BUS_NOTIFY_UNBIND_DRIVER. When the i2c controller's probe fails, the driver core tears the adapter down and emits BUS_NOTIFY_DRIVER_NOT_BOUND instead. Should that action be handled too?
Since ptp_ocp_i2c_retry() re-arms the work every 30 seconds indefinitely on persistent failure, this coincidence window keeps reopening for the lifetime of the device.
[ ... ]
Quoted 7 lines
drivers/ptp/ptp_ocp.c +1
-0@@ -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;
drivers/ptp/ptp_ocp.c ptp_ocp_detach_sysfs(struct ptp_ocp *bp) · line 5480
Quoted 10 lines
drivers/ptp/ptp_ocp.c +10
-0+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);
[Severity: Medium]
Is the ordering here inverted? The platform bus notifier is dropped first, and the work is only quiesced later inside ptp_ocp_i2c_unregister(), which clears i2c_root_present and calls disable_delayed_work_sync().
Between those two points bp->i2c_resources_ready and bp->i2c_root_present are still true and bp->i2c_work is still armed:
ptp_ocp_detach()
bus_unregister_notifier(&platform_bus_type, &bp->i2c_ctrl_notifier); /* preempted here */
...
ptp_ocp_i2c_unregister(bp); /* only now is the work stopped */
A concurrent unbind of the child i2c controller takes device_lock(&i2c_ctrl->dev), not the PCI device lock held by the detach path, so can it reach i2c_del_adapter() in that window without ptp_ocp_i2c_ctrl_notifier_call() ever running, which is the callback the commit message describes as providing the required ordering?
Would calling ptp_ocp_i2c_unregister(bp) before dropping the notifiers give the intended sequence?
[ ... ]
Quoted 28 lines
drivers/ptp/ptp_ocp.c +28
-0+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 ; +}
[ ... ]
PATCH v5 6/7 ptp: ocp: Add R4006 I2C peripheral topology
In reply to Ahmad ByagowiR4006 boards place sensors and an IS32FL3207 LED controller behind a four-channel PCA9546 mux.
Describe the LM75B, SHT3x, and ICP10100 sensors and the five RGB LED groups. The BNO08x at address 0x4a is omitted because it has no upstream driver; the LM75B using the same address is on a different mux channel.
Read the board ID independently of the serial EEPROM and select this profile from the R4006 prefix. Production IDs may append a board revision, while unknown board IDs remain untouched.
--- drivers/ptp/ptp_ocp.c | 97 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-)
drivers/ptp/ptp_ocp.c +96 -1
@@ -578,6 +578,71 @@ static struct ptp_ocp_eeprom_map art_eeprom_map[] = { { } }; +/* +* The BNO08x at 0x4a on channel 3 has no upstream Linux driver, so omit it. +* The LM75B at the same address is a separate device on mux channel 0. +*/ +static const struct ptp_ocp_i2c_device ptp_ocp_r4006_sensors[] = { + {"temperature@48" ,"national,lm75b" ,"lm75b" ,0 ,0x48 }, + {"temperature@49" ,"national,lm75b" ,"lm75b" ,0 ,0x49 }, + {"temperature@4a" ,"national,lm75b" ,"lm75b" ,0 ,0x4a }, + {"humidity@44" ,NULL ,"sht3x" ,1 ,0x44 }, + {"pressure@63" ,"invensense,icp10100" ,"icp10100" ,2 ,0x63 }, +}; + +static const struct ptp_ocp_led ptp_ocp_r4006_leds[] = { + { + .node_name ="multi-led@c" , + .function =LED_FUNCTION_STATUS , + .channel = {13 ,12 ,14 }, + }, + { + .node_name ="multi-led@6" , + .function =LED_FUNCTION_INDICATOR , + .function_enumerator =1 , + .has_function_enumerator = true, + .channel = {7 ,6 ,8 }, + }, + { + .node_name ="multi-led@9" , + .function =LED_FUNCTION_INDICATOR , + .function_enumerator =2 , + .has_function_enumerator = true, + .channel = {10 ,9 ,11 }, + }, + { + .node_name ="multi-led@0" , + .function =LED_FUNCTION_INDICATOR , + .function_enumerator =3 , + .has_function_enumerator = true, + .channel = {1 ,0 ,2 }, + }, + { + .node_name ="multi-led@3" , + .function =LED_FUNCTION_INDICATOR , + .function_enumerator =4 , + .has_function_enumerator = true, + .channel = {4 ,3 ,5 }, + }, +}; + +static_assert (ARRAY_SIZE (ptp_ocp_r4006_sensors) <= +OCP_I2C_MAX_SENSOR_COUNT ); +static_assert (ARRAY_SIZE (ptp_ocp_r4006_leds) <=OCP_I2C_MAX_LED_COUNT ); + +static const struct ptp_ocp_i2c_profile ptp_ocp_r4006_profile = { + .name ="r4006" , + .sensors = ptp_ocp_r4006_sensors, + .sensor_count =ARRAY_SIZE (ptp_ocp_r4006_sensors), + .leds = ptp_ocp_r4006_leds, + .led_count =ARRAY_SIZE (ptp_ocp_r4006_leds), + .led_node_name ="led-controller@34" , + .led_mux_channel =1 , + .led_address =0x34 , + .led_riset_ohms =4700 , + .led_max_microamp =8150 , +}; +#define bp_assign_entry (bp, res, val) ({ \ uintptr_t addr = (uintptr_t)(bp) + (res)->bp_offset; \ *(typeof(val) *)addr = val; \
@@ -2178,6 +2243,20 @@ ptp_ocp_i2c_supported(struct ptp_ocp *bp) bp->pdev->device == PCI_DEVICE_ID_CELESTICA_TIMECARD); } +static const struct ptp_ocp_i2c_profile * +ptp_ocp_i2c_select_profile (struct ptp_ocp *bp) +{ +/* Pairs with field publication in ptp_ocp_read_eeprom(). */ +if (!smp_load_acquire (&bp->has_board_id)) +return NULL ; + +/* Production R4006 board IDs may carry a revision suffix. */ +if (!memcmp (bp->board_id,"R4006" ,5 )) +return &ptp_ocp_r4006_profile; + +return NULL ; +} +static int ptp_ocp_i2c_init_nodes (struct ptp_ocp *bp) {
@@ -2496,13 +2575,29 @@ ptp_ocp_i2c_populate_topology(struct ptp_ocp *bp,if (!READ_ONCE (bp->i2c_root_present))return 0 ; -if (!ptp_ocp_i2c_supported (bp) || !bp->i2c_profile) +if (!ptp_ocp_i2c_supported (bp) || !bp->eeprom_map)return 0 ; adapter =ptp_ocp_i2c_root_adapter (i2c_ctrl);if (!adapter)return -EAGAIN ; +/* Pairs with field publication in ptp_ocp_read_eeprom(). */ +if (!smp_load_acquire (&bp->has_board_id)) { + ret =ptp_ocp_read_eeprom (bp,OCP_EEPROM_BOARD_ID ); +if (ret) + goto out_put_adapter; + } +/* Pairs with field publication in ptp_ocp_read_eeprom(). */ +if (!smp_load_acquire (&bp->has_board_id)) { + ret = -EAGAIN ; + goto out_put_adapter; + } +if (!bp->i2c_profile) + bp->i2c_profile =ptp_ocp_i2c_select_profile (bp); +if (!bp->i2c_profile) + goto out_put_adapter; + ret =ptp_ocp_i2c_init_nodes (bp);if (ret) goto out_put_adapter;
Signature · 1 line
2.50.1 (Apple Git-155)
[email protected]
AI reviewRe: [PATCH net-next v5 6/7] ptp: ocp: Add R4006 I2C peripheral topology
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 7 lines
R4006 boards place sensors and an IS32FL3207 LED controller behind a four-channel PCA9546 mux.
Describe the LM75B, SHT3x, and ICP10100 sensors and the five RGB LED groups. The BNO08x at address
0x4ais omitted because it has no upstream driver; the LM75B using the same address is on a different mux channel.
Quoted 5 lines
Read the board ID independently of the serial EEPROM and select this profile from the R4006 prefix. Production IDs may append a board revision, while unknown board IDs remain untouched.
Signed-off-by Ahmad Byagowi <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=6
Jakub Kicinski
maintainerRe: [PATCH net-next v5 6/7] ptp: ocp: Add R4006 I2C peripheral topology
In reply to Ahmad ByagowiThis is an AI-generated review of your patch. The human sending this email has considered the AI review valid, or at least plausible.
---
ptp: ocp: Add R4006 I2C peripheral topology
Adds an I2C profile describing the LM75B/SHT3x/ICP10100 sensors and the five IS32FL3207 RGB LED groups behind the PCA9546 mux on R4006 boards. The board ID is now read independently of the serial EEPROM and used to select the profile.
Quoted 4 lines
drivers/ptp/ptp_ocp.c +0
-0
[ ... ]
Quoted 8 lines
drivers/ptp/ptp_ocp.c +1
-1@@ -2496,13 +2575,29 @@ ptp_ocp_i2c_populate_topology(struct ptp_ocp *bp,if (!READ_ONCE (bp->i2c_root_present))return 0 ; -if (!ptp_ocp_i2c_supported (bp) || !bp->i2c_profile) +if (!ptp_ocp_i2c_supported (bp) || !bp->eeprom_map)return 0 ;
drivers/ptp/ptp_ocp.c ptp_ocp_i2c_populate_topology(struct ptp_ocp *bp, · line 2590
Quoted 10 lines
drivers/ptp/ptp_ocp.c +6
-0adapter =ptp_ocp_i2c_root_adapter (i2c_ctrl);if (!adapter)return -EAGAIN ; +/* Pairs with field publication in ptp_ocp_read_eeprom(). */ +if (!smp_load_acquire (&bp->has_board_id)) { + ret =ptp_ocp_read_eeprom (bp,OCP_EEPROM_BOARD_ID ); +if (ret) + goto out_put_adapter; + }
[Severity: Medium]
Does relaxing this guard from !bp->i2c_profile to !bp->eeprom_map change behaviour on boards that are not R4006?
Before this patch nothing ever assigned bp->i2c_profile, so ptp_ocp_i2c_populate_topology() returned 0 immediately on every board and the topology/retry machinery never ran. Now every device accepted by ptp_ocp_i2c_supported() (the Meta and Celestica TimeCard IDs) passes the guard, because ptp_ocp_fb_board_init() sets:
bp->eeprom_map = fb_eeprom_map;
so all of them reach the new unconditional board-ID read.
What happens when that read fails rather than returning an unknown board ID? ptp_ocp_read_eeprom() returns -EPROBE_DEFER when no nvmem provider is registered, -EOPNOTSUPP with CONFIG_NVMEM=n via the nvmem_device_read() stub, or -ENODEV/-EIO on a bus failure. Note that PTP_1588_CLOCK_OCP selects neither EEPROM_AT24 nor NVMEM, so a build or configuration without at24 is an ordinary case.
The error is propagated through goto out_put_adapter to ptp_ocp_i2c_work(), which hands it to ptp_ocp_i2c_retry():
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);There is no bound on the recovery re-queues, so a board that will never have a profile ends up logging an "I2C topology failed" error and then re-arming bp->i2c_work every 30 seconds for the lifetime of the binding. Would it be preferable to treat a failed board-ID read as "no profile" and return 0, or to keep the early-out tied to a board that actually has a profile?
drivers/ptp/ptp_ocp.c ptp_ocp_i2c_populate_topology(struct ptp_ocp *bp, · line 2603
Quoted 13 lines
drivers/ptp/ptp_ocp.c +10
-0+/* Pairs with field publication in ptp_ocp_read_eeprom(). */ +if (!smp_load_acquire (&bp->has_board_id)) { + ret = -EAGAIN ; + goto out_put_adapter; + } +if (!bp->i2c_profile) + bp->i2c_profile =ptp_ocp_i2c_select_profile (bp); +if (!bp->i2c_profile) + goto out_put_adapter; + ret =ptp_ocp_i2c_init_nodes (bp);if (ret) goto out_put_adapter;
Related to the above, the commit message says:
while unknown board IDs remain untouched.
That holds for board IDs that are read successfully, but could the changelog also mention that the topology and retry work is now entered on every supported PCI ID, and what happens when the board-ID EEPROM cannot be read at all?
PATCH v5 7/7 ptp: ocp: Add Time Card V9 I2C peripheral topology
In reply to Ahmad ByagowiTime Card V9 boards use the same mux infrastructure with a BME280, a BNO055, and an IS32FL3207 at a different address.
Describe six RGB groups. The first two expose the GNSS1 and GNSS2 status LEDs on outputs 0 through 5; the remaining four expose the SMA indicator LEDs on outputs 6 through 17.
Select the profile only when the fixed-width board ID exactly matches TIMECARD-V9 with zero or erased-byte padding. Older revisions share the detectable I2C devices but not the V9 LED wiring.
--- drivers/ptp/ptp_ocp.c | 87 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+)
drivers/ptp/ptp_ocp.c +87 -0
@@ -643,6 +643,73 @@ static const struct ptp_ocp_i2c_profile ptp_ocp_r4006_profile = { .led_max_microamp = 8150, }; +static const struct ptp_ocp_i2c_device ptp_ocp_v9_sensors[] = { + {"pressure@76" ,"bosch,bme280" ,"bme280" ,1 ,0x76 }, + {"imu@29" ,"bosch,bno055" ,"bno055" ,1 ,0x29 }, +}; + +static const struct ptp_ocp_led ptp_ocp_v9_leds[] = { + { + .node_name ="multi-led@0" , + .function =LED_FUNCTION_STATUS , + .function_enumerator =1 , + .has_function_enumerator = true, + .channel = {0 ,1 ,2 }, + }, + { + .node_name ="multi-led@3" , + .function =LED_FUNCTION_STATUS , + .function_enumerator =2 , + .has_function_enumerator = true, + .channel = {3 ,4 ,5 }, + }, + { + .node_name ="multi-led@6" , + .function =LED_FUNCTION_INDICATOR , + .function_enumerator =1 , + .has_function_enumerator = true, + .channel = {6 ,7 ,8 }, + }, + { + .node_name ="multi-led@9" , + .function =LED_FUNCTION_INDICATOR , + .function_enumerator =2 , + .has_function_enumerator = true, + .channel = {9 ,10 ,11 }, + }, + { + .node_name ="multi-led@c" , + .function =LED_FUNCTION_INDICATOR , + .function_enumerator =3 , + .has_function_enumerator = true, + .channel = {12 ,13 ,14 }, + }, + { + .node_name ="multi-led@f" , + .function =LED_FUNCTION_INDICATOR , + .function_enumerator =4 , + .has_function_enumerator = true, + .channel = {15 ,16 ,17 }, + }, +}; + +static_assert (ARRAY_SIZE (ptp_ocp_v9_sensors) <= +OCP_I2C_MAX_SENSOR_COUNT ); +static_assert (ARRAY_SIZE (ptp_ocp_v9_leds) <=OCP_I2C_MAX_LED_COUNT ); + +static const struct ptp_ocp_i2c_profile ptp_ocp_v9_profile = { + .name ="v9" , + .sensors = ptp_ocp_v9_sensors, + .sensor_count =ARRAY_SIZE (ptp_ocp_v9_sensors), + .leds = ptp_ocp_v9_leds, + .led_count =ARRAY_SIZE (ptp_ocp_v9_leds), + .led_node_name ="led-controller@37" , + .led_mux_channel =1 , + .led_address =0x37 , + .led_riset_ohms =4700 , + .led_max_microamp =8100 , +}; +#define bp_assign_entry (bp, res, val) ({ \ uintptr_t addr = (uintptr_t)(bp) + (res)->bp_offset; \ *(typeof(val) *)addr = val; \
@@ -2243,6 +2310,22 @@ ptp_ocp_i2c_supported(struct ptp_ocp *bp) bp->pdev->device == PCI_DEVICE_ID_CELESTICA_TIMECARD); } +static bool +ptp_ocp_board_id_matches (const u8 *board_id,const char *id) +{ +size_t len =strlen (id); +unsigned int i; + +if (len >OCP_BOARD_ID_LEN ||memcmp (board_id, id, len)) +return false; + +for (i = len; i <OCP_BOARD_ID_LEN ; i++) +if (board_id[i] !=0 && board_id[i] !=0xff ) +return false; + +return true; +} +static const struct ptp_ocp_i2c_profile *ptp_ocp_i2c_select_profile (struct ptp_ocp *bp) {
@@ -2254,6 +2337,10 @@ ptp_ocp_i2c_select_profile(struct ptp_ocp *bp)if (!memcmp (bp->board_id,"R4006" ,5 ))return &ptp_ocp_r4006_profile; +/* Older revisions share V9's I2C devices but not its LED wiring. */ +if (ptp_ocp_board_id_matches (bp->board_id,"TIMECARD-V9" )) +return &ptp_ocp_v9_profile; +return NULL ; }
Signature · 1 line
2.50.1 (Apple Git-155)
[email protected]
AI reviewRe: [PATCH net-next v5 7/7] ptp: ocp: Add Time Card V9 I2C peripheral topology
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 7 lines
Time Card V9 boards use the same mux infrastructure with a BME280, a BNO055, and an IS32FL3207 at a different address.
Describe six RGB groups. The first two expose the GNSS1 and GNSS2 status LEDs on outputs 0 through 5; the remaining four expose the SMA indicator LEDs on outputs 6 through 17.
Quoted 5 lines
Select the profile only when the fixed-width board ID exactly matches TIMECARD-V9 with zero or erased-byte padding. Older revisions share the detectable I2C devices but not the V9 LED wiring.
Signed-off-by Ahmad Byagowi <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=7
Jakub Kicinski
maintainerRe: [PATCH net-next v5 7/7] ptp: ocp: Add Time Card V9 I2C peripheral topology
In reply to Ahmad ByagowiThis is an AI-generated review of your patch. The human sending this email has considered the AI review valid, or at least plausible.
---
ptp: ocp: Add Time Card V9 I2C peripheral topology
This adds a new I2C profile describing the Time Card V9 board: a BME280 pressure sensor, a BNO055 IMU, and an IS32FL3207 LED controller at 0x37 with six RGB groups. A new helper, ptp_ocp_board_id_matches(), selects the profile when the fixed-width board ID equals TIMECARD-V9 with 0x00 or 0xff padding.
drivers/ptp/ptp_ocp.c static const struct ptp_ocp_i2c_profile ptp_ocp_r4006_profile = { · line 649
Quoted 12 lines
drivers/ptp/ptp_ocp.c +4
-0@@ -643,6 +643,73 @@ static const struct ptp_ocp_i2c_profile ptp_ocp_r4006_profile = { .led_max_microamp = 8150, }; +static const struct ptp_ocp_i2c_device ptp_ocp_v9_sensors[] = { + {"pressure@76" ,"bosch,bme280" ,"bme280" ,1 ,0x76 }, + {"imu@29" ,"bosch,bno055" ,"bno055" ,1 ,0x29 }, +};
[Severity: Low]
This isn't a bug, but the generated software node for pressure@76 does not describe the supplies that the BME280 binding marks as required.
ptp_ocp_i2c_init_nodes() emits only two properties per sensor:
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);Documentation/devicetree/bindings/iio/pressure/bmp085.yaml lists vddd-supply and vdda-supply in its required list, and bmp280_common_probe() calls devm_regulator_bulk_get() for both.
To be clear, this does not break probing on the hosts this card plugs into:devm_regulator_bulk_get() uses NORMAL_GET, and _regulator_get() substitutes dummy_regulator_rdev whenever have_full_constraints() is true, which is the case on ACPI systems because acpi_early_init() calls regulator_has_full_constraints(). The visible effect is two dev_warn() lines of the form "supply vddd not found, using dummy regulator", plus the description mismatch against the binding. The preceding patch in this series relies on the same behaviour for the R4006 invensense,icp10100 node.
Is relying on the dummy regulator here intentional, and would it be worth saying so in the commit message?
[ ... ]
Quoted 9 lines
drivers/ptp/ptp_ocp.c +5
-0@@ -2243,6 +2310,22 @@ ptp_ocp_i2c_supported(struct ptp_ocp *bp) bp->pdev->device == PCI_DEVICE_ID_CELESTICA_TIMECARD); } +static bool +ptp_ocp_board_id_matches (const u8 *board_id,const char *id) +{ +size_t len =strlen (id); +unsigned int i;
drivers/ptp/ptp_ocp.c ptp_ocp_i2c_supported(struct ptp_ocp *bp) · line 2327
Quoted 10 lines
drivers/ptp/ptp_ocp.c +10
-0+ +if (len >OCP_BOARD_ID_LEN ||memcmp (board_id, id, len)) +return false; + +for (i = len; i <OCP_BOARD_ID_LEN ; i++) +if (board_id[i] !=0 && board_id[i] !=0xff ) +return false; + +return true; +}
[Severity: Low]
This isn't a bug introduced by this patch, but the new helper documents that 0xff padding really does appear in bp->board_id, which makes an existing formatting inconsistency reachable.
ptp_ocp_devlink_info_get() formats the same field for userspace:
snprintf(buf, sizeof(buf), "%.*s", OCP_BOARD_ID_LEN, (const char *)bp->board_id); err = devlink_info_version_fixed_put(req, board_id_key, buf);
The "%.*s" conversion stops early only on a NUL byte, so for exactly the boards targeted here, "TIMECARD-V9" followed by erased-flash 0xff bytes, devlink publishes a DEVLINK_INFO_VERSION_GENERIC_BOARD_ID string with raw 0xff bytes appended. The write is bounded by OCP_BOARD_ID_LEN over a u8[13] array, so this is presentational only, but the two consumers of the field now disagree: one trims 0x00 and 0xff padding, the other forwards it verbatim.
Would it make sense to trim the padding in a shared helper, or in the devlink path, so both call sites agree on the encoding of board_id?
Jakub Kicinski
maintainerQuoted 3 lines · On Fri, 14 Aug 2026 16:10:48 -0700 Ahmad Byagowi wrote:
Add the generic LED and I2C-mux support needed to describe peripherals behind the FPGA I2C controller, then use it to instantiate the R4006 and Time Card V9 board topologies from software nodes.
Ahmad, could you explain why? What are you going to do with these LEDs as a end user?
The influx of the LLM generated patches has a hugely negative impact on the community.
Hi Jakub,
These RGB LEDs have been part of the Time Card hardware since the original design, beside the four SMA connectors and the GNSS inputs. We never had the opportunity to add Linux driver support, so they have remained unused under Linux.
There is recurring confusion about which logical SMA number corresponds to which physical connector and how each connector is configured. The LEDs address a similar operational need to ethtool -p:
they help an operator correlate a logical interface with the physical connector. Their colors can also represent the SMA input/output configuration and timing status, together with GNSS fix or lock status.
This series exposes the LEDs through the standard LED class, avoiding board-specific raw I2C commands. The generic LED controller driver provides the standard interface, while the board-specific status policy remains outside that driver. The second GNSS LED supports the card variant with a second receiver.
I should have stated this existing hardware and end-user use case explicitly in the cover letter. I will add it to the next revision.
Quoted 11 lines · Jakub Kicinski
Quoted 3 lines · On Fri, 14 Aug 2026 16:10:48 -0700 Ahmad Byagowi wrote:
Add the generic LED and I2C-mux support needed to describe peripherals behind the FPGA I2C controller, then use it to instantiate the R4006 and Time Card V9 board topologies from software nodes.
Ahmad, could you explain why? What are you going to do with these LEDs as a end user?
The influx of the LLM generated patches has a hugely negative impact on the community.
Signature · 6 lines
73 With best wishes / Mit herzlichsten Grüßen Ahmad Byagowi, Ph.D., Dr. Techn., P.Eng. Phone: +1 (650) 924 6653 Please consider the environment before printing this e-mail.
Jakub Kicinski
maintainerQuoted 2 lines · On Tue, 18 Aug 2026 11:12:35 -0700 Ahmad Byagowi wrote:
I should have stated this existing hardware and end-user use case explicitly in the cover letter. I will add it to the next revision.
You need to send the leds and i2c patches to relevant trees first. We won't take them via net-next.
Understood. I will split the series and send the IS32FL3207 binding and driver through the LED tree, and the I2C mux changes through the I2C tree. Once those prerequisites land, I will rebase and resend only the ptp_ocp board-profile changes to net-next.
Thanks for the clarification.
Quoted 7 lines · Jakub Kicinski
Quoted 2 lines · On Tue, 18 Aug 2026 11:12:35 -0700 Ahmad Byagowi wrote:
I should have stated this existing hardware and end-user use case explicitly in the cover letter. I will add it to the next revision.
You need to send the leds and i2c patches to relevant trees first. We won't take them via net-next.
--
73
With best wishes / Mit herzlichsten Grüßen
Ahmad Byagowi, Ph.D., Dr. Techn., P.Eng.
Phone: +1 (650) 924 6653
Please consider the environment before printing this e-mail.