* [PATCH v3 1/2] dt-bindings: i3c: Add NXP P3H2x4x i3c-hub support
@ 2025-08-08 13:20 Aman Kumar Pandey
2025-08-08 13:20 ` [PATCH v3 2/2] mfd: i3c: Add driver for NXP P3H2x4x i3c-hub device Aman Kumar Pandey
` (3 more replies)
0 siblings, 4 replies; 7+ messages in thread
From: Aman Kumar Pandey @ 2025-08-08 13:20 UTC (permalink / raw)
To: linux-kernel, linux-i3c, alexandre.belloni, krzk+dt, robh,
conor+dt, devicetree, broonie, lee
Cc: vikash.bansal, priyanka.jain, shashank.rebbapragada, Frank.Li,
Aman Kumar Pandey
Add bindings for the NXP P3H2x4x (P3H2440/P3H2441/P3H2840/P3H2841)
multiport I3C hub family. These devices connect to a host via
I3C/I2C/SMBus and allow communication with multiple downstream
peripherals.
Signed-off-by: Aman Kumar Pandey <aman.kumarpandey@nxp.com>
Signed-off-by: Vikash Bansal <vikash.bansal@nxp.com>
---
V1 -> V2: Cleaned up coding style and addressed review comments
V2 -> V3: Added mfd device support for i3c hub and regulator.
---
.../bindings/mfd/nxp,p3h2840-i3c-hub.yaml | 320 ++++++++++++++++++
MAINTAINERS | 8 +
2 files changed, 328 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mfd/nxp,p3h2840-i3c-hub.yaml
diff --git a/Documentation/devicetree/bindings/mfd/nxp,p3h2840-i3c-hub.yaml b/Documentation/devicetree/bindings/mfd/nxp,p3h2840-i3c-hub.yaml
new file mode 100644
index 000000000000..490595c94627
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/nxp,p3h2840-i3c-hub.yaml
@@ -0,0 +1,320 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright 2025 NXP
+
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mfd/nxp,p3h2840-i3c-hub.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NXP P3H2X4X I3C HUB
+
+maintainers:
+ - Aman Kumar Pandey <aman.kumarpandey@nxp.com>
+ - Vikash Bansal <vikash.bansal@nxp.com>
+
+description: |
+ P3H2x4x (P3H2440/P3H2441/P3H2840/P3H2841) is a family of multiport I3C
+ hub devices that connect to:-
+ 1. A host CPU via I3C/I2C/SMBus bus on upstream side and connect to multiple
+ peripheral devices on the downstream side.
+ 2. Have two Controller Ports which can support either
+ I2C/SMBus or I3C buses and connect to a CPU, BMC or SOC.
+ 3. P3H2840/ P3H2841 are 8 port I3C hub with eight I3C/I2C Target Port.
+ 4. P3H2440/ P3H2441 are 4 port I3C hub with four I3C/I2C Target Port.
+ Target ports can be configured as I2C/SMBus, I3C or GPIO and connect to
+ peripherals.
+
+allOf:
+ - $ref: i3c.yaml#
+
+properties:
+ compatible:
+ items:
+ - const: nxp,p3h2840
+
+ cp0-ldo-microvolt:
+ description:
+ Selects the voltage for controller Port 0, in microvolts.
+ enum: [1000000, 1100000, 1200000, 1800000]
+ default: 1000000
+
+ cp1-ldo-microvolt:
+ description:
+ Selects the voltage for controller Port 1, in microvolts.
+ enum: [1000000, 1100000, 1200000, 1800000]
+ default: 1000000
+
+ tp0145-ldo-microvolt:
+ description:
+ Selects the voltage for target Port 0/1/4/5, in microvolts.
+ enum: [1000000, 1100000, 1200000, 1800000]
+ default: 1000000
+
+ tp2367-ldo-microvolt:
+ description:
+ Selects the voltage for target Port 2/3/6/7, in microvolts.
+ enum: [1000000, 1100000, 1200000, 1800000]
+ default: 1000000
+
+ tp0145-pullup-ohms:
+ description:
+ Selects the pull up resistance for target Port 0/1/4/5, in ohms.
+ enum: [250, 500, 1000, 2000]
+ default: 500
+
+ tp2367-pullup-ohms:
+ description:
+ Selects the pull up resistance for target Port 2/3/6/7, in ohms.
+ enum: [250, 500, 1000, 2000]
+ default: 500
+
+ cp0-io-strength-ohms:
+ description:
+ Selects the IO drive strength for controller Port 0, in ohms.
+ enum: [20, 30, 40, 50]
+ default: 20
+
+ cp1-io-strength-ohms:
+ description:
+ Selects the IO drive strength for controller Port 1, in ohms.
+ enum: [20, 30, 40, 50]
+ default: 20
+
+ tp0145-io-strength-ohms:
+ description:
+ Selects the IO drive strength for target port 0/1/4/5, in ohms.
+ enum: [20, 30, 40, 50]
+ default: 20
+
+ tp2367-io-strength-ohms:
+ description:
+ Selects the IO drive strength for target port 2/3/6/7, in ohms.
+ enum: [20, 30, 40, 50]
+ default: 20
+
+ cp0-supply:
+ description: Phandle to voltage regulator providing power to controller port 0.
+
+ cp1-supply:
+ description: Phandle to voltage regulator providing power to controller port 1.
+
+ tp0145-supply:
+ description: Phandle to voltage regulator providing power to target port 0/1/4/5.
+
+ tp2367-supply:
+ description: Phandle to voltage regulator providing power to target port 2/3/6/7.
+
+ regulators:
+ type: object
+ additionalProperties: false
+
+ properties:
+ cp0:
+ type: object
+ $ref: /schemas/regulator/regulator.yaml#
+ unevaluatedProperties: false
+
+ cp1:
+ type: object
+ $ref: /schemas/regulator/regulator.yaml#
+ unevaluatedProperties: false
+
+ tp0145:
+ type: object
+ $ref: /schemas/regulator/regulator.yaml#
+ unevaluatedProperties: false
+
+ tp2367:
+ type: object
+ $ref: /schemas/regulator/regulator.yaml#
+ unevaluatedProperties: false
+
+patternProperties:
+ "@[0-7]$":
+ type: object
+ additionalProperties: false
+ description:
+ I3C HUB Target Port child, should be named as target-port@<target-port-id>
+
+ properties:
+ reg:
+ description:
+ The I3C HUB Target Port number.
+ minimum: 0
+ maximum: 0x07
+
+ mode:
+ enum: [i3c, smbus, i2c, gpio]
+ description:
+ I3C HUB Target Port mode setting to control Target Port functionality.
+ As per now it is only supporting SMBus, i2c and i3c( i2c mode will work
+ with hub network).
+
+ pullup-enable:
+ type: boolean
+ description:
+ Enables the on-die pull-up for Target Port.
+
+ ibi-enable:
+ type: boolean
+ description:
+ Enables the IBI for Target Port.
+
+ local-dev:
+ $ref: /schemas/types.yaml#/definitions/uint8-array
+ description:
+ SMBus Target Agent can discard transactions of downstream device and not generate an IBI to upstream I3C Hub
+ Controller Port. Up to 8 device (addresses) can be configured as local Devices.
+
+ This property is optional. If not provided, local device list will empty.
+
+ hub_bridge_en:
+ type: boolean
+ description:
+ Enables the hub network (Controller port -> target port).
+
+ patternProperties:
+ "@[0-9a-f]+$":
+ type: object
+ additionalProperties: false
+ description: |
+ I2C child, should be named: <device-type>@<i2c-address>
+
+ properties:
+ compatible:
+ description:
+ Compatible of the I2C/SMBus downstream device.
+
+ reg:
+ description: |
+ I2C address, Downstream device address which are connected to target port.
+ minimum: 0
+ maximum: 0x7f
+
+ required:
+ - compatible
+ - reg
+
+ "@[0-9a-f]+,[0-9a-f]+$":
+ type: object
+ additionalProperties: false
+ description: |
+ I3C child, should be named: <device-type>@<static-i2c-address>,<i3c-pid>
+
+ properties:
+ reg:
+ items:
+ - items:
+ - description:
+ Encodes the static I2C address. Should be 0 if the device does
+ not have one (0 is not a valid I2C address).
+ minimum: 0
+ maximum: 0x7f
+ - description: |
+ First half of the Provisioned ID (following the PID
+ definition provided by the I3C specification).
+
+ Contains the manufacturer ID left-shifted by 1.
+ - description: |
+ Second half of the Provisioned ID (following the PID
+ definition provided by the I3C specification).
+
+ Contains the ORing of the part ID left-shifted by 16,
+ the instance ID left-shifted by 12 and extra information.
+
+ assigned-address:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ minimum: 0x1
+ maximum: 0xff
+ description:
+ Dynamic address to be assigned to this device. In case static address is
+ present (first cell of the reg property != 0), this address is assigned
+ through SETDASA. If static address is not present, this address is assigned
+ through SETNEWDA after assigning a temporary address via ENTDAA.
+
+ required:
+ - reg
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ i3c {
+ #address-cells = <3>;
+ #size-cells = <0>;
+
+ hub@70,236153000c2 {
+ reg = <0x70 0x236 0x3000c2>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ assigned-address = <0x50>;
+
+ cp0-ldo-microvolt = <1800000>;
+ cp1-ldo-microvolt = <1800000>;
+ tp0145-ldo-microvolt = <1800000>;
+ tp2367-ldo-microvolt = <1800000>;
+ tp0145-pullup-ohm = <1000>;
+ tp2367-pullup-ohm = <1000>;
+ cp0-io-strength-ohm = <50>;
+ cp1-io-strength-ohm = <50>;
+ tp0145-io-strength-ohm = <50>;
+ tp2367-io-strength-ohm = <50>;
+ cp0-supply = <&cp0>;
+ tp0145-supply = <&tp0145>;
+
+ regulators {
+ cp0 {
+ regulator-name = "cp0";
+ regulator-min-microvolt = <1000000>;
+ regulator-max-microvolt = <1800000>;
+ };
+
+ cp1 {
+ regulator-name = "cp1";
+ regulator-min-microvolt = <1000000>;
+ regulator-max-microvolt = <1800000>;
+ };
+
+ tp0145 {
+ regulator-name = "tp0145";
+ regulator-min-microvolt = <1000000>;
+ regulator-max-microvolt = <1800000>;
+ };
+
+ tp2367 {
+ regulator-name = "tp2367";
+ regulator-min-microvolt = <1000000>;
+ regulator-max-microvolt = <1800000>;
+ };
+ };
+
+ target-port@0 {
+ reg = <0x0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ mode = "smbus";
+ pullup-enable;
+
+ eeprom@57 {
+ compatible = "atmel,24c32";
+ reg = <0x57>;
+ pagesize = <32>;
+ wp-gpios = <&gpio2 2 0>;
+ num-addresses = <8>;
+ };
+ };
+
+ target-port@2 {
+ reg = <0x2>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ mode = "i3c";
+ pullup-enable;
+
+ sensor@68,39200144004 {
+ reg = <0x68 0x392 0x144004>;
+ assigned-address = <0xa>;
+ };
+ };
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 4262e8d833c4..b33e90030188 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15477,6 +15477,14 @@ S: Maintained
F: Documentation/devicetree/bindings/sound/nxp,tfa989x.yaml
F: sound/soc/codecs/tfa989x.c
+NXP P3H2X4X I3C-HUB DRIVER
+M: Vikash Bansal <vikash.bansal@nxp.com>
+M: Aman Kumar Pandey <aman.kumarpandey@nxp.com>
+L: linux-kernel@vger.kernel.org
+L: linux-i3c-owner@lists.infradead.org
+S: Maintained
+F: Documentation/devicetree/bindings/i3c/p3h2x4x_i3c_hub.yaml
+
NZXT-KRAKEN2 HARDWARE MONITORING DRIVER
M: Jonas Malaco <jonas@protocubo.io>
L: linux-hwmon@vger.kernel.org
--
2.25.1
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH v3 2/2] mfd: i3c: Add driver for NXP P3H2x4x i3c-hub device 2025-08-08 13:20 [PATCH v3 1/2] dt-bindings: i3c: Add NXP P3H2x4x i3c-hub support Aman Kumar Pandey @ 2025-08-08 13:20 ` Aman Kumar Pandey 2025-08-08 20:38 ` Frank Li 2025-08-11 7:01 ` Krzysztof Kozlowski 2025-08-11 6:56 ` [PATCH v3 1/2] dt-bindings: i3c: Add NXP P3H2x4x i3c-hub support Krzysztof Kozlowski ` (2 subsequent siblings) 3 siblings, 2 replies; 7+ messages in thread From: Aman Kumar Pandey @ 2025-08-08 13:20 UTC (permalink / raw) To: linux-kernel, linux-i3c, alexandre.belloni, krzk+dt, robh, conor+dt, devicetree, broonie, lee Cc: vikash.bansal, priyanka.jain, shashank.rebbapragada, Frank.Li, Aman Kumar Pandey Add support for the NXP P3H2x4x (P3H2440/P3H2441/P3H2840/P3H2841) family of multiport I3C hub devices. These hubs connect to a host via I3C/I2C/SMBus and provide multiple downstream target ports. This driver adds the core support with separate drivers adding support for I3C hub and regulators. The driver supports: 1. I3C/I2C communication between host and hub 2. Transparent I3C/I2C communication between host and downstream devices 3. Configurable target ports (I2C/SMBus or I3C) 4. MCTP device support 5. In-band interrupt handling 6. On-die regulator configuration P3H2440/P3H2441 support 4 target ports; P3H2840/P3H2841 support 8 target ports. Signed-off-by: Aman Kumar Pandey <aman.kumarpandey@nxp.com> Signed-off-by: Vikash Bansal <vikash.bansal@nxp.com> --- V1 -> V2: Cleaned up coding style and addressed review comments V2 -> V3: Added mfd device support for i3c hub and regulator. --- MAINTAINERS | 1 + drivers/i3c/Kconfig | 1 + drivers/i3c/Makefile | 1 + drivers/i3c/hub/Kconfig | 10 + drivers/i3c/hub/Makefile | 4 + drivers/i3c/hub/p3h2840_i3c_hub.h | 380 +++++++++ drivers/i3c/hub/p3h2840_i3c_hub_common.c | 782 ++++++++++++++++++ drivers/i3c/hub/p3h2840_i3c_hub_i3c.c | 345 ++++++++ drivers/i3c/hub/p3h2840_i3c_hub_smbus.c | 627 ++++++++++++++ drivers/mfd/Kconfig | 12 + drivers/mfd/Makefile | 1 + drivers/mfd/p3h2840.c | 129 +++ drivers/regulator/Kconfig | 10 + drivers/regulator/Makefile | 1 + drivers/regulator/p3h2840_i3c_hub_regulator.c | 163 ++++ include/linux/i3c/device.h | 1 + include/linux/mfd/p3h2840.h | 22 + 17 files changed, 2490 insertions(+) create mode 100644 drivers/i3c/hub/Kconfig create mode 100644 drivers/i3c/hub/Makefile create mode 100644 drivers/i3c/hub/p3h2840_i3c_hub.h create mode 100644 drivers/i3c/hub/p3h2840_i3c_hub_common.c create mode 100644 drivers/i3c/hub/p3h2840_i3c_hub_i3c.c create mode 100644 drivers/i3c/hub/p3h2840_i3c_hub_smbus.c create mode 100644 drivers/mfd/p3h2840.c create mode 100644 drivers/regulator/p3h2840_i3c_hub_regulator.c create mode 100644 include/linux/mfd/p3h2840.h diff --git a/MAINTAINERS b/MAINTAINERS index b33e90030188..220978128696 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15484,6 +15484,7 @@ L: linux-kernel@vger.kernel.org L: linux-i3c-owner@lists.infradead.org S: Maintained F: Documentation/devicetree/bindings/i3c/p3h2x4x_i3c_hub.yaml +F: i3c/hub/* NZXT-KRAKEN2 HARDWARE MONITORING DRIVER M: Jonas Malaco <jonas@protocubo.io> diff --git a/drivers/i3c/Kconfig b/drivers/i3c/Kconfig index 30a441506f61..254c50c3d33a 100644 --- a/drivers/i3c/Kconfig +++ b/drivers/i3c/Kconfig @@ -21,4 +21,5 @@ menuconfig I3C if I3C source "drivers/i3c/master/Kconfig" +source "drivers/i3c/hub/Kconfig" endif # I3C diff --git a/drivers/i3c/Makefile b/drivers/i3c/Makefile index 11982efbc6d9..ea01bdda938d 100644 --- a/drivers/i3c/Makefile +++ b/drivers/i3c/Makefile @@ -2,3 +2,4 @@ i3c-y := device.o master.o obj-$(CONFIG_I3C) += i3c.o obj-$(CONFIG_I3C) += master/ +obj-$(CONFIG_I3C) += hub/ diff --git a/drivers/i3c/hub/Kconfig b/drivers/i3c/hub/Kconfig new file mode 100644 index 000000000000..9d3e1a29e744 --- /dev/null +++ b/drivers/i3c/hub/Kconfig @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright 2025 NXP +config P3H2X4X_I3C_HUB + tristate "P3H2X4X I3C HUB support" + depends on MFD_P3H2X4X + help + This enables support for NXP P3H244x/P3H284x I3C HUB. These hubs + connect to a host via I3C/I2C/SMBus and allow communication with + multiple downstream peripherals. The Say Y or M here to use I3C + HUB driver to configure I3C HUB device. diff --git a/drivers/i3c/hub/Makefile b/drivers/i3c/hub/Makefile new file mode 100644 index 000000000000..9dbd8a7b4184 --- /dev/null +++ b/drivers/i3c/hub/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright 2025 NXP +p3h2840_i3c_hub-y := p3h2840_i3c_hub_common.o p3h2840_i3c_hub_i3c.o p3h2840_i3c_hub_smbus.o +obj-$(CONFIG_P3H2X4X_I3C_HUB) += p3h2840_i3c_hub.o diff --git a/drivers/i3c/hub/p3h2840_i3c_hub.h b/drivers/i3c/hub/p3h2840_i3c_hub.h new file mode 100644 index 000000000000..bcd99004a844 --- /dev/null +++ b/drivers/i3c/hub/p3h2840_i3c_hub.h @@ -0,0 +1,380 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2025 NXP + * This header file contain private device structure definition. + */ + +#ifndef P3H2840_I3C_HUB_H +#define P3H2840_I3C_HUB_H + +#include <linux/i2c.h> +#include <linux/i3c/device.h> +#include <linux/i3c/master.h> +#include <linux/regulator/consumer.h> +#include <linux/regmap.h> + +/* I3C HUB REGISTERS */ + +/* Device Information Registers */ +#define P3H2x4x_DEV_INFO_0 0x00 +#define P3H2x4x_DEV_INFO_1 0x01 +#define P3H2x4x_PID_5 0x02 +#define P3H2x4x_PID_4 0x03 +#define P3H2x4x_PID_3 0x04 +#define P3H2x4x_PID_2 0x05 +#define P3H2x4x_PID_1 0x06 +#define P3H2x4x_PID_0 0x07 +#define P3H2x4x_BCR 0x08 +#define P3H2x4x_DCR 0x09 +#define P3H2x4x_DEV_CAPAB 0x0a +#define P3H2x4x_DEV_REV 0x0b + +/* Device Configuration Registers */ +#define P3H2x4x_DEV_REG_PROTECTION_CODE 0x10 +#define P3H2x4x_REGISTERS_LOCK_CODE 0x00 +#define P3H2x4x_REGISTERS_UNLOCK_CODE 0x69 +#define P3H2x4x_CP1_REGISTERS_UNLOCK_CODE 0x6a + +#define P3H2x4x_CP_CONF 0x11 +#define P3H2x4x_TP_ENABLE 0x12 + +#define P3H2x4x_DEV_CONF 0x13 +#define P3H2x4x_IO_STRENGTH 0x14 +#define P3H2x4x_TP0145_IO_STRENGTH_MASK GENMASK(1, 0) +#define P3H2x4x_TP0145_IO_STRENGTH(x) \ + FIELD_PREP(P3H2x4x_TP0145_IO_STRENGTH_MASK, x) +#define P3H2x4x_TP2367_IO_STRENGTH_MASK GENMASK(3, 2) +#define P3H2x4x_TP2367_IO_STRENGTH(x) \ + FIELD_PREP(P3H2x4x_TP2367_IO_STRENGTH_MASK, x) +#define P3H2x4x_CP0_IO_STRENGTH_MASK GENMASK(5, 4) +#define P3H2x4x_CP0_IO_STRENGTH(x) \ + FIELD_PREP(P3H2x4x_CP0_IO_STRENGTH_MASK, x) +#define P3H2x4x_CP1_IO_STRENGTH_MASK GENMASK(7, 6) +#define P3H2x4x_CP1_IO_STRENGTH(x) \ + FIELD_PREP(P3H2x4x_CP1_IO_STRENGTH_MASK, x) + +#define P3H2x4x_TP_IO_MODE_CONF 0x17 +#define P3H2x4x_TP_SMBUS_AGNT_EN 0x18 + +#define P3H2x4x_LDO_AND_PULLUP_CONF 0x19 + +#define P3H2x4x_TP0145_PULLUP_CONF_MASK GENMASK(7, 6) +#define P3H2x4x_TP0145_PULLUP_CONF(x) \ + FIELD_PREP(P3H2x4x_TP0145_PULLUP_CONF_MASK, x) +#define P3H2x4x_TP2367_PULLUP_CONF_MASK GENMASK(5, 4) +#define P3H2x4x_TP2367_PULLUP_CONF(x) \ + FIELD_PREP(P3H2x4x_TP2367_PULLUP_CONF_MASK, x) + +#define P3H2x4x_CP_IBI_CONF 0x1a + +#define P3H2x4x_TP_SMBUS_AGNT_IBI_CONFIG 0x1b +#define P3H2x4x_ALL_TP_IBI_EN 0xff +#define P3H2x4x_ALL_TP_IBI_DIS 0x00 + +#define P3H2x4x_IBI_MDB_CUSTOM 0x1c +#define P3H2x4x_JEDEC_CONTEXT_ID 0x1d +#define P3H2x4x_TP_GPIO_MODE_EN 0x1e + +/* Device Status and IBI Registers */ +#define P3H2x4x_DEV_AND_IBI_STS 0x20 +#define P3H2x4x_TP_SMBUS_AGNT_IBI_STS 0x21 +#define P3H2x4x_SMBUS_AGENT_EVENT_FLAG_STATUS BIT(4) + +/* Controller Port Control/Status Registers */ +#define P3H2x4x_CP_MUX_SET 0x38 +#define P3H2x4x_CONTROLLER_PORT_MUX_REQ BIT(0) +#define P3H2x4x_CP_MUX_STS 0x39 +#define P3H2x4x_CONTROLLER_PORT_MUX_CONNECTION_STATUS BIT(0) + +/* Target Ports Control Registers */ +#define P3H2x4x_TP_SMBUS_AGNT_TRANS_START 0x50 +#define P3H2x4x_TP_NET_CON_CONF 0x51 + +#define P3H2x4x_TP_PULLUP_EN 0x53 + +#define P3H2x4x_TP_SCL_OUT_EN 0x54 +#define P3H2x4x_TP_SDA_OUT_EN 0x55 +#define P3H2x4x_TP_SCL_OUT_LEVEL 0x56 +#define P3H2x4x_TP_SDA_OUT_LEVEL 0x57 +#define P3H2x4x_TP_IN_DETECT_MODE_CONF 0x58 +#define P3H2x4x_TP_SCL_IN_DETECT_IBI_EN 0x59 +#define P3H2x4x_TP_SDA_IN_DETECT_IBI_EN 0x5a + +/* Target Ports Status Registers */ +#define P3H2x4x_TP_SCL_IN_LEVEL_STS 0x60 +#define P3H2x4x_TP_SDA_IN_LEVEL_STS 0x61 +#define P3H2x4x_TP_SCL_IN_DETECT_FLG 0x62 +#define P3H2x4x_TP_SDA_IN_DETECT_FLG 0x63 + +/* SMBus Agent Configuration and Status Registers */ +#define P3H2x4x_TP0_SMBUS_AGNT_STS 0x64 +#define P3H2x4x_TP1_SMBUS_AGNT_STS 0x65 +#define P3H2x4x_TP2_SMBUS_AGNT_STS 0x66 +#define P3H2x4x_TP3_SMBUS_AGNT_STS 0x67 +#define P3H2x4x_TP4_SMBUS_AGNT_STS 0x68 +#define P3H2x4x_TP5_SMBUS_AGNT_STS 0x69 +#define P3H2x4x_TP6_SMBUS_AGNT_STS 0x6a +#define P3H2x4x_TP7_SMBUS_AGNT_STS 0x6b +#define P3H2x4x_ONCHIP_TD_AND_SMBUS_AGNT_CONF 0x6c + +/* buf receive flag set */ +#define P3H2x4x_TARGET_BUF_CA_TF BIT(0) +#define P3H2x4x_TARGET_BUF_0_RECEIVE BIT(1) +#define P3H2x4x_TARGET_BUF_1_RECEIVE BIT(2) +#define P3H2x4x_TARGET_BUF_0_1_RECEIVE GENMASK(2, 1) +#define P3H2x4x_TARGET_BUF_OVRFL GENMASK(3, 1) +#define BUF_RECEIVED_FLAG_MASK GENMASK(3, 1) +#define BUF_RECEIVED_FLAG_TF_MASK GENMASK(3, 0) + +#define P3H2x4x_TARGET_AGENT_LOCAL_DEV 0x11 +#define P3H2x4x_TARGET_BUFF_0_PAGE 0x12 +#define P3H2x4x_TARGET_BUFF_1_PAGE 0x13 + +/* Special Function Registers */ +#define P3H2x4x_LDO_AND_CPSEL_STS 0x79 +#define P3H2x4x_CP_SDA1_LEVEL BIT(7) +#define P3H2x4x_CP_SCL1_LEVEL BIT(6) + +#define P3H2x4x_CP_SEL_PIN_INPUT_CODE_MASK GENMASK(5, 4) +#define P3H2x4x_CP_SEL_PIN_INPUT_CODE_GET(x) \ + (((x) & P3H2x4x_CP_SEL_PIN_INPUT_CODE_MASK) >> 4) +#define P3H2x4x_CP_SDA1_SCL1_PINS_CODE_MASK GENMASK(7, 6) +#define P3H2x4x_CP_SDA1_SCL1_PINS_CODE_GET(x) \ + (((x) & P3H2x4x_CP_SDA1_SCL1_PINS_CODE_MASK) >> 6) +#define P3H2x4x_VCCIO1_PWR_GOOD BIT(3) +#define P3H2x4x_VCCIO0_PWR_GOOD BIT(2) +#define P3H2x4x_CP1_VCCIO_PWR_GOOD BIT(1) +#define P3H2x4x_CP0_VCCIO_PWR_GOOD BIT(0) + +#define P3H2x4x_BUS_RESET_SCL_TIMEOUT 0x7a +#define P3H2x4x_ONCHIP_TD_PROTO_ERR_FLG 0x7b +#define P3H2x4x_DEV_CMD 0x7c +#define P3H2x4x_ONCHIP_TD_STS 0x7d +#define P3H2x4x_ONCHIP_TD_ADDR_CONF 0x7e +#define P3H2x4x_PAGE_PTR 0x7f + +/* Paged Transaction Registers */ +#define P3H2x4x_CONTROLLER_BUFFER_PAGE 0x10 +#define P3H2x4x_CONTROLLER_AGENT_BUFF 0x80 +#define P3H2x4x_CONTROLLER_AGENT_BUFF_DATA 0x84 + +#define P3H2x4x_TARGET_BUFF_LENGTH 0x80 +#define P3H2x4x_TARGET_BUFF_ADDRESS 0x81 +#define P3H2x4x_TARGET_BUFF_DATA 0x82 + +#define P3H2x4x_TP_MAX_COUNT 0x08 +#define P3H2x4x_CP_MAX_COUNT 0x02 +#define P3H2x4x_TP_LOCAL_DEV 0x08 + +/* LDO Disable/Enable DT settings */ +#define P3H2x4x_LDO_VOLT_1_0V 0x00 +#define P3H2x4x_LDO_VOLT_1_1V 0x01 +#define P3H2x4x_LDO_VOLT_1_2V 0x02 +#define P3H2x4x_LDO_VOLT_1_8V 0x03 +#define P3H2x4x_DT_LDO_VOLT_NOT_SET 0x04 + +#define P3H2x4x_LDO_DISABLED 0x00 +#define P3H2x4x_LDO_ENABLED 0x01 + +#define P3H2x4x_IBI_DISABLED 0x00 +#define P3H2x4x_IBI_ENABLED 0x01 + +/* Pullup selection DT settings */ +#define P3H2x4x_TP_PULLUP_250R 0x00 +#define P3H2x4x_TP_PULLUP_500R 0x01 +#define P3H2x4x_TP_PULLUP_1000R 0x02 +#define P3H2x4x_TP_PULLUP_2000R 0x03 +#define P3H2x4x_TP_PULLUP_NOT_SET 0x04 + +#define P3H2x4x_TP_PULLUP_DISABLED 0x00 +#define P3H2x4x_TP_PULLUP_ENABLED 0x01 + +#define P3H2x4x_IO_STRENGTH_20_OHM 0x00 +#define P3H2x4x_IO_STRENGTH_30_OHM 0x01 +#define P3H2x4x_IO_STRENGTH_40_OHM 0x02 +#define P3H2x4x_IO_STRENGTH_50_OHM 0x03 +#define P3H2x4x_IO_STRENGTH_NOT_SET 0x04 + +#define P3H2x4x_TP_MODE_I3C 0x00 +#define P3H2x4x_TP_MODE_SMBUS 0x01 +#define P3H2x4x_TP_MODE_GPIO 0x02 +#define P3H2x4x_TP_MODE_I2C 0x03 +#define P3H2x4x_TP_MODE_NOT_SET 0x04 + +#define ONE_BYTE_SIZE 0x01 + +/* holding SDA low when both SMBus Target Agent received data buffers are full. + * This feature can be used as a flow-control mechanism for MCTP applications to + * avoid MCTP transmitters on Target Ports time out when the SMBus agent buffers + * are not serviced in time by upstream controller and only receives write message + * from its downstream ports. + * SMBUS_AGENT_TX_RX_LOOPBACK_EN/TARGET_AGENT_BUF_FULL_SDA_LOW_EN + */ + +#define P3H2x4x_TARGET_AGENT_DFT_IBI_CONF 0x20 +#define P3H2x4x_TARGET_AGENT_DFT_IBI_CONF_MASK 0x21 + +/* Transaction status checking mask */ + +#define P3H2x4x_SMBUS_TRANSACTION_FINISH_FLAG 1 +#define P3H2x4x_SMBUS_CNTRL_STATUS_TXN_SHIFT 4 + +#define P3H2x4x_SMBUS_CNTRL_STATUS_TXN_OK 0 +#define P3H2x4x_SMBUS_CNTRL_STATUS_TXN_ADDR_NAK 1 +#define P3H2x4x_SMBUS_CNTRL_STATUS_TXN_DATA_NAK 2 +#define P3H2x4x_SMBUS_CNTRL_STATUS_TXN_WTR_NAK 3 +#define P3H2x4x_SMBUS_CNTRL_STATUS_TXN_SYNC_RCV 4 +#define P3H2x4x_SMBUS_CNTRL_STATUS_TXN_SYNC_RCVCLR 5 +#define P3H2x4x_SMBUS_CNTRL_STATUS_TXN_FAULT 6 +#define P3H2x4x_SMBUS_CNTRL_STATUS_TXN_ARB_LOSS 7 +#define P3H2x4x_SMBUS_CNTRL_STATUS_TXN_SCL_TO 8 + +#define P3H2x4x_TP_BUFFER_STATUS_MASK 0x0f +#define P3H2x4x_TP_TRANSACTION_CODE_MASK 0xf0 + +/* SMBus transaction types fields */ +#define P3H2x4x_SMBUS_400kHz BIT(2) + +/* SMBus polling */ +#define P3H2x4x_POLLING_ROLL_PERIOD_MS 10 + +/* Hub buffer size */ +#define P3H2x4x_CONTROLLER_BUFFER_SIZE 88 +#define P3H2x4x_TARGET_BUFFER_SIZE 80 +#define P3H2x4x_SMBUS_DESCRIPTOR_SIZE 4 +#define P3H2x4x_SMBUS_PAYLOAD_SIZE \ + (P3H2x4x_CONTROLLER_BUFFER_SIZE - P3H2x4x_SMBUS_DESCRIPTOR_SIZE) +#define P3H2x4x_SMBUS_TARGET_PAYLOAD_SIZE (P3H2x4x_TARGET_BUFFER_SIZE - 2) + +/* Hub SMBus transaction time */ +#define P3H2x4x_SMBUS_400kHz_TRANSFER_TIMEOUT(x) ((20 * (x)) + 80) + +#define P3H2x4x_NO_PAGE_PER_TP 4 + +#define P3H2x4x_MAX_PAYLOAD_LEN 2 +#define P3H2x4x_NUM_SLOTS 6 + +#define P3H2x4x_HUB_ID 0 + +#define P3H2x4x_SET_BIT(n) BIT(n) + +enum p3h2x4x_tp { + TP_0, + TP_1, + TP_2, + TP_3, + TP_4, + TP_5, + TP_6, + TP_7, +}; + +enum p3h2x4x_rcv_buf { + RCV_BUF_0, + RCV_BUF_1, + RCV_BUF_OF, +}; + +struct p3h2x4x_regulators { + struct regulator *rcp0; + struct regulator *rcp1; + struct regulator *rtp0145; + struct regulator *rtp2367; +}; + +struct p3h2x4x_setting { + const char *const name; + const u8 value; +}; + +struct tp_setting { + bool pullup_en; + bool ibi_en; + bool always_enable; + int mode; +}; + +struct dt_settings { + bool cp0_ldo_en; + bool cp1_ldo_en; + bool tp0145_ldo_en; + bool tp2367_ldo_en; + int cp0_ldo_volt; + int cp1_ldo_volt; + int tp0145_ldo_volt; + int tp2367_ldo_volt; + int tp0145_pullup; + int tp2367_pullup; + int cp0_io_strength; + int cp1_io_strength; + int tp0145_io_strength; + int tp2367_io_strength; + struct tp_setting tp[P3H2x4x_TP_MAX_COUNT]; +}; + +struct smbus_device { + struct i2c_client *client; + const char *compatible; + int addr; + struct list_head list; + struct device_node *tp_device_dt_node; +}; + +struct tp_bus { + bool is_registered; /* bus was registered in the framework. */ + bool is_slave_registered; + u8 tp_mask; + u8 tp_port; + u8 local_dev_count; + u8 local_dev_list[P3H2x4x_TP_LOCAL_DEV]; + struct i3c_master_controller i3c_port_controller; + struct i2c_adapter *smbus_port_adapter; + struct device_node *of_node; + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub; + struct list_head tp_device_entry; + struct mutex port_mutex; /* per port mutex */ +}; + +struct p3h2x4x_i3c_hub_dev { + struct i3c_master_controller *driving_master; + struct i3c_device *i3cdev; + struct regmap *regmap; + struct mutex etx_mutex; /* all port mutex */ + struct dt_settings settings; + struct tp_bus tp_bus[P3H2x4x_TP_MAX_COUNT]; + struct p3h2x4x_regulators rp3h2x4x; + /* Offset for reading HUB's register. */ + u8 tp_ibi_mask; + u8 tp_i3c_bus_mask; + u8 tp_always_enable_mask; + bool is_p3h2x4x_in_i3c; + struct device *dev; +}; + +/** + * p3h2x4x_tp_smbus_algo - add i2c adapter for target port who + * configured as SMBus. + * @priv: p3h2x4x device structure. + * @tp: target port. + * Return: 0 in case of success, a negative EINVAL code if the error. + */ +int p3h2x4x_tp_smbus_algo(struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub, int tp); + +/** + * p3h2x4x_tp_i3c_algo - register i3c controller for target port who + * configured as I3C. + * @priv: p3h2x4x device structure. + * @tp: target port. + * Return: 0 in case of success, a negative EINVAL code if the error. + */ +int p3h2x4x_tp_i3c_algo(struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub, int tp); + +/** + * p3h2x4x_ibi_handler - IBI handler. + * @i3cdev: i3c device. + * @payload: two byte IBI payload data. + */ +void p3h2x4x_ibi_handler(struct i3c_device *i3cdev, + const struct i3c_ibi_payload *payload); +#endif /* P3H2840_I3C_HUB_H */ + diff --git a/drivers/i3c/hub/p3h2840_i3c_hub_common.c b/drivers/i3c/hub/p3h2840_i3c_hub_common.c new file mode 100644 index 000000000000..1ca887ad8e2b --- /dev/null +++ b/drivers/i3c/hub/p3h2840_i3c_hub_common.c @@ -0,0 +1,782 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2025 NXP + * This P3H2x4x driver file implements functions for Hub probe and DT parsing. + */ + +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/mfd/p3h2840.h> + +#include "p3h2840_i3c_hub.h" + +/* LDO voltage DT settings */ +#define P3H2x4x_DT_LDO_VOLT_1_0V 1000 +#define P3H2x4x_DT_LDO_VOLT_1_1V 1100 +#define P3H2x4x_DT_LDO_VOLT_1_2V 1200 +#define P3H2x4x_DT_LDO_VOLT_1_8V 1800 + +/* target port pull-up settings */ +#define P3H2x4x_DT_TP_PULLUP_250R 250 +#define P3H2x4x_DT_TP_PULLUP_500R 500 +#define P3H2x4x_DT_TP_PULLUP_1000R 1000 +#define P3H2x4x_DT_TP_PULLUP_2000R 2000 + +/* IO strenght settings */ +#define P3H2x4x_DT_IO_STRENGTH_20_OHM 20 +#define P3H2x4x_DT_IO_STRENGTH_30_OHM 30 +#define P3H2x4x_DT_IO_STRENGTH_40_OHM 40 +#define P3H2x4x_DT_IO_STRENGTH_50_OHM 50 + +/* target port mode settings */ +static const struct p3h2x4x_setting tp_mode_settings[] = { + { "i3c", P3H2x4x_TP_MODE_I3C }, + { "smbus", P3H2x4x_TP_MODE_SMBUS }, + { "gpio", P3H2x4x_TP_MODE_GPIO }, + { "i2c", P3H2x4x_TP_MODE_I2C }, +}; + +static const struct i3c_ibi_setup p3h2x4x_ibireq = { + .handler = p3h2x4x_ibi_handler, + .max_payload_len = P3H2x4x_MAX_PAYLOAD_LEN, + .num_slots = P3H2x4x_NUM_SLOTS, +}; + +static void p3h2x4x_of_get_dt_setting(struct device *dev, + const struct device_node *node, + const char *setting_name, + const struct p3h2x4x_setting settings[], + const int settings_count, + int *setting_value) +{ + const char *sval; + int ret; + int i; + + ret = of_property_read_string(node, setting_name, &sval); + if (ret) { + if (ret != -EINVAL) + dev_warn(dev, "No setting or invalid setting for %s, err=%i\n", + setting_name, ret); + return; + } + + for (i = 0; i < settings_count; ++i) { + const struct p3h2x4x_setting *const setting = &settings[i]; + + if (!strcmp(setting->name, sval)) { + *setting_value = setting->value; + return; + } + } + dev_warn(dev, "Unknown setting for %s\n", setting_name); +} + +static u8 p3h2x4x_pullup_dt_to_reg(int dt_value) +{ + switch (dt_value) { + case P3H2x4x_DT_TP_PULLUP_2000R: + return P3H2x4x_TP_PULLUP_2000R; + case P3H2x4x_DT_TP_PULLUP_1000R: + return P3H2x4x_TP_PULLUP_1000R; + case P3H2x4x_DT_TP_PULLUP_250R: + return P3H2x4x_TP_PULLUP_250R; + default: + return P3H2x4x_TP_PULLUP_500R; + } +} + +static u8 p3h2x4x_io_strength_dt_to_reg(int dt_value) +{ + switch (dt_value) { + case P3H2x4x_DT_IO_STRENGTH_50_OHM: + return P3H2x4x_IO_STRENGTH_50_OHM; + case P3H2x4x_DT_IO_STRENGTH_40_OHM: + return P3H2x4x_IO_STRENGTH_40_OHM; + case P3H2x4x_DT_IO_STRENGTH_30_OHM: + return P3H2x4x_IO_STRENGTH_30_OHM; + default: + return P3H2x4x_IO_STRENGTH_20_OHM; + } +} + +static int p3h2x4x_configure_ldo(struct device *dev) +{ + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = dev_get_drvdata(dev); + int ret; + + p3h2x4x_i3c_hub->rp3h2x4x.rcp0 = devm_regulator_get_optional(dev, "cp0"); + if (IS_ERR(p3h2x4x_i3c_hub->rp3h2x4x.rcp0)) { + p3h2x4x_i3c_hub->rp3h2x4x.rcp0 = NULL; + dev_dbg(dev, "cp0-supply not found\n"); + } + + p3h2x4x_i3c_hub->rp3h2x4x.rcp1 = devm_regulator_get_optional(dev, "cp1"); + if (IS_ERR(p3h2x4x_i3c_hub->rp3h2x4x.rcp1)) { + p3h2x4x_i3c_hub->rp3h2x4x.rcp1 = NULL; + dev_dbg(dev, "cp1-supply not found\n"); + } + + p3h2x4x_i3c_hub->rp3h2x4x.rtp0145 = devm_regulator_get_optional(dev, "tp0145"); + if (IS_ERR(p3h2x4x_i3c_hub->rp3h2x4x.rtp0145)) { + p3h2x4x_i3c_hub->rp3h2x4x.rtp0145 = NULL; + dev_dbg(dev, "tp0145-supply not found\n"); + } + + p3h2x4x_i3c_hub->rp3h2x4x.rtp2367 = devm_regulator_get_optional(dev, "tp2367"); + if (IS_ERR(p3h2x4x_i3c_hub->rp3h2x4x.rtp2367)) { + p3h2x4x_i3c_hub->rp3h2x4x.rtp2367 = NULL; + dev_dbg(dev, "tp2367-supply not found\n"); + } + + /* Set the regulators volatage */ + if (p3h2x4x_i3c_hub->settings.cp0_ldo_volt != P3H2x4x_DT_LDO_VOLT_NOT_SET && + p3h2x4x_i3c_hub->rp3h2x4x.rcp0) { + ret = regulator_set_voltage(p3h2x4x_i3c_hub->rp3h2x4x.rcp0, + p3h2x4x_i3c_hub->settings.cp0_ldo_volt, + p3h2x4x_i3c_hub->settings.cp0_ldo_volt); + if (ret) + dev_warn(dev, "Failed to set CP0 voltage (ignoring)\n"); + } + + /* Set the regulators volatage */ + if (p3h2x4x_i3c_hub->settings.cp1_ldo_volt != P3H2x4x_DT_LDO_VOLT_NOT_SET && + p3h2x4x_i3c_hub->rp3h2x4x.rcp1) { + ret = regulator_set_voltage(p3h2x4x_i3c_hub->rp3h2x4x.rcp1, + p3h2x4x_i3c_hub->settings.cp1_ldo_volt, + p3h2x4x_i3c_hub->settings.cp1_ldo_volt); + if (ret) + dev_warn(dev, "Failed to set CP1 voltage (ignoring)\n"); + } + + /* Set the regulators volatage */ + if (p3h2x4x_i3c_hub->settings.tp0145_ldo_volt != P3H2x4x_DT_LDO_VOLT_NOT_SET && + p3h2x4x_i3c_hub->rp3h2x4x.rtp0145) { + ret = regulator_set_voltage(p3h2x4x_i3c_hub->rp3h2x4x.rtp0145, + p3h2x4x_i3c_hub->settings.tp0145_ldo_volt, + p3h2x4x_i3c_hub->settings.tp0145_ldo_volt); + if (ret) + dev_warn(dev, "Failed to set TP0145 voltage (ignoring)\n"); + } + + /* Set the regulators volatage */ + if (p3h2x4x_i3c_hub->settings.tp2367_ldo_volt != P3H2x4x_DT_LDO_VOLT_NOT_SET && + p3h2x4x_i3c_hub->rp3h2x4x.rtp2367) { + ret = regulator_set_voltage(p3h2x4x_i3c_hub->rp3h2x4x.rtp2367, + p3h2x4x_i3c_hub->settings.tp2367_ldo_volt, + p3h2x4x_i3c_hub->settings.tp2367_ldo_volt); + if (ret) + dev_warn(dev, "Failed to set TP2367 voltage (ignoring)\n"); + } + + /* Enable the regulators */ + if (p3h2x4x_i3c_hub->rp3h2x4x.rcp0) { + ret = regulator_enable(p3h2x4x_i3c_hub->rp3h2x4x.rcp0); + if (ret < 0) { + dev_err(dev, "Failed to enable cp0 ldo\n"); + return ret; + } + } + + if (p3h2x4x_i3c_hub->rp3h2x4x.rcp1) { + ret = regulator_enable(p3h2x4x_i3c_hub->rp3h2x4x.rcp1); + if (ret < 0) { + dev_err(dev, "Failed to enable cp1 ldo\n"); + return ret; + } + } + + if (p3h2x4x_i3c_hub->rp3h2x4x.rtp0145) { + ret = regulator_enable(p3h2x4x_i3c_hub->rp3h2x4x.rtp0145); + if (ret < 0) { + dev_err(dev, "Failed to enable tp0145 ldo\n"); + return ret; + } + } + + if (p3h2x4x_i3c_hub->rp3h2x4x.rtp2367) { + ret = regulator_enable(p3h2x4x_i3c_hub->rp3h2x4x.rtp2367); + if (ret < 0) { + dev_err(dev, "Failed to enable tp2367 ldo\n"); + return ret; + } + } + return 0; +} + +static int p3h2x4x_configure_pullup(struct device *dev) +{ + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = dev_get_drvdata(dev); + u8 mask_all = 0, val_all = 0; + + if (p3h2x4x_i3c_hub->settings.tp0145_pullup != P3H2x4x_TP_PULLUP_NOT_SET) { + val_all |= P3H2x4x_TP0145_PULLUP_CONF(p3h2x4x_pullup_dt_to_reg + (p3h2x4x_i3c_hub->settings.tp0145_pullup)); + mask_all |= P3H2x4x_TP0145_PULLUP_CONF_MASK; + } + + if (p3h2x4x_i3c_hub->settings.tp2367_pullup != P3H2x4x_TP_PULLUP_NOT_SET) { + val_all |= P3H2x4x_TP2367_PULLUP_CONF(p3h2x4x_pullup_dt_to_reg + (p3h2x4x_i3c_hub->settings.tp2367_pullup)); + mask_all |= P3H2x4x_TP2367_PULLUP_CONF_MASK; + } + + return regmap_update_bits(p3h2x4x_i3c_hub->regmap, P3H2x4x_LDO_AND_PULLUP_CONF, + mask_all, val_all); +} + +static int p3h2x4x_configure_io_strength(struct device *dev) +{ + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = dev_get_drvdata(dev); + u8 mask_all = 0, val_all = 0; + + if (p3h2x4x_i3c_hub->settings.cp0_io_strength != P3H2x4x_IO_STRENGTH_NOT_SET) { + val_all |= P3H2x4x_CP0_IO_STRENGTH(p3h2x4x_io_strength_dt_to_reg + (p3h2x4x_i3c_hub->settings.cp0_io_strength)); + mask_all |= P3H2x4x_CP0_IO_STRENGTH_MASK; + } + + if (p3h2x4x_i3c_hub->settings.cp1_io_strength != P3H2x4x_IO_STRENGTH_NOT_SET) { + val_all |= P3H2x4x_CP1_IO_STRENGTH(p3h2x4x_io_strength_dt_to_reg + (p3h2x4x_i3c_hub->settings.cp1_io_strength)); + mask_all |= P3H2x4x_CP1_IO_STRENGTH_MASK; + } + + if (p3h2x4x_i3c_hub->settings.tp0145_io_strength != P3H2x4x_IO_STRENGTH_NOT_SET) { + val_all |= P3H2x4x_TP0145_IO_STRENGTH(p3h2x4x_io_strength_dt_to_reg + (p3h2x4x_i3c_hub->settings.tp0145_io_strength)); + mask_all |= P3H2x4x_TP0145_IO_STRENGTH_MASK; + } + + if (p3h2x4x_i3c_hub->settings.tp2367_io_strength != P3H2x4x_IO_STRENGTH_NOT_SET) { + val_all |= P3H2x4x_TP2367_IO_STRENGTH(p3h2x4x_io_strength_dt_to_reg + (p3h2x4x_i3c_hub->settings.tp2367_io_strength)); + mask_all |= P3H2x4x_TP2367_IO_STRENGTH_MASK; + } + + return regmap_update_bits(p3h2x4x_i3c_hub->regmap, P3H2x4x_IO_STRENGTH, mask_all, val_all); +} + +static int p3h2x4x_configure_tp(struct device *dev) +{ + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = dev_get_drvdata(dev); + u8 pullup_val = 0, ibi_val = 0; + u8 smbus_val = 0, i2c_val = 0; + u8 gpio_val = 0, i3c_val = 0; + u8 i, mode_mask = 0; + int ret; + + /* TBD: Read type of HUB from register P3H2x4x_DEV_INFO_0 to learn target ports count. */ + for (i = 0; i < P3H2x4x_TP_MAX_COUNT; ++i) { + if (p3h2x4x_i3c_hub->settings.tp[i].mode != P3H2x4x_TP_MODE_NOT_SET) { + if (p3h2x4x_i3c_hub->settings.tp[i].mode == P3H2x4x_TP_MODE_I3C) + i3c_val |= P3H2x4x_SET_BIT(i); + else if (p3h2x4x_i3c_hub->settings.tp[i].mode == P3H2x4x_TP_MODE_SMBUS) + smbus_val |= P3H2x4x_SET_BIT(i); + else if (p3h2x4x_i3c_hub->settings.tp[i].mode == P3H2x4x_TP_MODE_GPIO) + gpio_val |= P3H2x4x_SET_BIT(i); + else if (p3h2x4x_i3c_hub->settings.tp[i].mode == P3H2x4x_TP_MODE_I2C) + i2c_val |= P3H2x4x_SET_BIT(i); + + mode_mask |= P3H2x4x_SET_BIT(i); + } + if (p3h2x4x_i3c_hub->settings.tp[i].pullup_en) + pullup_val |= P3H2x4x_SET_BIT(i); + + if (p3h2x4x_i3c_hub->settings.tp[i].ibi_en) + ibi_val |= P3H2x4x_SET_BIT(i); + } + + ret = regmap_update_bits(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP_IO_MODE_CONF, + mode_mask, (smbus_val | i2c_val)); + if (ret) + return ret; + + ret = regmap_update_bits(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP_PULLUP_EN, + pullup_val, pullup_val); + if (ret) + return ret; + + ret = regmap_update_bits(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP_SMBUS_AGNT_IBI_CONFIG, + ibi_val, ibi_val); + if (ret) + return ret; + p3h2x4x_i3c_hub->tp_ibi_mask = ibi_val; + + ret = regmap_update_bits(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP_SMBUS_AGNT_EN, + mode_mask, smbus_val); + if (ret) + return ret; + + ret = regmap_update_bits(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP_GPIO_MODE_EN, + mode_mask, gpio_val); + if (ret) + return ret; + + /* Request for HUB Network connection in case any TP is configured in I3C mode */ + if ((i3c_val) || (i2c_val)) { + ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_CP_MUX_SET, + P3H2x4x_CONTROLLER_PORT_MUX_REQ); + if (ret) + return ret; + } + + /* Enable TP here in case TP was configured */ + ret = regmap_update_bits(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP_ENABLE, + mode_mask, (i3c_val | smbus_val | gpio_val | i2c_val)); + if (ret) + return ret; + + return 0; +} + +static int p3h2x4x_configure_smbus_local_device(struct device *dev) +{ + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = dev_get_drvdata(dev); + u8 target_buffer_page, hub_tp; + int ret = 0; + + for (hub_tp = 0; hub_tp < P3H2x4x_TP_MAX_COUNT; hub_tp++) { + if (p3h2x4x_i3c_hub->settings.tp[hub_tp].mode == P3H2x4x_TP_MODE_SMBUS && + p3h2x4x_i3c_hub->tp_bus[hub_tp].local_dev_count) { + target_buffer_page = P3H2x4x_TARGET_AGENT_LOCAL_DEV + 4 * hub_tp; + ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_PAGE_PTR, + target_buffer_page); + if (ret) { + dev_err(dev, "Failed to configure local device settings\n"); + break; + } + + ret = regmap_bulk_write(p3h2x4x_i3c_hub->regmap, + P3H2x4x_CONTROLLER_AGENT_BUFF, + p3h2x4x_i3c_hub->tp_bus[hub_tp].local_dev_list, + p3h2x4x_i3c_hub->tp_bus[hub_tp].local_dev_count); + if (ret) { + dev_err(dev, "Failed to add local devices\n"); + break; + } + } + } + regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_PAGE_PTR, 0x00); + return ret; +} + +static int p3h2x4x_configure_hw(struct device *dev) +{ + int ret; + + ret = p3h2x4x_configure_ldo(dev); + if (ret) + return ret; + + ret = p3h2x4x_configure_io_strength(dev); + if (ret) + return ret; + + ret = p3h2x4x_configure_pullup(dev); + if (ret) + return ret; + + ret = p3h2x4x_configure_smbus_local_device(dev); + if (ret) + return ret; + + return p3h2x4x_configure_tp(dev); +} + +static void p3h2x4x_of_get_tp_dt_conf(struct device *dev, + const struct device_node *node) +{ + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = dev_get_drvdata(dev); + struct device_node *dev_node; + u64 tp_port; + + for_each_available_child_of_node(node, dev_node) { + if (of_property_read_reg(dev_node, 0, &tp_port, NULL)) + continue; + + if (tp_port < P3H2x4x_TP_MAX_COUNT) { + p3h2x4x_i3c_hub->tp_bus[tp_port].of_node = dev_node; + p3h2x4x_i3c_hub->tp_bus[tp_port].tp_mask = BIT(tp_port); + p3h2x4x_i3c_hub->tp_bus[tp_port].p3h2x4x_i3c_hub = p3h2x4x_i3c_hub; + p3h2x4x_i3c_hub->tp_bus[tp_port].tp_port = tp_port; + } + } +} + +/* return true when backend node exist */ +static bool p3h2x4x_is_backend_node_exist(int port, + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub, + u32 addr) +{ + struct smbus_device *backend = NULL; + + list_for_each_entry(backend, + &p3h2x4x_i3c_hub->tp_bus[port].tp_device_entry, list) { + if (backend->addr == addr) + return true; + } + return false; +} + +static int p3h2x4x_read_backend_from_dts(struct device_node *i3c_node_target, + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub) +{ + struct device_node *tp_node; + const char *compatible; + u64 tp_port, addr_dts; + int ret; + + struct smbus_device *backend; + + if (of_property_read_reg(i3c_node_target, 0, &tp_port, NULL)) + return -EINVAL; + + if (tp_port >= P3H2x4x_TP_MAX_COUNT || tp_port < 0) + return -ERANGE; + + INIT_LIST_HEAD(&p3h2x4x_i3c_hub->tp_bus[tp_port].tp_device_entry); + + if (p3h2x4x_i3c_hub->settings.tp[tp_port].mode == P3H2x4x_TP_MODE_I3C) + return 0; + + for_each_available_child_of_node(i3c_node_target, tp_node) { + ret = of_property_read_reg(tp_node, 0, &addr_dts, NULL); + if (ret) + return ret; + + if (p3h2x4x_is_backend_node_exist(tp_port, p3h2x4x_i3c_hub, addr_dts)) + continue; + + ret = of_property_read_string(tp_node, "compatible", &compatible); + if (ret) + return ret; + + backend = kzalloc(sizeof(*backend), GFP_KERNEL); + if (!backend) + return -ENOMEM; + + backend->addr = addr_dts; + backend->compatible = compatible; + backend->tp_device_dt_node = tp_node; + backend->client = NULL; + + list_add(&backend->list, + &p3h2x4x_i3c_hub->tp_bus[tp_port].tp_device_entry); + } + + return 0; +} + +static void p3h2x4x_parse_dt_tp(struct device *dev, + const struct device_node *i3c_node_hub, + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub) +{ + struct device_node *i3c_node_target; + int ret; + + for_each_available_child_of_node(i3c_node_hub, i3c_node_target) { + ret = p3h2x4x_read_backend_from_dts(i3c_node_target, p3h2x4x_i3c_hub); + if (ret) + dev_warn(dev, "Skiping DTS node %s", i3c_node_target->name); + } +} + +static int p3h2x4x_get_tp_local_device_dt_setting(struct device *dev, + const struct device_node *node, u32 id) +{ + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = dev_get_drvdata(dev); + int ret; + + ret = of_property_read_variable_u8_array(node, "local-dev", + p3h2x4x_i3c_hub->tp_bus[id].local_dev_list, + sizeof(u8), P3H2x4x_TP_LOCAL_DEV); + if (ret > 0 && ret <= P3H2x4x_TP_LOCAL_DEV) + p3h2x4x_i3c_hub->tp_bus[id].local_dev_count = ret; + else if (ret == -EOVERFLOW) + dev_warn(dev, + "local Devices list is out of range or invalid\n"); + + return ret; +} + +static void p3h2x4x_get_tp_of_get_setting(struct device *dev, + const struct device_node *node, + struct tp_setting tp_setting[]) +{ + struct device_node *tp_node; + u64 id; + + for_each_available_child_of_node(node, tp_node) { + if (of_property_read_reg(tp_node, 0, &id, NULL)) + continue; + + if (id >= P3H2x4x_TP_MAX_COUNT) { + dev_warn(dev, "Invalid target port index found in DT: %lli\n", id); + continue; + } + + p3h2x4x_of_get_dt_setting(dev, tp_node, "mode", tp_mode_settings, + ARRAY_SIZE(tp_mode_settings), + &tp_setting[id].mode); + + tp_setting[id].pullup_en = + of_property_read_bool(tp_node, "pullup-enable"); + tp_setting[id].ibi_en = + of_property_read_bool(tp_node, "ibi-enable"); + tp_setting[id].always_enable = + of_property_read_bool(tp_node, "hub_bridge_en"); + + p3h2x4x_get_tp_local_device_dt_setting(dev, tp_node, id); + } +} + +static void p3h2x4x_of_get_p3h2x4x_conf(struct device *dev, + const struct device_node *node) +{ + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = dev_get_drvdata(dev); + + p3h2x4x_i3c_hub->settings.cp0_ldo_en = + of_property_read_bool(node, "cp0-ldo-enable"); + p3h2x4x_i3c_hub->settings.cp1_ldo_en = + of_property_read_bool(node, "cp1-ldo-enable"); + p3h2x4x_i3c_hub->settings.tp0145_ldo_en = + of_property_read_bool(node, "tp0145-ldo-enable"); + p3h2x4x_i3c_hub->settings.tp2367_ldo_en = + of_property_read_bool(node, "tp2367-ldo-enable"); + + of_property_read_u32(node, "cp0-ldo-microvolt", + &p3h2x4x_i3c_hub->settings.cp0_ldo_volt); + of_property_read_u32(node, "cp1-ldo-microvolt", + &p3h2x4x_i3c_hub->settings.cp1_ldo_volt); + of_property_read_u32(node, "tp0145-ldo-microvolt", + &p3h2x4x_i3c_hub->settings.tp0145_ldo_volt); + of_property_read_u32(node, "tp2367-ldo-microvolt", + &p3h2x4x_i3c_hub->settings.tp2367_ldo_volt); + of_property_read_u32(node, "tp0145-pullup-ohms", + &p3h2x4x_i3c_hub->settings.tp0145_pullup); + of_property_read_u32(node, "tp2367-pullup-ohms", + &p3h2x4x_i3c_hub->settings.tp2367_pullup); + of_property_read_u32(node, "cp0-io-strength-ohms", + &p3h2x4x_i3c_hub->settings.cp0_io_strength); + of_property_read_u32(node, "cp1-io-strength-ohms", + &p3h2x4x_i3c_hub->settings.cp1_io_strength); + of_property_read_u32(node, "tp0145-io-strength-ohms", + &p3h2x4x_i3c_hub->settings.tp0145_io_strength); + of_property_read_u32(node, "tp2367-io-strength-ohms", + &p3h2x4x_i3c_hub->settings.tp2367_io_strength); + + p3h2x4x_get_tp_of_get_setting(dev, node, p3h2x4x_i3c_hub->settings.tp); +} + +static void p3h2x4x_of_default_configuration(struct device *dev) +{ + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = dev_get_drvdata(dev); + int tp_count; + + p3h2x4x_i3c_hub->settings.cp0_ldo_volt = P3H2x4x_DT_LDO_VOLT_NOT_SET; + p3h2x4x_i3c_hub->settings.cp1_ldo_volt = P3H2x4x_DT_LDO_VOLT_NOT_SET; + p3h2x4x_i3c_hub->settings.tp0145_ldo_volt = P3H2x4x_DT_LDO_VOLT_NOT_SET; + p3h2x4x_i3c_hub->settings.tp2367_ldo_volt = P3H2x4x_DT_LDO_VOLT_NOT_SET; + p3h2x4x_i3c_hub->settings.tp0145_pullup = P3H2x4x_TP_PULLUP_NOT_SET; + p3h2x4x_i3c_hub->settings.tp2367_pullup = P3H2x4x_TP_PULLUP_NOT_SET; + p3h2x4x_i3c_hub->settings.cp0_io_strength = P3H2x4x_IO_STRENGTH_NOT_SET; + p3h2x4x_i3c_hub->settings.cp1_io_strength = P3H2x4x_IO_STRENGTH_NOT_SET; + p3h2x4x_i3c_hub->settings.tp0145_io_strength = P3H2x4x_IO_STRENGTH_NOT_SET; + p3h2x4x_i3c_hub->settings.tp2367_io_strength = P3H2x4x_IO_STRENGTH_NOT_SET; + + for (tp_count = 0; tp_count < P3H2x4x_TP_MAX_COUNT; ++tp_count) + p3h2x4x_i3c_hub->settings.tp[tp_count].mode = P3H2x4x_TP_MODE_NOT_SET; +} + +int p3h2x4x_i3c_hub_probe(struct platform_device *pdev) +{ + struct p3h2x4x_dev *p3h2x4x = dev_get_drvdata(pdev->dev.parent); + struct device_node *node __free(device_node) = NULL; + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub; + struct device *dev = &pdev->dev; + int ret, i; + + p3h2x4x_i3c_hub = devm_kzalloc(dev, sizeof(*p3h2x4x_i3c_hub), GFP_KERNEL); + if (!p3h2x4x_i3c_hub) + return -ENOMEM; + + platform_set_drvdata(pdev, p3h2x4x_i3c_hub); + + p3h2x4x_i3c_hub->regmap = p3h2x4x->regmap; + + if (p3h2x4x->is_p3h2x4x_in_i3c) { + p3h2x4x_i3c_hub->i3cdev = p3h2x4x->i3cdev; + p3h2x4x_i3c_hub->driving_master = i3c_dev_get_master(p3h2x4x->i3cdev->desc); + } + + p3h2x4x_i3c_hub->is_p3h2x4x_in_i3c = p3h2x4x->is_p3h2x4x_in_i3c; + p3h2x4x_i3c_hub->dev = dev; + + device_set_of_node_from_dev(dev, dev->parent); + + p3h2x4x_of_default_configuration(dev); + mutex_init(&p3h2x4x_i3c_hub->etx_mutex); + + for (i = 0; i < P3H2x4x_TP_MAX_COUNT; i++) + mutex_init(&p3h2x4x_i3c_hub->tp_bus[i].port_mutex); + + /* get hub node from DT */ + node = dev->of_node; + if (!node) { + dev_dbg(dev, "No DT entry - running with hardware defaults.\n"); + } else { + p3h2x4x_of_get_p3h2x4x_conf(dev, node); + p3h2x4x_of_get_tp_dt_conf(dev, node); + /* Parse DTS to find backend device on the SMBus target mode */ + p3h2x4x_parse_dt_tp(dev, node, p3h2x4x_i3c_hub); + } + + /* Unlock access to protected registers */ + ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_DEV_REG_PROTECTION_CODE, + P3H2x4x_REGISTERS_UNLOCK_CODE); + if (ret) + return dev_err_probe(dev, ret, "Failed to unlock HUB's protected registers\n"); + + ret = p3h2x4x_configure_hw(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to configure the HUB\n"); + + if (p3h2x4x->is_p3h2x4x_in_i3c) { + /* Register logic for native vertual I3C ports */ + for (i = 0; i < P3H2x4x_TP_MAX_COUNT; i++) { + if (p3h2x4x_i3c_hub->settings.tp[i].mode == P3H2x4x_TP_MODE_I3C && + !p3h2x4x_i3c_hub->settings.tp[i].always_enable) { + ret = p3h2x4x_tp_i3c_algo(p3h2x4x_i3c_hub, i); + if (ret) + return dev_err_probe(dev, ret, + "failed to register i3c bus tp %d\n", + i); + } + } + } + + for (i = 0; i < P3H2x4x_TP_MAX_COUNT; ++i) { + if (p3h2x4x_i3c_hub->settings.tp[i].always_enable) + p3h2x4x_i3c_hub->tp_always_enable_mask = + (p3h2x4x_i3c_hub->tp_always_enable_mask | BIT(i)); + } + + ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP_NET_CON_CONF, + p3h2x4x_i3c_hub->tp_always_enable_mask); + if (ret) + return dev_err_probe(dev, ret, "Failed to open Target Port(s)\n"); + + if (p3h2x4x->is_p3h2x4x_in_i3c) { + ret = i3c_master_do_daa(p3h2x4x_i3c_hub->driving_master); + if (ret) + dev_warn(dev, "Failed to run DAA\n"); + + /* + * holding SDA low when both SMBus Target Agent received data buffers are full. + * This feature can be used as a flow-control mechanism for MCTP applications to + * avoid MCTP transmitters on Target Ports time out when the SMBus agent buffers + * are not serviced in time by upstream controller and only receives write message + * from its downstream ports. + */ + ret = regmap_update_bits(p3h2x4x_i3c_hub->regmap, + P3H2x4x_ONCHIP_TD_AND_SMBUS_AGNT_CONF, + P3H2x4x_TARGET_AGENT_DFT_IBI_CONF_MASK, + P3H2x4x_TARGET_AGENT_DFT_IBI_CONF); + if (ret) + return dev_err_probe(dev, ret, "Failed to data buffer full config\n"); + + ret = i3c_device_request_ibi(p3h2x4x->i3cdev, &p3h2x4x_ibireq); + if (ret) + return dev_err_probe(dev, ret, "Failed to request IBI\n"); + + ret = i3c_device_enable_ibi(p3h2x4x->i3cdev); + if (ret) + return dev_err_probe(dev, ret, "Failed to Enable IBI\n"); + } + + /* Register logic for native SMBus ports */ + for (i = 0; i < P3H2x4x_TP_MAX_COUNT; i++) { + if (p3h2x4x_i3c_hub->settings.tp[i].mode == P3H2x4x_TP_MODE_SMBUS) { + ret = p3h2x4x_tp_smbus_algo(p3h2x4x_i3c_hub, i); + if (ret) + return dev_err_probe(dev, ret, + "failed to add i2c adapter for tp %d\n", i); + } + } + + /* Lock access to protected registers */ + ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_DEV_REG_PROTECTION_CODE, + P3H2x4x_REGISTERS_LOCK_CODE); + if (ret) + return dev_err_probe(dev, ret, "Failed to lock HUB's protected registers\n"); + + return 0; +} + +int p3h2x4x_i3c_hub_remove(struct platform_device *pdev) +{ + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = platform_get_drvdata(pdev); + struct p3h2x4x_dev *p3h2x4x = dev_get_drvdata(pdev->dev.parent); + struct i3c_master_controller *tp_controller; + struct smbus_device *backend = NULL; + struct i2c_adapter *tp_adap; + u8 i; + + if (p3h2x4x_i3c_hub->rp3h2x4x.rcp0) + regulator_disable(p3h2x4x_i3c_hub->rp3h2x4x.rcp0); + + if (p3h2x4x_i3c_hub->rp3h2x4x.rcp1) + regulator_disable(p3h2x4x_i3c_hub->rp3h2x4x.rcp1); + + if (p3h2x4x_i3c_hub->rp3h2x4x.rtp0145) + regulator_disable(p3h2x4x_i3c_hub->rp3h2x4x.rtp0145); + + if (p3h2x4x_i3c_hub->rp3h2x4x.rtp2367) + regulator_disable(p3h2x4x_i3c_hub->rp3h2x4x.rtp2367); + + for (i = 0; i < P3H2x4x_TP_MAX_COUNT; i++) { + tp_adap = p3h2x4x_i3c_hub->tp_bus[i].smbus_port_adapter; + tp_controller = &p3h2x4x_i3c_hub->tp_bus[i].i3c_port_controller; + + if (p3h2x4x_i3c_hub->tp_bus[i].is_registered) { + if (p3h2x4x_i3c_hub->settings.tp[i].mode == P3H2x4x_TP_MODE_SMBUS) { + list_for_each_entry(backend, + &p3h2x4x_i3c_hub->tp_bus[i].tp_device_entry, + list) { + i2c_unregister_device(backend->client); + kfree(backend); + } + i2c_del_adapter(tp_adap); + } else if (p3h2x4x_i3c_hub->settings.tp[i].mode == P3H2x4x_TP_MODE_I3C) { + i3c_master_unregister(tp_controller); + } + } + } + if (p3h2x4x->is_p3h2x4x_in_i3c) { + i3c_device_disable_ibi(p3h2x4x->i3cdev); + i3c_device_free_ibi(p3h2x4x->i3cdev); + } + + mutex_destroy(&p3h2x4x_i3c_hub->etx_mutex); + for (i = 0; i < P3H2x4x_TP_MAX_COUNT; i++) + mutex_destroy(&p3h2x4x_i3c_hub->tp_bus[i].port_mutex); + + return 0; +} + +static struct platform_driver p3h2x4x_i3c_hub_driver = { + .driver = { + .name = "p3h2x4x-i3c-hub", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .probe = p3h2x4x_i3c_hub_probe, + .remove = p3h2x4x_i3c_hub_remove, +}; +module_platform_driver(p3h2x4x_i3c_hub_driver); + +MODULE_AUTHOR("Aman Kumar Pandey <aman.kumarpandey@nxp.com>"); +MODULE_AUTHOR("vikash Bansal <vikash.bansal@nxp.com>"); +MODULE_DESCRIPTION("P3H2x4x I3C HUB driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i3c/hub/p3h2840_i3c_hub_i3c.c b/drivers/i3c/hub/p3h2840_i3c_hub_i3c.c new file mode 100644 index 000000000000..8903bc48a88c --- /dev/null +++ b/drivers/i3c/hub/p3h2840_i3c_hub_i3c.c @@ -0,0 +1,345 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2025 NXP + * This P3H2x4x driver file contain functions for I3C virtual Bus creation, connect/disconnect + * hub network and read/write. + */ +#include <linux/mfd/p3h2840.h> +#include <linux/regmap.h> + +#include "p3h2840_i3c_hub.h" + +static void p3h2x4x_en_p3h2x4x_ntwk_tp(struct tp_bus *bus) +{ + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = bus->p3h2x4x_i3c_hub; + + if (p3h2x4x_i3c_hub->settings.tp[bus->tp_port].always_enable) + return; + + regmap_set_bits(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP_NET_CON_CONF, bus->tp_mask); +} + +static void p3h2x4x_dis_p3h2x4x_ntwk_tp(struct tp_bus *bus) +{ + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = bus->p3h2x4x_i3c_hub; + + if (p3h2x4x_i3c_hub->settings.tp[bus->tp_port].always_enable) + return; + + regmap_clear_bits(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP_NET_CON_CONF, bus->tp_mask); +} + +static struct tp_bus *p3h2x4x_bus_from_i3c_desc(struct i3c_dev_desc *desc) +{ + struct i3c_master_controller *controller = i3c_dev_get_master(desc); + + return container_of(controller, struct tp_bus, i3c_port_controller); +} + +static struct tp_bus *p3h2x4x_bus_from_i2c_desc(struct i2c_dev_desc *desc) +{ + struct i3c_master_controller *controller = i2c_dev_get_master(desc); + + return container_of(controller, struct tp_bus, i3c_port_controller); +} + +static struct i3c_master_controller +*get_parent_controller(struct i3c_master_controller *controller) +{ + struct tp_bus *bus = container_of(controller, struct tp_bus, i3c_port_controller); + + return bus->p3h2x4x_i3c_hub->driving_master; +} + +static struct i3c_master_controller +*get_parent_controller_from_i3c_desc(struct i3c_dev_desc *desc) +{ + struct i3c_master_controller *controller = i3c_dev_get_master(desc); + struct tp_bus *bus = container_of(controller, struct tp_bus, i3c_port_controller); + + return bus->p3h2x4x_i3c_hub->driving_master; +} + +static struct i3c_master_controller +*get_parent_controller_from_i2c_desc(struct i2c_dev_desc *desc) +{ + struct i3c_master_controller *controller = desc->common.master; + struct tp_bus *bus = container_of(controller, struct tp_bus, i3c_port_controller); + + return bus->p3h2x4x_i3c_hub->driving_master; +} + +static struct i3c_master_controller +*update_i3c_i2c_desc_parent(struct i3c_i2c_dev_desc *desc, + struct i3c_master_controller *parent) +{ + struct i3c_master_controller *orig_parent = desc->master; + + desc->master = parent; + + return orig_parent; +} + +static void restore_i3c_i2c_desc_parent(struct i3c_i2c_dev_desc *desc, + struct i3c_master_controller *parent) +{ + desc->master = parent; +} + +static int p3h2x4x_i3c_bus_init(struct i3c_master_controller *controller) +{ + struct tp_bus *bus = container_of(controller, struct tp_bus, i3c_port_controller); + + controller->this = bus->p3h2x4x_i3c_hub->i3cdev->desc; + return 0; +} + +static void p3h2x4x_i3c_bus_cleanup(struct i3c_master_controller *controller) +{ + controller->this = NULL; +} + +static int p3h2x4x_attach_i3c_dev(struct i3c_dev_desc *dev) +{ + struct i3c_master_controller *parent = get_parent_controller_from_i3c_desc(dev); + struct i3c_master_controller *orig_parent; + int ret; + + orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent); + ret = parent->ops->attach_i3c_dev(dev); + restore_i3c_i2c_desc_parent(&dev->common, orig_parent); + return ret; +} + +static int p3h2x4x_reattach_i3c_dev(struct i3c_dev_desc *dev, u8 old_dyn_addr) +{ + struct i3c_master_controller *parent = get_parent_controller_from_i3c_desc(dev); + struct i3c_master_controller *orig_parent; + int ret; + + orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent); + ret = parent->ops->reattach_i3c_dev(dev, old_dyn_addr); + restore_i3c_i2c_desc_parent(&dev->common, orig_parent); + return ret; +} + +static void p3h2x4x_detach_i3c_dev(struct i3c_dev_desc *dev) +{ + struct i3c_master_controller *parent = get_parent_controller_from_i3c_desc(dev); + struct i3c_master_controller *orig_parent; + + orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent); + parent->ops->detach_i3c_dev(dev); + restore_i3c_i2c_desc_parent(&dev->common, orig_parent); +} + +static int p3h2x4x_do_daa(struct i3c_master_controller *controller) +{ + struct tp_bus *bus = container_of(controller, struct tp_bus, i3c_port_controller); + struct i3c_master_controller *parent = get_parent_controller(controller); + + int ret; + + p3h2x4x_en_p3h2x4x_ntwk_tp(bus); + down_write(&parent->bus.lock); + ret = parent->ops->do_daa(parent); + up_write(&parent->bus.lock); + p3h2x4x_dis_p3h2x4x_ntwk_tp(bus); + + return ret; +} + +static bool p3h2x4x_supports_ccc_cmd(struct i3c_master_controller *controller, + const struct i3c_ccc_cmd *cmd) +{ + struct i3c_master_controller *parent = get_parent_controller(controller); + + return parent->ops->supports_ccc_cmd(parent, cmd); +} + +static int p3h2x4x_send_ccc_cmd(struct i3c_master_controller *controller, + struct i3c_ccc_cmd *cmd) +{ + struct tp_bus *bus = container_of(controller, struct tp_bus, i3c_port_controller); + struct i3c_master_controller *parent = get_parent_controller(controller); + int ret; + + if (cmd->id == I3C_CCC_RSTDAA(true)) + return 0; + + p3h2x4x_en_p3h2x4x_ntwk_tp(bus); + ret = parent->ops->send_ccc_cmd(parent, cmd); + p3h2x4x_dis_p3h2x4x_ntwk_tp(bus); + + return ret; +} + +static int p3h2x4x_priv_xfers(struct i3c_dev_desc *dev, + struct i3c_priv_xfer *xfers, int nxfers) +{ + struct i3c_master_controller *parent = get_parent_controller_from_i3c_desc(dev); + struct tp_bus *bus = p3h2x4x_bus_from_i3c_desc(dev); + struct i3c_master_controller *orig_parent; + int res; + + p3h2x4x_en_p3h2x4x_ntwk_tp(bus); + orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent); + res = parent->ops->priv_xfers(dev, xfers, nxfers); + restore_i3c_i2c_desc_parent(&dev->common, orig_parent); + p3h2x4x_dis_p3h2x4x_ntwk_tp(bus); + + return res; +} + +static int p3h2x4x_attach_i2c_dev(struct i2c_dev_desc *dev) +{ + struct i3c_master_controller *parent = get_parent_controller_from_i2c_desc(dev); + struct i3c_master_controller *orig_parent; + int ret; + + orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent); + ret = parent->ops->attach_i2c_dev(dev); + restore_i3c_i2c_desc_parent(&dev->common, orig_parent); + return ret; +} + +static void p3h2x4x_detach_i2c_dev(struct i2c_dev_desc *dev) +{ + struct i3c_master_controller *parent = get_parent_controller_from_i2c_desc(dev); + struct i3c_master_controller *orig_parent; + + orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent); + parent->ops->detach_i2c_dev(dev); + restore_i3c_i2c_desc_parent(&dev->common, orig_parent); +} + +static int p3h2x4x_i2c_xfers(struct i2c_dev_desc *dev, + const struct i2c_msg *xfers, int nxfers) +{ + struct i3c_master_controller *parent = get_parent_controller_from_i2c_desc(dev); + struct tp_bus *bus = p3h2x4x_bus_from_i2c_desc(dev); + struct i3c_master_controller *orig_parent; + int ret; + + p3h2x4x_en_p3h2x4x_ntwk_tp(bus); + orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent); + ret = parent->ops->i2c_xfers(dev, xfers, nxfers); + restore_i3c_i2c_desc_parent(&dev->common, orig_parent); + p3h2x4x_dis_p3h2x4x_ntwk_tp(bus); + return ret; +} + +static int p3h2x4x_request_ibi(struct i3c_dev_desc *dev, + const struct i3c_ibi_setup *req) +{ + struct i3c_master_controller *parent = get_parent_controller_from_i3c_desc(dev); + struct tp_bus *bus = p3h2x4x_bus_from_i3c_desc(dev); + struct i3c_master_controller *orig_parent; + int ret; + + p3h2x4x_en_p3h2x4x_ntwk_tp(bus); + orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent); + ret = parent->ops->request_ibi(dev, req); + restore_i3c_i2c_desc_parent(&dev->common, orig_parent); + p3h2x4x_dis_p3h2x4x_ntwk_tp(bus); + return ret; +} + +static void p3h2x4x_free_ibi(struct i3c_dev_desc *dev) +{ + struct i3c_master_controller *parent = get_parent_controller_from_i3c_desc(dev); + struct tp_bus *bus = p3h2x4x_bus_from_i3c_desc(dev); + struct i3c_master_controller *orig_parent; + + p3h2x4x_en_p3h2x4x_ntwk_tp(bus); + orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent); + parent->ops->free_ibi(dev); + restore_i3c_i2c_desc_parent(&dev->common, orig_parent); + p3h2x4x_dis_p3h2x4x_ntwk_tp(bus); +} + +static int p3h2x4x_enable_ibi(struct i3c_dev_desc *dev) +{ + struct i3c_master_controller *parent = get_parent_controller_from_i3c_desc(dev); + struct tp_bus *bus = p3h2x4x_bus_from_i3c_desc(dev); + struct i3c_master_controller *orig_parent; + int ret; + + p3h2x4x_en_p3h2x4x_ntwk_tp(bus); + orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent); + ret = parent->ops->enable_ibi(dev); + restore_i3c_i2c_desc_parent(&dev->common, orig_parent); + p3h2x4x_dis_p3h2x4x_ntwk_tp(bus); + return ret; +} + +static int p3h2x4x_disable_ibi(struct i3c_dev_desc *dev) +{ + struct i3c_master_controller *parent = get_parent_controller_from_i3c_desc(dev); + struct tp_bus *bus = p3h2x4x_bus_from_i3c_desc(dev); + struct i3c_master_controller *orig_parent; + int ret; + + p3h2x4x_en_p3h2x4x_ntwk_tp(bus); + orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent); + ret = parent->ops->disable_ibi(dev); + restore_i3c_i2c_desc_parent(&dev->common, orig_parent); + p3h2x4x_dis_p3h2x4x_ntwk_tp(bus); + return ret; +} + +static void p3h2x4x_recycle_ibi_slot(struct i3c_dev_desc *dev, + struct i3c_ibi_slot *slot) +{ + struct i3c_master_controller *parent = get_parent_controller_from_i3c_desc(dev); + struct i3c_master_controller *orig_parent; + + orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent); + parent->ops->recycle_ibi_slot(dev, slot); + restore_i3c_i2c_desc_parent(&dev->common, orig_parent); +} + +static const struct i3c_master_controller_ops p3h2x4x_i3c_ops = { + .bus_init = p3h2x4x_i3c_bus_init, + .bus_cleanup = p3h2x4x_i3c_bus_cleanup, + .attach_i3c_dev = p3h2x4x_attach_i3c_dev, + .reattach_i3c_dev = p3h2x4x_reattach_i3c_dev, + .detach_i3c_dev = p3h2x4x_detach_i3c_dev, + .do_daa = p3h2x4x_do_daa, + .supports_ccc_cmd = p3h2x4x_supports_ccc_cmd, + .send_ccc_cmd = p3h2x4x_send_ccc_cmd, + .priv_xfers = p3h2x4x_priv_xfers, + .attach_i2c_dev = p3h2x4x_attach_i2c_dev, + .detach_i2c_dev = p3h2x4x_detach_i2c_dev, + .i2c_xfers = p3h2x4x_i2c_xfers, + .request_ibi = p3h2x4x_request_ibi, + .free_ibi = p3h2x4x_free_ibi, + .enable_ibi = p3h2x4x_enable_ibi, + .disable_ibi = p3h2x4x_disable_ibi, + .recycle_ibi_slot = p3h2x4x_recycle_ibi_slot, +}; + +/** + * p3h2x4x_tp_i3c_algo - register i3c master for target port who + * configured as i3c. + * @p3h2x4x_i3c_hub: p3h2x4x device structure. + * @tp: target port. + * + * Return: 0 in case of success, a negative EINVAL code if the error. + */ +int p3h2x4x_tp_i3c_algo(struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub, int tp) +{ + int ret; + + p3h2x4x_i3c_hub->dev->of_node = p3h2x4x_i3c_hub->tp_bus[tp].of_node; + ret = i3c_master_register(&p3h2x4x_i3c_hub->tp_bus[tp].i3c_port_controller, + p3h2x4x_i3c_hub->dev, &p3h2x4x_i3c_ops, false); + if (ret) { + dev_warn(p3h2x4x_i3c_hub->dev, + "Failed to register i3c controller for port:%i\n", tp); + return ret; + } + p3h2x4x_i3c_hub->tp_bus[tp].is_registered = true; + + return 0; +} diff --git a/drivers/i3c/hub/p3h2840_i3c_hub_smbus.c b/drivers/i3c/hub/p3h2840_i3c_hub_smbus.c new file mode 100644 index 000000000000..3416cbfd5675 --- /dev/null +++ b/drivers/i3c/hub/p3h2840_i3c_hub_smbus.c @@ -0,0 +1,627 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2025 NXP + * This P3H2x4x driver file contain functions for SMBus/I2C virtual Bus creation and read/write. + */ +#include <linux/mfd/p3h2840.h> +#include <linux/regmap.h> + +#include "p3h2840_i3c_hub.h" + +#if IS_ENABLED(CONFIG_I2C_SLAVE) +static void p3h2x4x_read_smbus_agent_rx_buf(struct i3c_device *i3cdev, enum p3h2x4x_rcv_buf rfbuf, + enum p3h2x4x_tp tp, bool is_of) +{ + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = dev_get_drvdata(&i3cdev->dev); + u8 slave_rx_buffer[P3H2x4x_SMBUS_TARGET_PAYLOAD_SIZE] = { 0 }; + u8 target_buffer_page, flag_clear = 0x0f, temp, i; + struct smbus_device *backend = NULL; + u32 packet_len, slave_address, ret; + + target_buffer_page = (((rfbuf) ? P3H2x4x_TARGET_BUFF_1_PAGE : P3H2x4x_TARGET_BUFF_0_PAGE) + + (P3H2x4x_NO_PAGE_PER_TP * tp)); + ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_PAGE_PTR, target_buffer_page); + if (ret) + goto ibi_err; + + /* read buffer length */ + ret = regmap_read(p3h2x4x_i3c_hub->regmap, P3H2x4x_TARGET_BUFF_LENGTH, &packet_len); + if (ret) + goto ibi_err; + + if (packet_len) + packet_len = packet_len - 1; + + if (packet_len > P3H2x4x_SMBUS_TARGET_PAYLOAD_SIZE) { + dev_err(&i3cdev->dev, "Received message too big for p3h2x4x buffer\n"); + return; + } + + /* read slave address */ + ret = regmap_read(p3h2x4x_i3c_hub->regmap, P3H2x4x_TARGET_BUFF_ADDRESS, &slave_address); + if (ret) + goto ibi_err; + + /* read data */ + if (packet_len) { + ret = regmap_bulk_read(p3h2x4x_i3c_hub->regmap, P3H2x4x_TARGET_BUFF_DATA, + slave_rx_buffer, packet_len); + if (ret) + goto ibi_err; + } + + if (is_of) + flag_clear = BUF_RECEIVED_FLAG_TF_MASK; + else + flag_clear = (((rfbuf == RCV_BUF_0) ? P3H2x4x_TARGET_BUF_0_RECEIVE : + P3H2x4x_TARGET_BUF_1_RECEIVE)); + + /* notify slave driver about received data */ + list_for_each_entry(backend, &p3h2x4x_i3c_hub->tp_bus[tp].tp_device_entry, list) { + if (p3h2x4x_i3c_hub->tp_bus[tp].is_slave_registered && + slave_address >> 1 == backend->addr) { + i2c_slave_event(backend->client, I2C_SLAVE_WRITE_REQUESTED, + (u8 *)&slave_address); + for (i = 0; i < packet_len; i++) { + temp = slave_rx_buffer[i]; + i2c_slave_event(backend->client, I2C_SLAVE_WRITE_RECEIVED, + &temp); + } + i2c_slave_event(backend->client, I2C_SLAVE_STOP, &temp); + break; + } + } + +ibi_err: + regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_PAGE_PTR, 0x00); + regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP0_SMBUS_AGNT_STS + tp, flag_clear); +} +#endif + +/** + * p3h2x4x_ibi_handler - IBI handler. + * @i3cdev: i3c device. + * @payload: two byte IBI payload data. + * + */ +void p3h2x4x_ibi_handler(struct i3c_device *i3cdev, + const struct i3c_ibi_payload *payload) +{ + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = dev_get_drvdata(&i3cdev->dev); + u32 target_port_status, payload_byte_one, payload_byte_two; + u32 ret, i; + + payload_byte_one = (*(int *)payload->data); + payload_byte_two = (*(int *)(payload->data + 4)); + + if (!(payload_byte_one & P3H2x4x_SMBUS_AGENT_EVENT_FLAG_STATUS)) + goto err; + +#if IS_ENABLED(CONFIG_I2C_SLAVE) + guard(mutex)(&p3h2x4x_i3c_hub->etx_mutex); + ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP_SMBUS_AGNT_IBI_CONFIG, + P3H2x4x_ALL_TP_IBI_DIS); + if (ret) { + dev_err(&i3cdev->dev, "Failed to Disable IBI\n"); + goto err; + } + + for (i = 0; i < P3H2x4x_TP_MAX_COUNT; ++i) { + if (p3h2x4x_i3c_hub->tp_bus[i].is_registered && (payload_byte_two >> i) & 0x01) { + ret = regmap_read(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP0_SMBUS_AGNT_STS + i, + &target_port_status); + if (ret) { + dev_err(&i3cdev->dev, "target port read status failed %d\n", ret); + goto err; + } + + /* process data receive buffer */ + switch (target_port_status & BUF_RECEIVED_FLAG_MASK) { + case P3H2x4x_TARGET_BUF_CA_TF: + break; + case P3H2x4x_TARGET_BUF_0_RECEIVE: + p3h2x4x_read_smbus_agent_rx_buf(i3cdev, RCV_BUF_0, i, false); + break; + case P3H2x4x_TARGET_BUF_1_RECEIVE: + p3h2x4x_read_smbus_agent_rx_buf(i3cdev, RCV_BUF_1, i, false); + break; + case P3H2x4x_TARGET_BUF_0_1_RECEIVE: + p3h2x4x_read_smbus_agent_rx_buf(i3cdev, RCV_BUF_0, i, false); + p3h2x4x_read_smbus_agent_rx_buf(i3cdev, RCV_BUF_1, i, false); + break; + case P3H2x4x_TARGET_BUF_OVRFL: + p3h2x4x_read_smbus_agent_rx_buf(i3cdev, RCV_BUF_0, i, false); + p3h2x4x_read_smbus_agent_rx_buf(i3cdev, RCV_BUF_1, i, true); + dev_err(&i3cdev->dev, "Overflow, reading buffer zero and one\n"); + break; + default: + regmap_write(p3h2x4x_i3c_hub->regmap, + P3H2x4x_TP0_SMBUS_AGNT_STS + i, + BUF_RECEIVED_FLAG_TF_MASK); + break; + } + } + } +#endif +err: + regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP_SMBUS_AGNT_IBI_CONFIG, + p3h2x4x_i3c_hub->tp_ibi_mask); +} + +static int p3h2x4x_read_smbus_transaction_status(struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub, + u8 target_port_status, + u8 data_length) +{ + u32 status_read; + int ret; + + mutex_unlock(&p3h2x4x_i3c_hub->etx_mutex); + fsleep(P3H2x4x_SMBUS_400kHz_TRANSFER_TIMEOUT(data_length)); + mutex_lock(&p3h2x4x_i3c_hub->etx_mutex); + + ret = regmap_read(p3h2x4x_i3c_hub->regmap, target_port_status, &status_read); + if (ret) + return ret; + + if (status_read & P3H2x4x_SMBUS_TRANSACTION_FINISH_FLAG) { + switch (status_read >> P3H2x4x_SMBUS_CNTRL_STATUS_TXN_SHIFT) { + case P3H2x4x_SMBUS_CNTRL_STATUS_TXN_OK: + ret = 0; + break; + case P3H2x4x_SMBUS_CNTRL_STATUS_TXN_ADDR_NAK: + ret = -ENXIO; + break; + case P3H2x4x_SMBUS_CNTRL_STATUS_TXN_DATA_NAK: + ret = -EIO; + break; + case P3H2x4x_SMBUS_CNTRL_STATUS_TXN_ARB_LOSS: + ret = -EAGAIN; + break; + case P3H2x4x_SMBUS_CNTRL_STATUS_TXN_SCL_TO: + ret = -ETIMEDOUT; + break; + case P3H2x4x_SMBUS_CNTRL_STATUS_TXN_WTR_NAK: + ret = -ENXIO; + break; + default: + ret = -EIO; + break; + } + } + return ret; +} + +/** + * p3h2x4x_tp_i2c_xfer_msg() - This starts a SMBus write transaction by writing a descriptor + * and a message to the p3h2x4x registers. Controller buffer page is determined by multiplying the + * target port index by four and adding the base page number to it. + */ +static int p3h2x4x_tp_i2c_xfer_msg(struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub, + struct i2c_msg *xfers, + u8 target_port, + u8 nxfers_i, u8 rw) +{ + u8 controller_buffer_page = P3H2x4x_CONTROLLER_BUFFER_PAGE + 4 * target_port; + u8 target_port_code = p3h2x4x_i3c_hub->tp_bus[target_port].tp_mask; + u8 target_port_status = P3H2x4x_TP0_SMBUS_AGNT_STS + target_port; + u8 desc[P3H2x4x_SMBUS_DESCRIPTOR_SIZE] = { 0 }; + u8 transaction_type = P3H2x4x_SMBUS_400kHz; + int write_length = xfers[nxfers_i].len; + int read_length = xfers[nxfers_i].len; + u8 addr = xfers[nxfers_i].addr; + u8 rw_address = 2 * addr; + int ret; + + if (rw) { + rw_address |= BIT(0); + write_length = 0; + } else { + read_length = 0; + } + + desc[0] = rw_address; + desc[1] = transaction_type; + desc[2] = write_length; + desc[3] = read_length; + + ret = regmap_write(p3h2x4x_i3c_hub->regmap, target_port_status, + P3H2x4x_TP_BUFFER_STATUS_MASK); + if (ret) + return ret; + + ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_PAGE_PTR, controller_buffer_page); + if (ret) + return ret; + + ret = regmap_bulk_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_CONTROLLER_AGENT_BUFF, + desc, P3H2x4x_SMBUS_DESCRIPTOR_SIZE); + if (ret) + return ret; + + if (!rw) { + ret = regmap_bulk_write(p3h2x4x_i3c_hub->regmap, + P3H2x4x_CONTROLLER_AGENT_BUFF_DATA, + xfers[nxfers_i].buf, xfers[nxfers_i].len); + if (ret) + return ret; + } + + ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP_SMBUS_AGNT_TRANS_START, + target_port_code); + if (ret) + return ret; + + ret = p3h2x4x_read_smbus_transaction_status(p3h2x4x_i3c_hub, target_port_status, + (write_length + read_length)); + if (ret) + return ret; + + if (rw) { + ret = regmap_bulk_read(p3h2x4x_i3c_hub->regmap, + P3H2x4x_CONTROLLER_AGENT_BUFF_DATA, + xfers[nxfers_i].buf, xfers[nxfers_i].len); + if (ret) + return ret; + } + + ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_PAGE_PTR, 0x00); + if (ret) + return ret; + + return 0; +} + +/* + * This function will be called whenever you call I2C read, write APIs like + * i2c_master_send(), i2c_master_recv() etc. + */ +static s32 p3h2x4x_tp_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) +{ + int ret_sum = 0, ret; + u8 msg_count, rw; + + struct tp_bus *bus = i2c_get_adapdata(adap); + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = bus->p3h2x4x_i3c_hub; + + guard(mutex)(&p3h2x4x_i3c_hub->etx_mutex); + guard(mutex)(&bus->port_mutex); + + if (p3h2x4x_i3c_hub->is_p3h2x4x_in_i3c) { + ret = i3c_device_disable_ibi(p3h2x4x_i3c_hub->i3cdev); + if (ret) { + dev_err(p3h2x4x_i3c_hub->dev, "Failed to disable IBI, ret :- %d\n", ret); + return ret; + } + } + + for (msg_count = 0; msg_count < num; msg_count++) { + if (msgs[msg_count].len > P3H2x4x_SMBUS_PAYLOAD_SIZE) { + dev_err(p3h2x4x_i3c_hub->dev, + "Message nr. %d not sent - length over %d bytes.\n", + msg_count, P3H2x4x_SMBUS_PAYLOAD_SIZE); + continue; + } + rw = msgs[msg_count].flags % 2; + + ret = p3h2x4x_tp_i2c_xfer_msg(p3h2x4x_i3c_hub, + msgs, + bus->tp_port, + msg_count, rw); + + if (ret) + goto error; + + ret_sum++; + } + +error: + if (p3h2x4x_i3c_hub->is_p3h2x4x_in_i3c) { + ret = i3c_device_enable_ibi(p3h2x4x_i3c_hub->i3cdev); + if (ret) { + dev_err(p3h2x4x_i3c_hub->dev, "Failed to enable IBI, ret %d\n", ret); + return ret; + } + } + return ret_sum; +} + +static int p3h2x4x_tp_smbus_xfer_msg(struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub, + u8 target_port, + u8 addr, + u8 rw, + u8 cmd, + int sz, + union i2c_smbus_data *data) +{ + u8 controller_buffer_page = P3H2x4x_CONTROLLER_BUFFER_PAGE + 4 * target_port; + u8 target_port_code = p3h2x4x_i3c_hub->tp_bus[target_port].tp_mask; + u8 target_port_status = P3H2x4x_TP0_SMBUS_AGNT_STS + target_port; + u8 desc[P3H2x4x_SMBUS_DESCRIPTOR_SIZE] = { 0 }; + u8 transaction_type = P3H2x4x_SMBUS_400kHz; + u8 buf[I2C_SMBUS_BLOCK_MAX + 2] = {0}; + u8 read_length = 0, write_length = 0; + u8 rw_address = 2 * addr; + int ret, i; + + /* Map the size to what the chip understands */ + switch (sz) { + case I2C_SMBUS_QUICK: + case I2C_SMBUS_BYTE: + if (rw) { + buf[0] = data->byte; + read_length = ONE_BYTE_SIZE; + write_length = 0; + rw_address |= BIT(0); + } else { + buf[0] = cmd; + write_length = ONE_BYTE_SIZE; + read_length = 0; + } + break; + case I2C_SMBUS_BYTE_DATA: + if (rw) { /* read write */ + buf[0] = cmd; + write_length = ONE_BYTE_SIZE; + read_length = ONE_BYTE_SIZE; + transaction_type |= BIT(0); + } else { /* only write */ + buf[0] = cmd; + buf[1] = data->byte; + write_length = ONE_BYTE_SIZE + 1; + read_length = 0; + } + break; + case I2C_SMBUS_WORD_DATA: + if (rw) { /* read write */ + buf[0] = cmd; + write_length = ONE_BYTE_SIZE; + read_length = 2; + transaction_type |= BIT(0); + } else { /* only write */ + buf[0] = cmd; + buf[1] = data->word & 0xff; + buf[2] = (data->word & 0xff00) >> 8; + write_length = ONE_BYTE_SIZE + 2; + read_length = 0; + } + break; + case I2C_SMBUS_BLOCK_DATA: + if (rw) { /* read write */ + buf[0] = cmd; + write_length = ONE_BYTE_SIZE; + read_length = data->block[0] + 1; + transaction_type |= BIT(0); + } else { /* only write */ + buf[0] = cmd; + for (i = 0 ; i <= data->block[0]; i++) + buf[i + 1] = data->block[i]; + + write_length = data->block[0] + 2; + read_length = 0; + } + break; + case I2C_SMBUS_I2C_BLOCK_DATA: + if (rw) { /* read write */ + buf[0] = cmd; + write_length = ONE_BYTE_SIZE; + read_length = data->block[0]; + transaction_type |= BIT(0); + } else { /* only write */ + buf[0] = cmd; + for (i = 0 ; i < data->block[0]; i++) + buf[i + 1] = data->block[i + 1]; + + write_length = data->block[0] + 1; + read_length = 0; + } + break; + default: + dev_warn(p3h2x4x_i3c_hub->dev, "Unsupported transaction %d\n", sz); + break; + } + + desc[0] = rw_address; + desc[1] = transaction_type; + desc[2] = write_length; + desc[3] = read_length; + + ret = regmap_write(p3h2x4x_i3c_hub->regmap, target_port_status, + P3H2x4x_TP_BUFFER_STATUS_MASK); + if (ret) + return ret; + + ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_PAGE_PTR, controller_buffer_page); + if (ret) + return ret; + + ret = regmap_bulk_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_CONTROLLER_AGENT_BUFF, + desc, P3H2x4x_SMBUS_DESCRIPTOR_SIZE); + if (ret) + return ret; + + if (write_length) { + ret = regmap_bulk_write(p3h2x4x_i3c_hub->regmap, + P3H2x4x_CONTROLLER_AGENT_BUFF_DATA, + buf, write_length); + if (ret) + return ret; + } + + ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP_SMBUS_AGNT_TRANS_START, + target_port_code); + if (ret) + return ret; + + ret = p3h2x4x_read_smbus_transaction_status(p3h2x4x_i3c_hub, target_port_status, + (write_length + read_length)); + if (ret) + return ret; + + if (rw) { + switch (sz) { + case I2C_SMBUS_QUICK: + case I2C_SMBUS_BYTE: + case I2C_SMBUS_BYTE_DATA: + { + ret = regmap_bulk_read(p3h2x4x_i3c_hub->regmap, + P3H2x4x_CONTROLLER_AGENT_BUFF_DATA + + write_length, + &data->byte, read_length); + break; + } + case I2C_SMBUS_WORD_DATA: + { + ret = regmap_bulk_read(p3h2x4x_i3c_hub->regmap, + P3H2x4x_CONTROLLER_AGENT_BUFF_DATA + + write_length, + (u8 *)&data->word, read_length); + break; + } + case I2C_SMBUS_BLOCK_DATA: + { + ret = regmap_bulk_read(p3h2x4x_i3c_hub->regmap, + P3H2x4x_CONTROLLER_AGENT_BUFF_DATA + + write_length, + data->block, read_length); + break; + } + case I2C_SMBUS_I2C_BLOCK_DATA: + { + ret = regmap_bulk_read(p3h2x4x_i3c_hub->regmap, + P3H2x4x_CONTROLLER_AGENT_BUFF_DATA + + write_length, + data->block + 1, read_length); + break; + } + default: + dev_warn(p3h2x4x_i3c_hub->dev, "Unsupported transaction %d\n", sz); + break; + } + + if (ret) + return ret; + } + + ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_PAGE_PTR, 0x00); + if (ret) + return ret; + + return 0; +} + +static s32 p3h2x4x_tp_smbus_xfer(struct i2c_adapter *adap, u16 addr, unsigned short flags, + char read_write, u8 command, int size, + union i2c_smbus_data *data) +{ + struct tp_bus *bus = i2c_get_adapdata(adap); + + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = bus->p3h2x4x_i3c_hub; + int ret, ret_status; + + guard(mutex)(&p3h2x4x_i3c_hub->etx_mutex); + guard(mutex)(&bus->port_mutex); + + if (p3h2x4x_i3c_hub->is_p3h2x4x_in_i3c) { + ret = i3c_device_disable_ibi(p3h2x4x_i3c_hub->i3cdev); + if (ret) { + dev_err(p3h2x4x_i3c_hub->dev, "Failed to disable IBI, ret :- %d\n", ret); + return ret; + } + } + + ret_status = p3h2x4x_tp_smbus_xfer_msg(p3h2x4x_i3c_hub, + (u8)bus->tp_port, + (u8)addr, + (u8)read_write, + (u8)command, + size, + data); + + if (p3h2x4x_i3c_hub->is_p3h2x4x_in_i3c) { + ret = i3c_device_enable_ibi(p3h2x4x_i3c_hub->i3cdev); + if (ret) { + dev_err(p3h2x4x_i3c_hub->dev, "Failed to enable IBI, ret %d\n", ret); + return ret; + } + } + + return ret_status; +} + +static u32 p3h2x4x_tp_smbus_funcs(struct i2c_adapter *adapter) +{ + return I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_I2C_BLOCK | I2C_FUNC_SMBUS_BLOCK_DATA | + I2C_FUNC_I2C; +} + +#if IS_ENABLED(CONFIG_I2C_SLAVE) +static int p3h2x4x_tp_i2c_reg_slave(struct i2c_client *slave) +{ + struct tp_bus *bus = i2c_get_adapdata(slave->adapter); + + bus->is_slave_registered = true; + + return 0; +} + +static int p3h2x4x_tp_i2c_unreg_slave(struct i2c_client *slave) +{ + struct tp_bus *bus = i2c_get_adapdata(slave->adapter); + + bus->is_slave_registered = false; + + return 0; +} +#endif + +/* + * I2C algorithm Structure + */ +static struct i2c_algorithm p3h2x4x_tp_i2c_algorithm = { + .master_xfer = p3h2x4x_tp_i2c_xfer, + .smbus_xfer = p3h2x4x_tp_smbus_xfer, +#if IS_ENABLED(CONFIG_I2C_SLAVE) + .reg_slave = p3h2x4x_tp_i2c_reg_slave, + .unreg_slave = p3h2x4x_tp_i2c_unreg_slave, +#endif + .functionality = p3h2x4x_tp_smbus_funcs, +}; + +/** + * p3h2x4x_tp_smbus_algo - add i2c adapter for target port who + * configured as SMBus. + * @p3h2x4x_i3c_hub: p3h2x4x device structure. + * @tp: target port. + * Return: 0 in case of success, a negative EINVAL code if the error. + */ +int p3h2x4x_tp_smbus_algo(struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub, int tp) +{ + struct i2c_adapter *smbus_adapter; + int ret; + + smbus_adapter = devm_kzalloc(p3h2x4x_i3c_hub->dev, sizeof(*smbus_adapter), GFP_KERNEL); + if (!smbus_adapter) + return -ENOMEM; + + smbus_adapter->owner = THIS_MODULE; + smbus_adapter->class = I2C_CLASS_HWMON; + smbus_adapter->algo = &p3h2x4x_tp_i2c_algorithm; + smbus_adapter->dev.parent = p3h2x4x_i3c_hub->dev; + smbus_adapter->dev.of_node = p3h2x4x_i3c_hub->tp_bus->of_node; + sprintf(smbus_adapter->name, "p3h2x4x-i3c-hub.tp-port-%d", tp); + + i2c_set_adapdata(smbus_adapter, &p3h2x4x_i3c_hub->tp_bus[tp]); + + ret = i2c_add_adapter(smbus_adapter); + if (ret) { + dev_warn(p3h2x4x_i3c_hub->dev, "Failed to register i2c adapter for port:%i\n", tp); + devm_kfree(p3h2x4x_i3c_hub->dev, smbus_adapter); + return ret; + } + + p3h2x4x_i3c_hub->tp_bus[tp].is_registered = true; + p3h2x4x_i3c_hub->tp_bus[tp].smbus_port_adapter = smbus_adapter; + return 0; +} diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 392b60c7c8dc..9f850a86ca2c 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -2414,5 +2414,17 @@ config MFD_RSMU_SPI Additional drivers must be enabled in order to use the functionality of the device. +config MFD_P3H2X4X + tristate "NXP P3H2X4X I3C Hub Device" + depends on I3C + select MFD_CORE + select REGMAP_I3C + help + Enable Support for NXP P3H244x/P3H284x I3C HUB device using I3C/I2c + communication interface. + + This driver provides support for I3C Hub and regulator, additional + drivers must be enabled in order to use the functionality of the device. + endmenu endif diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index cdbd3ef0c462..2fa6a5bf6a73 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -294,3 +294,4 @@ rsmu-i2c-objs := rsmu_core.o rsmu_i2c.o rsmu-spi-objs := rsmu_core.o rsmu_spi.o obj-$(CONFIG_MFD_RSMU_I2C) += rsmu-i2c.o obj-$(CONFIG_MFD_RSMU_SPI) += rsmu-spi.o +obj-$(CONFIG_MFD_P3H2X4X) += p3h2840.o diff --git a/drivers/mfd/p3h2840.c b/drivers/mfd/p3h2840.c new file mode 100644 index 000000000000..535c9a272ce7 --- /dev/null +++ b/drivers/mfd/p3h2840.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2025 NXP + * P3H2x4x i3c hub and regulator device. + */ + +#include <linux/i3c/master.h> +#include <linux/i2c.h> +#include <linux/mfd/core.h> +#include <linux/regmap.h> +#include <linux/mfd/p3h2840.h> + +static const struct mfd_cell p3h2x4x_devs[] = { + { + .name = "p3h2x4x-regulator", + }, + { + .name = "p3h2x4x-i3c-hub", + }, +}; + +static const struct regmap_config p3h2x4x_regmap_config = { + .reg_bits = P3H2x4x_REG_BITS, + .val_bits = P3H2x4x_VAL_BITS, + .max_register = 0xFF, +}; + +static int p3h2x4x_device_probe_i3c(struct i3c_device *i3cdev) +{ + struct p3h2x4x_dev *p3h2x4x; + int ret; + + p3h2x4x = devm_kzalloc(&i3cdev->dev, sizeof(*p3h2x4x), GFP_KERNEL); + if (!p3h2x4x) + return -ENOMEM; + + i3cdev_set_drvdata(i3cdev, p3h2x4x); + + p3h2x4x->regmap = devm_regmap_init_i3c(i3cdev, &p3h2x4x_regmap_config); + if (IS_ERR(p3h2x4x->regmap)) { + ret = PTR_ERR(p3h2x4x->regmap); + return dev_err_probe(&i3cdev->dev, ret, "Failed to register I3C HUB regmap\n"); + } + + p3h2x4x->is_p3h2x4x_in_i3c = true; + p3h2x4x->i3cdev = i3cdev; + + ret = devm_mfd_add_devices(&i3cdev->dev, PLATFORM_DEVID_NONE, + p3h2x4x_devs, ARRAY_SIZE(p3h2x4x_devs), + NULL, 0, NULL); + if (ret) + return dev_err_probe(&i3cdev->dev, ret, "Failed to add sub devices\n"); + + return 0; +} + +static int p3h2x4x_device_probe_i2c(struct i2c_client *client) +{ + struct p3h2x4x_dev *p3h2x4x; + int ret; + + p3h2x4x = devm_kzalloc(&client->dev, sizeof(*p3h2x4x), GFP_KERNEL); + if (!p3h2x4x) + return -ENOMEM; + + i2c_set_clientdata(client, p3h2x4x); + + p3h2x4x->regmap = devm_regmap_init_i2c(client, &p3h2x4x_regmap_config); + if (IS_ERR(p3h2x4x->regmap)) { + ret = PTR_ERR(p3h2x4x->regmap); + return dev_err_probe(&client->dev, ret, "Failed to register I3C HUB regmap\n"); + } + + p3h2x4x->is_p3h2x4x_in_i3c = false; + p3h2x4x->i2cdev = client; + + ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, + p3h2x4x_devs, ARRAY_SIZE(p3h2x4x_devs), + NULL, 0, NULL); + if (ret) + return dev_err_probe(&client->dev, ret, "Failed to add sub devices\n"); + + return 0; +} + +/* p3h2x4x ids (i3c) */ +static const struct i3c_device_id p3h2x4x_i3c_ids[] = { + I3C_CLASS(I3C_DCR_HUB, NULL), + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(i3c, p3h2x4x_i3c_ids); + +/* p3h2x4x ids (i2c) */ +static const struct i2c_device_id p3h2x4x_i2c_id_table[] = { + { "nxp-i3c-hub" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, p3h2x4x_i2c_id_table); + +static const struct of_device_id p3h2x4x_i2c_of_match[] = { + { .compatible = "nxp,p3h2840", }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, p3h2x4x_i2c_of_match); +static struct i3c_driver p3h2x4x_i3c = { + .driver = { + .name = "p3h2x4x_i3c_drv", + }, + .probe = p3h2x4x_device_probe_i3c, + .id_table = p3h2x4x_i3c_ids, +}; + +static struct i2c_driver p3h2x4x_i2c = { + .driver = { + .name = "p3h2x4x_i2c_drv", + .of_match_table = p3h2x4x_i2c_of_match, + }, + .probe = p3h2x4x_device_probe_i2c, + .id_table = p3h2x4x_i2c_id_table, +}; + +module_i3c_i2c_driver(p3h2x4x_i3c, &p3h2x4x_i2c); + +MODULE_AUTHOR("Aman Kumar Pandey <aman.kumarpandey@nxp.com>"); +MODULE_AUTHOR("vikash Bansal <vikash.bansal@nxp.com>"); +MODULE_DESCRIPTION("P3H2x4x I3C HUB driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index b3513900ccb5..966317b21276 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -1005,6 +1005,16 @@ config REGULATOR_PF8X00 on the NXP PF8100/PF8121A/PF8200 PMIC. The module will be named "pf8x00-regulator". +config REGULATOR_P3H2X4X + tristate "P3H2X4X regulator support" + depends on MFD_P3H2X4X + help + This driver provides support for the voltage regulators of the + P3H244x/P3H284x multi-function I3C Hub device. + + Say M here if you want to include support for this regulator as + a module. The module will be named "p3h2840_i3c_hub_regulator". + config REGULATOR_PF1550_RPMSG tristate "Freescale PF1550 rpmsg regulator driver" depends on RPMSG diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index c284babb2345..8feeacb67d2c 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -201,5 +201,6 @@ obj-$(CONFIG_REGULATOR_WM831X) += wm831x-ldo.o obj-$(CONFIG_REGULATOR_WM8350) += wm8350-regulator.o obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o obj-$(CONFIG_REGULATOR_WM8994) += wm8994-regulator.o +obj-$(CONFIG_REGULATOR_P3H2X4X) += p3h2840_i3c_hub_regulator.o ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG diff --git a/drivers/regulator/p3h2840_i3c_hub_regulator.c b/drivers/regulator/p3h2840_i3c_hub_regulator.c new file mode 100644 index 000000000000..f01dbc0aa691 --- /dev/null +++ b/drivers/regulator/p3h2840_i3c_hub_regulator.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2025 NXP + * This P3H2x4x driver file contain functions for enable/disable regulator and voltage set/get. + */ +#include <linux/regulator/driver.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/mfd/p3h2840.h> + +#define P3H2x4x_LDO_AND_PULLUP_CONF 0x19 +#define P3H2x4x_LDO_ENABLE_DISABLE_MASK GENMASK(3, 0) +#define P3H2x4x_CP0_EN_LDO BIT(0) +#define P3H2x4x_CP1_EN_LDO BIT(1) +#define P3H2x4x_TP0145_EN_LDO BIT(2) +#define P3H2x4x_TP2367_EN_LDO BIT(3) + +#define P3H2x4x_NET_OPER_MODE_CONF 0x15 +#define P3H2x4x_VCCIO_LDO_CONF 0x16 +#define P3H2x4x_CP0_VCCIO_LDO_VOLTAGE_MASK GENMASK(1, 0) +#define P3H2x4x_CP0_VCCIO_LDO_VOLTAGE(x) \ + FIELD_PREP(P3H2x4x_CP0_VCCIO_LDO_VOLTAGE_MASK, x) +#define P3H2x4x_CP1_VCCIO_LDO_VOLTAGE_MASK GENMASK(3, 2) +#define P3H2x4x_CP1_VCCIO_LDO_VOLTAGE(x) \ + FIELD_PREP(P3H2x4x_CP1_VCCIO_LDO_VOLTAGE_MASK, x) +#define P3H2x4x_TP0145_VCCIO_LDO_VOLTAGE_MASK GENMASK(5, 4) +#define P3H2x4x_TP0145_VCCIO_LDO_VOLTAGE(x) \ + FIELD_PREP(P3H2x4x_TP0145_VCCIO_LDO_VOLTAGE_MASK, x) +#define P3H2x4x_TP2367_VCCIO_LDO_VOLTAGE_MASK GENMASK(7, 6) +#define P3H2x4x_TP2367_VCCIO_LDO_VOLTAGE(x) \ + FIELD_PREP(P3H2x4x_TP2367_VCCIO_LDO_VOLTAGE_MASK, x) +#define P3H2x4x_LDO_COUNT 4 + +struct p3h2x4x_regulator_dev { + struct regulator_dev *rp3h2x4x_dev[P3H2x4x_LDO_COUNT]; + struct regmap *regmap; +}; + +static const struct regulator_ops p3h2x4x_ldo_ops = { + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_iterate, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + +static const unsigned int p3h2x4x_voltage_table[] = { + 1000000, + 1100000, + 1200000, + 1800000, +}; + +static struct regulator_desc p3h2x4x_regulators[] = { + { + .name = "cp0", + .of_match = of_match_ptr("cp0"), + .regulators_node = of_match_ptr("regulators"), + .volt_table = p3h2x4x_voltage_table, + .n_voltages = ARRAY_SIZE(p3h2x4x_voltage_table), + .ops = &p3h2x4x_ldo_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_reg = P3H2x4x_LDO_AND_PULLUP_CONF, + .enable_mask = P3H2x4x_CP0_EN_LDO, + .vsel_reg = P3H2x4x_VCCIO_LDO_CONF, + .vsel_mask = P3H2x4x_CP0_VCCIO_LDO_VOLTAGE_MASK, + }, + { + .name = "cp1", + .of_match = of_match_ptr("cp1"), + .regulators_node = of_match_ptr("regulators"), + .volt_table = p3h2x4x_voltage_table, + .n_voltages = ARRAY_SIZE(p3h2x4x_voltage_table), + .ops = &p3h2x4x_ldo_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_reg = P3H2x4x_LDO_AND_PULLUP_CONF, + .enable_mask = P3H2x4x_CP1_EN_LDO, + .vsel_reg = P3H2x4x_VCCIO_LDO_CONF, + .vsel_mask = P3H2x4x_CP1_VCCIO_LDO_VOLTAGE_MASK, + }, + { + .name = "tp0145", + .of_match = of_match_ptr("tp0145"), + .regulators_node = of_match_ptr("regulators"), + .volt_table = p3h2x4x_voltage_table, + .n_voltages = ARRAY_SIZE(p3h2x4x_voltage_table), + .ops = &p3h2x4x_ldo_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_reg = P3H2x4x_LDO_AND_PULLUP_CONF, + .enable_mask = P3H2x4x_TP0145_EN_LDO, + .vsel_reg = P3H2x4x_VCCIO_LDO_CONF, + .vsel_mask = P3H2x4x_TP0145_VCCIO_LDO_VOLTAGE_MASK, + }, + { + .name = "tp2367", + .of_match = of_match_ptr("tp2367"), + .regulators_node = of_match_ptr("regulators"), + .volt_table = p3h2x4x_voltage_table, + .n_voltages = ARRAY_SIZE(p3h2x4x_voltage_table), + .ops = &p3h2x4x_ldo_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_reg = P3H2x4x_LDO_AND_PULLUP_CONF, + .enable_mask = P3H2x4x_TP2367_EN_LDO, + .vsel_reg = P3H2x4x_VCCIO_LDO_CONF, + .vsel_mask = P3H2x4x_TP2367_VCCIO_LDO_VOLTAGE_MASK, + }, +}; + +int p3h2x4x_regulator_probe(struct platform_device *pdev) +{ + struct p3h2x4x_dev *p3h2x4x = dev_get_drvdata(pdev->dev.parent); + struct p3h2x4x_regulator_dev *p3h2x4x_regulator; + struct regulator_config rcfg = { }; + struct device *dev = &pdev->dev; + struct regulator_dev *rdev; + int ret, i; + + p3h2x4x_regulator = devm_kzalloc(dev, sizeof(*p3h2x4x_regulator), GFP_KERNEL); + if (!p3h2x4x_regulator) + return -ENOMEM; + + platform_set_drvdata(pdev, p3h2x4x_regulator); + + p3h2x4x_regulator->regmap = p3h2x4x->regmap; + device_set_of_node_from_dev(dev, dev->parent); + + rcfg.dev = dev; + rcfg.dev->of_node = dev->of_node; + rcfg.regmap = p3h2x4x_regulator->regmap; + rcfg.driver_data = p3h2x4x_regulator; + + for (i = 0; i < ARRAY_SIZE(p3h2x4x_regulators); i++) { + rdev = devm_regulator_register(&pdev->dev, &p3h2x4x_regulators[i], &rcfg); + if (IS_ERR(rdev)) { + ret = PTR_ERR(rdev); + dev_err(dev, "Failed to register %s\n", p3h2x4x_regulators[i].name); + return ret; + } + p3h2x4x_regulator->rp3h2x4x_dev[i] = rdev; + } + return 0; +} + +static struct platform_driver p3h2x4x_regulator_driver = { + .driver = { + .name = "p3h2x4x-regulator", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .probe = p3h2x4x_regulator_probe, +}; +module_platform_driver(p3h2x4x_regulator_driver); + +MODULE_AUTHOR("Aman Kumar Pandey <aman.kumarpandey@nxp.com>"); +MODULE_AUTHOR("vikash Bansal <vikash.bansal@nxp.com>"); +MODULE_DESCRIPTION("P3H2x4x I3C HUB driver"); +MODULE_LICENSE("GPL"); + diff --git a/include/linux/i3c/device.h b/include/linux/i3c/device.h index ef6217da8253..f43feda2fc09 100644 --- a/include/linux/i3c/device.h +++ b/include/linux/i3c/device.h @@ -77,6 +77,7 @@ struct i3c_priv_xfer { */ enum i3c_dcr { I3C_DCR_GENERIC_DEVICE = 0, + I3C_DCR_HUB = 194, }; #define I3C_PID_MANUF_ID(pid) (((pid) & GENMASK_ULL(47, 33)) >> 33) diff --git a/include/linux/mfd/p3h2840.h b/include/linux/mfd/p3h2840.h new file mode 100644 index 000000000000..4e3c737eb8a8 --- /dev/null +++ b/include/linux/mfd/p3h2840.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2025 NXP + * This header file contain private Reg address and its bit mapping etc. + */ + +#ifndef P3H2840_H +#define P3H2840_H + +#include <linux/types.h> + +/* Reg config for Regmap */ +#define P3H2x4x_REG_BITS 8 +#define P3H2x4x_VAL_BITS 8 + +struct p3h2x4x_dev { + struct i3c_device *i3cdev; + struct i2c_client *i2cdev; + struct regmap *regmap; + bool is_p3h2x4x_in_i3c; +}; +#endif /* P3H2840_H */ -- 2.25.1 ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v3 2/2] mfd: i3c: Add driver for NXP P3H2x4x i3c-hub device 2025-08-08 13:20 ` [PATCH v3 2/2] mfd: i3c: Add driver for NXP P3H2x4x i3c-hub device Aman Kumar Pandey @ 2025-08-08 20:38 ` Frank Li 2025-08-11 7:01 ` Krzysztof Kozlowski 1 sibling, 0 replies; 7+ messages in thread From: Frank Li @ 2025-08-08 20:38 UTC (permalink / raw) To: Aman Kumar Pandey Cc: linux-kernel, linux-i3c, alexandre.belloni, krzk+dt, robh, conor+dt, devicetree, broonie, lee, vikash.bansal, priyanka.jain, shashank.rebbapragada On Fri, Aug 08, 2025 at 04:20:33PM +0300, Aman Kumar Pandey wrote: > Add support for the NXP P3H2x4x (P3H2440/P3H2441/P3H2840/P3H2841) > family of multiport I3C hub devices. These hubs connect to a host > via I3C/I2C/SMBus and provide multiple downstream target ports. > > This driver adds the core support with separate drivers adding support > for I3C hub and regulators. > > The driver supports: > 1. I3C/I2C communication between host and hub > 2. Transparent I3C/I2C communication between host and downstream devices > 3. Configurable target ports (I2C/SMBus or I3C) > 4. MCTP device support > 5. In-band interrupt handling > 6. On-die regulator configuration > > P3H2440/P3H2441 support 4 target ports; > P3H2840/P3H2841 support 8 target ports. > > Signed-off-by: Aman Kumar Pandey <aman.kumarpandey@nxp.com> > Signed-off-by: Vikash Bansal <vikash.bansal@nxp.com> > > --- > V1 -> V2: Cleaned up coding style and addressed review comments > V2 -> V3: Added mfd device support for i3c hub and regulator. need re-run get_mainstainer for your patch, at least Mark Brown <broonie@kernel.org> is missed. > --- > MAINTAINERS | 1 + > drivers/i3c/Kconfig | 1 + > drivers/i3c/Makefile | 1 + > drivers/i3c/hub/Kconfig | 10 + > drivers/i3c/hub/Makefile | 4 + > drivers/i3c/hub/p3h2840_i3c_hub.h | 380 +++++++++ > drivers/i3c/hub/p3h2840_i3c_hub_common.c | 782 ++++++++++++++++++ > drivers/i3c/hub/p3h2840_i3c_hub_i3c.c | 345 ++++++++ > drivers/i3c/hub/p3h2840_i3c_hub_smbus.c | 627 ++++++++++++++ > drivers/mfd/Kconfig | 12 + > drivers/mfd/Makefile | 1 + > drivers/mfd/p3h2840.c | 129 +++ > drivers/regulator/Kconfig | 10 + > drivers/regulator/Makefile | 1 + > drivers/regulator/p3h2840_i3c_hub_regulator.c | 163 ++++ > include/linux/i3c/device.h | 1 + > include/linux/mfd/p3h2840.h | 22 + You need 3 patches 1: mfd related change 2: regulator change 3: i3c/hub change > 17 files changed, 2490 insertions(+) > create mode 100644 drivers/i3c/hub/Kconfig > create mode 100644 drivers/i3c/hub/Makefile > create mode 100644 drivers/i3c/hub/p3h2840_i3c_hub.h > create mode 100644 drivers/i3c/hub/p3h2840_i3c_hub_common.c > create mode 100644 drivers/i3c/hub/p3h2840_i3c_hub_i3c.c > create mode 100644 drivers/i3c/hub/p3h2840_i3c_hub_smbus.c > create mode 100644 drivers/mfd/p3h2840.c > create mode 100644 drivers/regulator/p3h2840_i3c_hub_regulator.c > create mode 100644 include/linux/mfd/p3h2840.h > > diff --git a/MAINTAINERS b/MAINTAINERS > index b33e90030188..220978128696 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -15484,6 +15484,7 @@ L: linux-kernel@vger.kernel.org > L: linux-i3c-owner@lists.infradead.org > S: Maintained > F: Documentation/devicetree/bindings/i3c/p3h2x4x_i3c_hub.yaml > +F: i3c/hub/* > > NZXT-KRAKEN2 HARDWARE MONITORING DRIVER > M: Jonas Malaco <jonas@protocubo.io> > diff --git a/drivers/i3c/Kconfig b/drivers/i3c/Kconfig > index 30a441506f61..254c50c3d33a 100644 > --- a/drivers/i3c/Kconfig > +++ b/drivers/i3c/Kconfig > @@ -21,4 +21,5 @@ menuconfig I3C > > if I3C > source "drivers/i3c/master/Kconfig" > +source "drivers/i3c/hub/Kconfig" > endif # I3C > diff --git a/drivers/i3c/Makefile b/drivers/i3c/Makefile > index 11982efbc6d9..ea01bdda938d 100644 > --- a/drivers/i3c/Makefile > +++ b/drivers/i3c/Makefile > @@ -2,3 +2,4 @@ > i3c-y := device.o master.o > obj-$(CONFIG_I3C) += i3c.o > obj-$(CONFIG_I3C) += master/ > +obj-$(CONFIG_I3C) += hub/ > diff --git a/drivers/i3c/hub/Kconfig b/drivers/i3c/hub/Kconfig > new file mode 100644 > index 000000000000..9d3e1a29e744 > --- /dev/null > +++ b/drivers/i3c/hub/Kconfig > @@ -0,0 +1,10 @@ > +# SPDX-License-Identifier: GPL-2.0 > +# Copyright 2025 NXP > +config P3H2X4X_I3C_HUB > + tristate "P3H2X4X I3C HUB support" > + depends on MFD_P3H2X4X > + help > + This enables support for NXP P3H244x/P3H284x I3C HUB. These hubs > + connect to a host via I3C/I2C/SMBus and allow communication with > + multiple downstream peripherals. The Say Y or M here to use I3C > + HUB driver to configure I3C HUB device. > diff --git a/drivers/i3c/hub/Makefile b/drivers/i3c/hub/Makefile > new file mode 100644 > index 000000000000..9dbd8a7b4184 > --- /dev/null > +++ b/drivers/i3c/hub/Makefile > @@ -0,0 +1,4 @@ > +# SPDX-License-Identifier: GPL-2.0 > +# Copyright 2025 NXP > +p3h2840_i3c_hub-y := p3h2840_i3c_hub_common.o p3h2840_i3c_hub_i3c.o p3h2840_i3c_hub_smbus.o > +obj-$(CONFIG_P3H2X4X_I3C_HUB) += p3h2840_i3c_hub.o > diff --git a/drivers/i3c/hub/p3h2840_i3c_hub.h b/drivers/i3c/hub/p3h2840_i3c_hub.h > new file mode 100644 > index 000000000000..bcd99004a844 > --- /dev/null > +++ b/drivers/i3c/hub/p3h2840_i3c_hub.h > @@ -0,0 +1,380 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* > + * Copyright 2025 NXP > + * This header file contain private device structure definition. > + */ > + > +#ifndef P3H2840_I3C_HUB_H > +#define P3H2840_I3C_HUB_H > + > +#include <linux/i2c.h> > +#include <linux/i3c/device.h> > +#include <linux/i3c/master.h> > +#include <linux/regulator/consumer.h> > +#include <linux/regmap.h> > + > +/* I3C HUB REGISTERS */ > + > +/* Device Information Registers */ > +#define P3H2x4x_DEV_INFO_0 0x00 > +#define P3H2x4x_DEV_INFO_1 0x01 > +#define P3H2x4x_PID_5 0x02 > +#define P3H2x4x_PID_4 0x03 > +#define P3H2x4x_PID_3 0x04 > +#define P3H2x4x_PID_2 0x05 > +#define P3H2x4x_PID_1 0x06 > +#define P3H2x4x_PID_0 0x07 > +#define P3H2x4x_BCR 0x08 > +#define P3H2x4x_DCR 0x09 > +#define P3H2x4x_DEV_CAPAB 0x0a > +#define P3H2x4x_DEV_REV 0x0b > + > +/* Device Configuration Registers */ > +#define P3H2x4x_DEV_REG_PROTECTION_CODE 0x10 > +#define P3H2x4x_REGISTERS_LOCK_CODE 0x00 > +#define P3H2x4x_REGISTERS_UNLOCK_CODE 0x69 > +#define P3H2x4x_CP1_REGISTERS_UNLOCK_CODE 0x6a > + > +#define P3H2x4x_CP_CONF 0x11 > +#define P3H2x4x_TP_ENABLE 0x12 > + > +#define P3H2x4x_DEV_CONF 0x13 > +#define P3H2x4x_IO_STRENGTH 0x14 > +#define P3H2x4x_TP0145_IO_STRENGTH_MASK GENMASK(1, 0) > +#define P3H2x4x_TP0145_IO_STRENGTH(x) \ > + FIELD_PREP(P3H2x4x_TP0145_IO_STRENGTH_MASK, x) > +#define P3H2x4x_TP2367_IO_STRENGTH_MASK GENMASK(3, 2) > +#define P3H2x4x_TP2367_IO_STRENGTH(x) \ > + FIELD_PREP(P3H2x4x_TP2367_IO_STRENGTH_MASK, x) > +#define P3H2x4x_CP0_IO_STRENGTH_MASK GENMASK(5, 4) > +#define P3H2x4x_CP0_IO_STRENGTH(x) \ > + FIELD_PREP(P3H2x4x_CP0_IO_STRENGTH_MASK, x) > +#define P3H2x4x_CP1_IO_STRENGTH_MASK GENMASK(7, 6) > +#define P3H2x4x_CP1_IO_STRENGTH(x) \ > + FIELD_PREP(P3H2x4x_CP1_IO_STRENGTH_MASK, x) > + > +#define P3H2x4x_TP_IO_MODE_CONF 0x17 > +#define P3H2x4x_TP_SMBUS_AGNT_EN 0x18 > + > +#define P3H2x4x_LDO_AND_PULLUP_CONF 0x19 > + > +#define P3H2x4x_TP0145_PULLUP_CONF_MASK GENMASK(7, 6) > +#define P3H2x4x_TP0145_PULLUP_CONF(x) \ > + FIELD_PREP(P3H2x4x_TP0145_PULLUP_CONF_MASK, x) > +#define P3H2x4x_TP2367_PULLUP_CONF_MASK GENMASK(5, 4) > +#define P3H2x4x_TP2367_PULLUP_CONF(x) \ > + FIELD_PREP(P3H2x4x_TP2367_PULLUP_CONF_MASK, x) > + > +#define P3H2x4x_CP_IBI_CONF 0x1a > + > +#define P3H2x4x_TP_SMBUS_AGNT_IBI_CONFIG 0x1b > +#define P3H2x4x_ALL_TP_IBI_EN 0xff > +#define P3H2x4x_ALL_TP_IBI_DIS 0x00 > + > +#define P3H2x4x_IBI_MDB_CUSTOM 0x1c > +#define P3H2x4x_JEDEC_CONTEXT_ID 0x1d > +#define P3H2x4x_TP_GPIO_MODE_EN 0x1e > + > +/* Device Status and IBI Registers */ > +#define P3H2x4x_DEV_AND_IBI_STS 0x20 > +#define P3H2x4x_TP_SMBUS_AGNT_IBI_STS 0x21 > +#define P3H2x4x_SMBUS_AGENT_EVENT_FLAG_STATUS BIT(4) > + > +/* Controller Port Control/Status Registers */ > +#define P3H2x4x_CP_MUX_SET 0x38 > +#define P3H2x4x_CONTROLLER_PORT_MUX_REQ BIT(0) > +#define P3H2x4x_CP_MUX_STS 0x39 > +#define P3H2x4x_CONTROLLER_PORT_MUX_CONNECTION_STATUS BIT(0) > + > +/* Target Ports Control Registers */ > +#define P3H2x4x_TP_SMBUS_AGNT_TRANS_START 0x50 > +#define P3H2x4x_TP_NET_CON_CONF 0x51 > + > +#define P3H2x4x_TP_PULLUP_EN 0x53 > + > +#define P3H2x4x_TP_SCL_OUT_EN 0x54 > +#define P3H2x4x_TP_SDA_OUT_EN 0x55 > +#define P3H2x4x_TP_SCL_OUT_LEVEL 0x56 > +#define P3H2x4x_TP_SDA_OUT_LEVEL 0x57 > +#define P3H2x4x_TP_IN_DETECT_MODE_CONF 0x58 > +#define P3H2x4x_TP_SCL_IN_DETECT_IBI_EN 0x59 > +#define P3H2x4x_TP_SDA_IN_DETECT_IBI_EN 0x5a > + > +/* Target Ports Status Registers */ > +#define P3H2x4x_TP_SCL_IN_LEVEL_STS 0x60 > +#define P3H2x4x_TP_SDA_IN_LEVEL_STS 0x61 > +#define P3H2x4x_TP_SCL_IN_DETECT_FLG 0x62 > +#define P3H2x4x_TP_SDA_IN_DETECT_FLG 0x63 > + > +/* SMBus Agent Configuration and Status Registers */ > +#define P3H2x4x_TP0_SMBUS_AGNT_STS 0x64 > +#define P3H2x4x_TP1_SMBUS_AGNT_STS 0x65 > +#define P3H2x4x_TP2_SMBUS_AGNT_STS 0x66 > +#define P3H2x4x_TP3_SMBUS_AGNT_STS 0x67 > +#define P3H2x4x_TP4_SMBUS_AGNT_STS 0x68 > +#define P3H2x4x_TP5_SMBUS_AGNT_STS 0x69 > +#define P3H2x4x_TP6_SMBUS_AGNT_STS 0x6a > +#define P3H2x4x_TP7_SMBUS_AGNT_STS 0x6b > +#define P3H2x4x_ONCHIP_TD_AND_SMBUS_AGNT_CONF 0x6c > + > +/* buf receive flag set */ > +#define P3H2x4x_TARGET_BUF_CA_TF BIT(0) > +#define P3H2x4x_TARGET_BUF_0_RECEIVE BIT(1) > +#define P3H2x4x_TARGET_BUF_1_RECEIVE BIT(2) > +#define P3H2x4x_TARGET_BUF_0_1_RECEIVE GENMASK(2, 1) > +#define P3H2x4x_TARGET_BUF_OVRFL GENMASK(3, 1) > +#define BUF_RECEIVED_FLAG_MASK GENMASK(3, 1) > +#define BUF_RECEIVED_FLAG_TF_MASK GENMASK(3, 0) > + > +#define P3H2x4x_TARGET_AGENT_LOCAL_DEV 0x11 > +#define P3H2x4x_TARGET_BUFF_0_PAGE 0x12 > +#define P3H2x4x_TARGET_BUFF_1_PAGE 0x13 > + > +/* Special Function Registers */ > +#define P3H2x4x_LDO_AND_CPSEL_STS 0x79 > +#define P3H2x4x_CP_SDA1_LEVEL BIT(7) > +#define P3H2x4x_CP_SCL1_LEVEL BIT(6) > + > +#define P3H2x4x_CP_SEL_PIN_INPUT_CODE_MASK GENMASK(5, 4) > +#define P3H2x4x_CP_SEL_PIN_INPUT_CODE_GET(x) \ > + (((x) & P3H2x4x_CP_SEL_PIN_INPUT_CODE_MASK) >> 4) > +#define P3H2x4x_CP_SDA1_SCL1_PINS_CODE_MASK GENMASK(7, 6) > +#define P3H2x4x_CP_SDA1_SCL1_PINS_CODE_GET(x) \ > + (((x) & P3H2x4x_CP_SDA1_SCL1_PINS_CODE_MASK) >> 6) > +#define P3H2x4x_VCCIO1_PWR_GOOD BIT(3) > +#define P3H2x4x_VCCIO0_PWR_GOOD BIT(2) > +#define P3H2x4x_CP1_VCCIO_PWR_GOOD BIT(1) > +#define P3H2x4x_CP0_VCCIO_PWR_GOOD BIT(0) > + > +#define P3H2x4x_BUS_RESET_SCL_TIMEOUT 0x7a > +#define P3H2x4x_ONCHIP_TD_PROTO_ERR_FLG 0x7b > +#define P3H2x4x_DEV_CMD 0x7c > +#define P3H2x4x_ONCHIP_TD_STS 0x7d > +#define P3H2x4x_ONCHIP_TD_ADDR_CONF 0x7e > +#define P3H2x4x_PAGE_PTR 0x7f > + > +/* Paged Transaction Registers */ > +#define P3H2x4x_CONTROLLER_BUFFER_PAGE 0x10 > +#define P3H2x4x_CONTROLLER_AGENT_BUFF 0x80 > +#define P3H2x4x_CONTROLLER_AGENT_BUFF_DATA 0x84 > + > +#define P3H2x4x_TARGET_BUFF_LENGTH 0x80 > +#define P3H2x4x_TARGET_BUFF_ADDRESS 0x81 > +#define P3H2x4x_TARGET_BUFF_DATA 0x82 > + > +#define P3H2x4x_TP_MAX_COUNT 0x08 > +#define P3H2x4x_CP_MAX_COUNT 0x02 > +#define P3H2x4x_TP_LOCAL_DEV 0x08 > + > +/* LDO Disable/Enable DT settings */ > +#define P3H2x4x_LDO_VOLT_1_0V 0x00 > +#define P3H2x4x_LDO_VOLT_1_1V 0x01 > +#define P3H2x4x_LDO_VOLT_1_2V 0x02 > +#define P3H2x4x_LDO_VOLT_1_8V 0x03 > +#define P3H2x4x_DT_LDO_VOLT_NOT_SET 0x04 > + > +#define P3H2x4x_LDO_DISABLED 0x00 > +#define P3H2x4x_LDO_ENABLED 0x01 > + > +#define P3H2x4x_IBI_DISABLED 0x00 > +#define P3H2x4x_IBI_ENABLED 0x01 > + > +/* Pullup selection DT settings */ > +#define P3H2x4x_TP_PULLUP_250R 0x00 > +#define P3H2x4x_TP_PULLUP_500R 0x01 > +#define P3H2x4x_TP_PULLUP_1000R 0x02 > +#define P3H2x4x_TP_PULLUP_2000R 0x03 > +#define P3H2x4x_TP_PULLUP_NOT_SET 0x04 > + > +#define P3H2x4x_TP_PULLUP_DISABLED 0x00 > +#define P3H2x4x_TP_PULLUP_ENABLED 0x01 > + > +#define P3H2x4x_IO_STRENGTH_20_OHM 0x00 > +#define P3H2x4x_IO_STRENGTH_30_OHM 0x01 > +#define P3H2x4x_IO_STRENGTH_40_OHM 0x02 > +#define P3H2x4x_IO_STRENGTH_50_OHM 0x03 > +#define P3H2x4x_IO_STRENGTH_NOT_SET 0x04 > + > +#define P3H2x4x_TP_MODE_I3C 0x00 > +#define P3H2x4x_TP_MODE_SMBUS 0x01 > +#define P3H2x4x_TP_MODE_GPIO 0x02 > +#define P3H2x4x_TP_MODE_I2C 0x03 > +#define P3H2x4x_TP_MODE_NOT_SET 0x04 > + > +#define ONE_BYTE_SIZE 0x01 > + > +/* holding SDA low when both SMBus Target Agent received data buffers are full. > + * This feature can be used as a flow-control mechanism for MCTP applications to > + * avoid MCTP transmitters on Target Ports time out when the SMBus agent buffers > + * are not serviced in time by upstream controller and only receives write message > + * from its downstream ports. > + * SMBUS_AGENT_TX_RX_LOOPBACK_EN/TARGET_AGENT_BUF_FULL_SDA_LOW_EN > + */ > + > +#define P3H2x4x_TARGET_AGENT_DFT_IBI_CONF 0x20 > +#define P3H2x4x_TARGET_AGENT_DFT_IBI_CONF_MASK 0x21 > + > +/* Transaction status checking mask */ > + > +#define P3H2x4x_SMBUS_TRANSACTION_FINISH_FLAG 1 > +#define P3H2x4x_SMBUS_CNTRL_STATUS_TXN_SHIFT 4 > + > +#define P3H2x4x_SMBUS_CNTRL_STATUS_TXN_OK 0 > +#define P3H2x4x_SMBUS_CNTRL_STATUS_TXN_ADDR_NAK 1 > +#define P3H2x4x_SMBUS_CNTRL_STATUS_TXN_DATA_NAK 2 > +#define P3H2x4x_SMBUS_CNTRL_STATUS_TXN_WTR_NAK 3 > +#define P3H2x4x_SMBUS_CNTRL_STATUS_TXN_SYNC_RCV 4 > +#define P3H2x4x_SMBUS_CNTRL_STATUS_TXN_SYNC_RCVCLR 5 > +#define P3H2x4x_SMBUS_CNTRL_STATUS_TXN_FAULT 6 > +#define P3H2x4x_SMBUS_CNTRL_STATUS_TXN_ARB_LOSS 7 > +#define P3H2x4x_SMBUS_CNTRL_STATUS_TXN_SCL_TO 8 > + > +#define P3H2x4x_TP_BUFFER_STATUS_MASK 0x0f > +#define P3H2x4x_TP_TRANSACTION_CODE_MASK 0xf0 > + > +/* SMBus transaction types fields */ > +#define P3H2x4x_SMBUS_400kHz BIT(2) > + > +/* SMBus polling */ > +#define P3H2x4x_POLLING_ROLL_PERIOD_MS 10 > + > +/* Hub buffer size */ > +#define P3H2x4x_CONTROLLER_BUFFER_SIZE 88 > +#define P3H2x4x_TARGET_BUFFER_SIZE 80 > +#define P3H2x4x_SMBUS_DESCRIPTOR_SIZE 4 > +#define P3H2x4x_SMBUS_PAYLOAD_SIZE \ > + (P3H2x4x_CONTROLLER_BUFFER_SIZE - P3H2x4x_SMBUS_DESCRIPTOR_SIZE) > +#define P3H2x4x_SMBUS_TARGET_PAYLOAD_SIZE (P3H2x4x_TARGET_BUFFER_SIZE - 2) > + > +/* Hub SMBus transaction time */ > +#define P3H2x4x_SMBUS_400kHz_TRANSFER_TIMEOUT(x) ((20 * (x)) + 80) > + > +#define P3H2x4x_NO_PAGE_PER_TP 4 > + > +#define P3H2x4x_MAX_PAYLOAD_LEN 2 > +#define P3H2x4x_NUM_SLOTS 6 > + > +#define P3H2x4x_HUB_ID 0 > + > +#define P3H2x4x_SET_BIT(n) BIT(n) > + > +enum p3h2x4x_tp { > + TP_0, > + TP_1, > + TP_2, > + TP_3, > + TP_4, > + TP_5, > + TP_6, > + TP_7, > +}; > + > +enum p3h2x4x_rcv_buf { > + RCV_BUF_0, > + RCV_BUF_1, > + RCV_BUF_OF, > +}; > + > +struct p3h2x4x_regulators { > + struct regulator *rcp0; > + struct regulator *rcp1; > + struct regulator *rtp0145; > + struct regulator *rtp2367; > +}; > + > +struct p3h2x4x_setting { > + const char *const name; > + const u8 value; > +}; > + > +struct tp_setting { > + bool pullup_en; > + bool ibi_en; > + bool always_enable; > + int mode; > +}; > + > +struct dt_settings { > + bool cp0_ldo_en; > + bool cp1_ldo_en; > + bool tp0145_ldo_en; > + bool tp2367_ldo_en; > + int cp0_ldo_volt; > + int cp1_ldo_volt; > + int tp0145_ldo_volt; > + int tp2367_ldo_volt; > + int tp0145_pullup; > + int tp2367_pullup; > + int cp0_io_strength; > + int cp1_io_strength; > + int tp0145_io_strength; > + int tp2367_io_strength; > + struct tp_setting tp[P3H2x4x_TP_MAX_COUNT]; > +}; > + > +struct smbus_device { > + struct i2c_client *client; > + const char *compatible; > + int addr; > + struct list_head list; > + struct device_node *tp_device_dt_node; > +}; > + > +struct tp_bus { > + bool is_registered; /* bus was registered in the framework. */ > + bool is_slave_registered; > + u8 tp_mask; > + u8 tp_port; > + u8 local_dev_count; > + u8 local_dev_list[P3H2x4x_TP_LOCAL_DEV]; > + struct i3c_master_controller i3c_port_controller; > + struct i2c_adapter *smbus_port_adapter; > + struct device_node *of_node; > + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub; > + struct list_head tp_device_entry; > + struct mutex port_mutex; /* per port mutex */ > +}; > + > +struct p3h2x4x_i3c_hub_dev { > + struct i3c_master_controller *driving_master; > + struct i3c_device *i3cdev; > + struct regmap *regmap; > + struct mutex etx_mutex; /* all port mutex */ > + struct dt_settings settings; > + struct tp_bus tp_bus[P3H2x4x_TP_MAX_COUNT]; > + struct p3h2x4x_regulators rp3h2x4x; > + /* Offset for reading HUB's register. */ > + u8 tp_ibi_mask; > + u8 tp_i3c_bus_mask; > + u8 tp_always_enable_mask; > + bool is_p3h2x4x_in_i3c; > + struct device *dev; > +}; > + > +/** > + * p3h2x4x_tp_smbus_algo - add i2c adapter for target port who > + * configured as SMBus. I remember need indentent to align - > + * @priv: p3h2x4x device structure. > + * @tp: target port. > + * Return: 0 in case of success, a negative EINVAL code if the error. > + */ > +int p3h2x4x_tp_smbus_algo(struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub, int tp); > + > +/** > + * p3h2x4x_tp_i3c_algo - register i3c controller for target port who > + * configured as I3C. > + * @priv: p3h2x4x device structure. > + * @tp: target port. > + * Return: 0 in case of success, a negative EINVAL code if the error. > + */ > +int p3h2x4x_tp_i3c_algo(struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub, int tp); > + > +/** > + * p3h2x4x_ibi_handler - IBI handler. > + * @i3cdev: i3c device. > + * @payload: two byte IBI payload data. > + */ > +void p3h2x4x_ibi_handler(struct i3c_device *i3cdev, > + const struct i3c_ibi_payload *payload); > +#endif /* P3H2840_I3C_HUB_H */ > + > diff --git a/drivers/i3c/hub/p3h2840_i3c_hub_common.c b/drivers/i3c/hub/p3h2840_i3c_hub_common.c > new file mode 100644 > index 000000000000..1ca887ad8e2b > --- /dev/null > +++ b/drivers/i3c/hub/p3h2840_i3c_hub_common.c > @@ -0,0 +1,782 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright 2025 NXP > + * This P3H2x4x driver file implements functions for Hub probe and DT parsing. > + */ > + > +#include <linux/of.h> > +#include <linux/of_address.h> > +#include <linux/platform_device.h> > +#include <linux/mfd/p3h2840.h> > + > +#include "p3h2840_i3c_hub.h" > + > +/* LDO voltage DT settings */ > +#define P3H2x4x_DT_LDO_VOLT_1_0V 1000 use tab to align 1000 with below lines > +#define P3H2x4x_DT_LDO_VOLT_1_1V 1100 > +#define P3H2x4x_DT_LDO_VOLT_1_2V 1200 > +#define P3H2x4x_DT_LDO_VOLT_1_8V 1800 > + > +/* target port pull-up settings */ > +#define P3H2x4x_DT_TP_PULLUP_250R 250 > +#define P3H2x4x_DT_TP_PULLUP_500R 500 > +#define P3H2x4x_DT_TP_PULLUP_1000R 1000 > +#define P3H2x4x_DT_TP_PULLUP_2000R 2000 > + > +/* IO strenght settings */ > +#define P3H2x4x_DT_IO_STRENGTH_20_OHM 20 > +#define P3H2x4x_DT_IO_STRENGTH_30_OHM 30 > +#define P3H2x4x_DT_IO_STRENGTH_40_OHM 40 > +#define P3H2x4x_DT_IO_STRENGTH_50_OHM 50 > + > +/* target port mode settings */ > +static const struct p3h2x4x_setting tp_mode_settings[] = { > + { "i3c", P3H2x4x_TP_MODE_I3C }, > + { "smbus", P3H2x4x_TP_MODE_SMBUS }, > + { "gpio", P3H2x4x_TP_MODE_GPIO }, > + { "i2c", P3H2x4x_TP_MODE_I2C }, > +}; > + > +static const struct i3c_ibi_setup p3h2x4x_ibireq = { > + .handler = p3h2x4x_ibi_handler, > + .max_payload_len = P3H2x4x_MAX_PAYLOAD_LEN, > + .num_slots = P3H2x4x_NUM_SLOTS, > +}; > + > +static void p3h2x4x_of_get_dt_setting(struct device *dev, > + const struct device_node *node, > + const char *setting_name, > + const struct p3h2x4x_setting settings[], > + const int settings_count, > + int *setting_value) > +{ > + const char *sval; > + int ret; > + int i; > + > + ret = of_property_read_string(node, setting_name, &sval); > + if (ret) { > + if (ret != -EINVAL) > + dev_warn(dev, "No setting or invalid setting for %s, err=%i\n", > + setting_name, ret); > + return; > + } > + > + for (i = 0; i < settings_count; ++i) { > + const struct p3h2x4x_setting *const setting = &settings[i]; > + > + if (!strcmp(setting->name, sval)) { > + *setting_value = setting->value; > + return; > + } > + } > + dev_warn(dev, "Unknown setting for %s\n", setting_name); > +} > + > +static u8 p3h2x4x_pullup_dt_to_reg(int dt_value) > +{ > + switch (dt_value) { > + case P3H2x4x_DT_TP_PULLUP_2000R: > + return P3H2x4x_TP_PULLUP_2000R; > + case P3H2x4x_DT_TP_PULLUP_1000R: > + return P3H2x4x_TP_PULLUP_1000R; > + case P3H2x4x_DT_TP_PULLUP_250R: > + return P3H2x4x_TP_PULLUP_250R; > + default: > + return P3H2x4x_TP_PULLUP_500R; > + } > +} > + > +static u8 p3h2x4x_io_strength_dt_to_reg(int dt_value) > +{ > + switch (dt_value) { > + case P3H2x4x_DT_IO_STRENGTH_50_OHM: > + return P3H2x4x_IO_STRENGTH_50_OHM; > + case P3H2x4x_DT_IO_STRENGTH_40_OHM: > + return P3H2x4x_IO_STRENGTH_40_OHM; > + case P3H2x4x_DT_IO_STRENGTH_30_OHM: > + return P3H2x4x_IO_STRENGTH_30_OHM; > + default: > + return P3H2x4x_IO_STRENGTH_20_OHM; > + } > +} > + > +static int p3h2x4x_configure_ldo(struct device *dev) > +{ > + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = dev_get_drvdata(dev); > + int ret; > + > + p3h2x4x_i3c_hub->rp3h2x4x.rcp0 = devm_regulator_get_optional(dev, "cp0"); > + if (IS_ERR(p3h2x4x_i3c_hub->rp3h2x4x.rcp0)) { > + p3h2x4x_i3c_hub->rp3h2x4x.rcp0 = NULL; > + dev_dbg(dev, "cp0-supply not found\n"); > + } > + > + p3h2x4x_i3c_hub->rp3h2x4x.rcp1 = devm_regulator_get_optional(dev, "cp1"); > + if (IS_ERR(p3h2x4x_i3c_hub->rp3h2x4x.rcp1)) { > + p3h2x4x_i3c_hub->rp3h2x4x.rcp1 = NULL; > + dev_dbg(dev, "cp1-supply not found\n"); > + } > + > + p3h2x4x_i3c_hub->rp3h2x4x.rtp0145 = devm_regulator_get_optional(dev, "tp0145"); > + if (IS_ERR(p3h2x4x_i3c_hub->rp3h2x4x.rtp0145)) { > + p3h2x4x_i3c_hub->rp3h2x4x.rtp0145 = NULL; > + dev_dbg(dev, "tp0145-supply not found\n"); > + } > + > + p3h2x4x_i3c_hub->rp3h2x4x.rtp2367 = devm_regulator_get_optional(dev, "tp2367"); > + if (IS_ERR(p3h2x4x_i3c_hub->rp3h2x4x.rtp2367)) { > + p3h2x4x_i3c_hub->rp3h2x4x.rtp2367 = NULL; > + dev_dbg(dev, "tp2367-supply not found\n"); > + } > + > + /* Set the regulators volatage */ > + if (p3h2x4x_i3c_hub->settings.cp0_ldo_volt != P3H2x4x_DT_LDO_VOLT_NOT_SET && > + p3h2x4x_i3c_hub->rp3h2x4x.rcp0) { > + ret = regulator_set_voltage(p3h2x4x_i3c_hub->rp3h2x4x.rcp0, > + p3h2x4x_i3c_hub->settings.cp0_ldo_volt, > + p3h2x4x_i3c_hub->settings.cp0_ldo_volt); > + if (ret) > + dev_warn(dev, "Failed to set CP0 voltage (ignoring)\n"); > + } > + > + /* Set the regulators volatage */ > + if (p3h2x4x_i3c_hub->settings.cp1_ldo_volt != P3H2x4x_DT_LDO_VOLT_NOT_SET && > + p3h2x4x_i3c_hub->rp3h2x4x.rcp1) { > + ret = regulator_set_voltage(p3h2x4x_i3c_hub->rp3h2x4x.rcp1, > + p3h2x4x_i3c_hub->settings.cp1_ldo_volt, > + p3h2x4x_i3c_hub->settings.cp1_ldo_volt); > + if (ret) > + dev_warn(dev, "Failed to set CP1 voltage (ignoring)\n"); > + } > + > + /* Set the regulators volatage */ > + if (p3h2x4x_i3c_hub->settings.tp0145_ldo_volt != P3H2x4x_DT_LDO_VOLT_NOT_SET && > + p3h2x4x_i3c_hub->rp3h2x4x.rtp0145) { > + ret = regulator_set_voltage(p3h2x4x_i3c_hub->rp3h2x4x.rtp0145, > + p3h2x4x_i3c_hub->settings.tp0145_ldo_volt, > + p3h2x4x_i3c_hub->settings.tp0145_ldo_volt); > + if (ret) > + dev_warn(dev, "Failed to set TP0145 voltage (ignoring)\n"); > + } > + > + /* Set the regulators volatage */ > + if (p3h2x4x_i3c_hub->settings.tp2367_ldo_volt != P3H2x4x_DT_LDO_VOLT_NOT_SET && > + p3h2x4x_i3c_hub->rp3h2x4x.rtp2367) { > + ret = regulator_set_voltage(p3h2x4x_i3c_hub->rp3h2x4x.rtp2367, > + p3h2x4x_i3c_hub->settings.tp2367_ldo_volt, > + p3h2x4x_i3c_hub->settings.tp2367_ldo_volt); > + if (ret) > + dev_warn(dev, "Failed to set TP2367 voltage (ignoring)\n"); > + } > + > + /* Enable the regulators */ > + if (p3h2x4x_i3c_hub->rp3h2x4x.rcp0) { > + ret = regulator_enable(p3h2x4x_i3c_hub->rp3h2x4x.rcp0); > + if (ret < 0) { > + dev_err(dev, "Failed to enable cp0 ldo\n"); > + return ret; > + } > + } > + > + if (p3h2x4x_i3c_hub->rp3h2x4x.rcp1) { > + ret = regulator_enable(p3h2x4x_i3c_hub->rp3h2x4x.rcp1); > + if (ret < 0) { > + dev_err(dev, "Failed to enable cp1 ldo\n"); > + return ret; > + } > + } > + > + if (p3h2x4x_i3c_hub->rp3h2x4x.rtp0145) { > + ret = regulator_enable(p3h2x4x_i3c_hub->rp3h2x4x.rtp0145); > + if (ret < 0) { > + dev_err(dev, "Failed to enable tp0145 ldo\n"); > + return ret; > + } > + } > + > + if (p3h2x4x_i3c_hub->rp3h2x4x.rtp2367) { > + ret = regulator_enable(p3h2x4x_i3c_hub->rp3h2x4x.rtp2367); > + if (ret < 0) { > + dev_err(dev, "Failed to enable tp2367 ldo\n"); Do you need disable previous enabled regulator? > + return ret; > + } > + } > + return 0; > +} > + > +static int p3h2x4x_configure_pullup(struct device *dev) > +{ > + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = dev_get_drvdata(dev); > + u8 mask_all = 0, val_all = 0; > + > + if (p3h2x4x_i3c_hub->settings.tp0145_pullup != P3H2x4x_TP_PULLUP_NOT_SET) { > + val_all |= P3H2x4x_TP0145_PULLUP_CONF(p3h2x4x_pullup_dt_to_reg > + (p3h2x4x_i3c_hub->settings.tp0145_pullup)); > + mask_all |= P3H2x4x_TP0145_PULLUP_CONF_MASK; > + } > + > + if (p3h2x4x_i3c_hub->settings.tp2367_pullup != P3H2x4x_TP_PULLUP_NOT_SET) { > + val_all |= P3H2x4x_TP2367_PULLUP_CONF(p3h2x4x_pullup_dt_to_reg > + (p3h2x4x_i3c_hub->settings.tp2367_pullup)); > + mask_all |= P3H2x4x_TP2367_PULLUP_CONF_MASK; > + } > + > + return regmap_update_bits(p3h2x4x_i3c_hub->regmap, P3H2x4x_LDO_AND_PULLUP_CONF, > + mask_all, val_all); > +} > + > +static int p3h2x4x_configure_io_strength(struct device *dev) > +{ > + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = dev_get_drvdata(dev); > + u8 mask_all = 0, val_all = 0; > + > + if (p3h2x4x_i3c_hub->settings.cp0_io_strength != P3H2x4x_IO_STRENGTH_NOT_SET) { > + val_all |= P3H2x4x_CP0_IO_STRENGTH(p3h2x4x_io_strength_dt_to_reg > + (p3h2x4x_i3c_hub->settings.cp0_io_strength)); > + mask_all |= P3H2x4x_CP0_IO_STRENGTH_MASK; > + } > + > + if (p3h2x4x_i3c_hub->settings.cp1_io_strength != P3H2x4x_IO_STRENGTH_NOT_SET) { > + val_all |= P3H2x4x_CP1_IO_STRENGTH(p3h2x4x_io_strength_dt_to_reg > + (p3h2x4x_i3c_hub->settings.cp1_io_strength)); > + mask_all |= P3H2x4x_CP1_IO_STRENGTH_MASK; > + } > + > + if (p3h2x4x_i3c_hub->settings.tp0145_io_strength != P3H2x4x_IO_STRENGTH_NOT_SET) { > + val_all |= P3H2x4x_TP0145_IO_STRENGTH(p3h2x4x_io_strength_dt_to_reg > + (p3h2x4x_i3c_hub->settings.tp0145_io_strength)); > + mask_all |= P3H2x4x_TP0145_IO_STRENGTH_MASK; > + } > + > + if (p3h2x4x_i3c_hub->settings.tp2367_io_strength != P3H2x4x_IO_STRENGTH_NOT_SET) { > + val_all |= P3H2x4x_TP2367_IO_STRENGTH(p3h2x4x_io_strength_dt_to_reg > + (p3h2x4x_i3c_hub->settings.tp2367_io_strength)); > + mask_all |= P3H2x4x_TP2367_IO_STRENGTH_MASK; > + } > + > + return regmap_update_bits(p3h2x4x_i3c_hub->regmap, P3H2x4x_IO_STRENGTH, mask_all, val_all); > +} > + > +static int p3h2x4x_configure_tp(struct device *dev) > +{ > + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = dev_get_drvdata(dev); > + u8 pullup_val = 0, ibi_val = 0; > + u8 smbus_val = 0, i2c_val = 0; > + u8 gpio_val = 0, i3c_val = 0; > + u8 i, mode_mask = 0; > + int ret; > + > + /* TBD: Read type of HUB from register P3H2x4x_DEV_INFO_0 to learn target ports count. */ > + for (i = 0; i < P3H2x4x_TP_MAX_COUNT; ++i) { > + if (p3h2x4x_i3c_hub->settings.tp[i].mode != P3H2x4x_TP_MODE_NOT_SET) { > + if (p3h2x4x_i3c_hub->settings.tp[i].mode == P3H2x4x_TP_MODE_I3C) > + i3c_val |= P3H2x4x_SET_BIT(i); > + else if (p3h2x4x_i3c_hub->settings.tp[i].mode == P3H2x4x_TP_MODE_SMBUS) > + smbus_val |= P3H2x4x_SET_BIT(i); > + else if (p3h2x4x_i3c_hub->settings.tp[i].mode == P3H2x4x_TP_MODE_GPIO) > + gpio_val |= P3H2x4x_SET_BIT(i); > + else if (p3h2x4x_i3c_hub->settings.tp[i].mode == P3H2x4x_TP_MODE_I2C) > + i2c_val |= P3H2x4x_SET_BIT(i); > + > + mode_mask |= P3H2x4x_SET_BIT(i); > + } > + if (p3h2x4x_i3c_hub->settings.tp[i].pullup_en) > + pullup_val |= P3H2x4x_SET_BIT(i); > + > + if (p3h2x4x_i3c_hub->settings.tp[i].ibi_en) > + ibi_val |= P3H2x4x_SET_BIT(i); > + } > + > + ret = regmap_update_bits(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP_IO_MODE_CONF, > + mode_mask, (smbus_val | i2c_val)); > + if (ret) > + return ret; > + > + ret = regmap_update_bits(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP_PULLUP_EN, > + pullup_val, pullup_val); > + if (ret) > + return ret; > + > + ret = regmap_update_bits(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP_SMBUS_AGNT_IBI_CONFIG, > + ibi_val, ibi_val); > + if (ret) > + return ret; > + p3h2x4x_i3c_hub->tp_ibi_mask = ibi_val; > + > + ret = regmap_update_bits(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP_SMBUS_AGNT_EN, > + mode_mask, smbus_val); > + if (ret) > + return ret; > + > + ret = regmap_update_bits(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP_GPIO_MODE_EN, > + mode_mask, gpio_val); > + if (ret) > + return ret; > + > + /* Request for HUB Network connection in case any TP is configured in I3C mode */ > + if ((i3c_val) || (i2c_val)) { > + ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_CP_MUX_SET, > + P3H2x4x_CONTROLLER_PORT_MUX_REQ); > + if (ret) > + return ret; > + } > + > + /* Enable TP here in case TP was configured */ > + ret = regmap_update_bits(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP_ENABLE, > + mode_mask, (i3c_val | smbus_val | gpio_val | i2c_val)); > + if (ret) > + return ret; > + > + return 0; > +} > + > +static int p3h2x4x_configure_smbus_local_device(struct device *dev) > +{ > + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = dev_get_drvdata(dev); > + u8 target_buffer_page, hub_tp; > + int ret = 0; > + > + for (hub_tp = 0; hub_tp < P3H2x4x_TP_MAX_COUNT; hub_tp++) { > + if (p3h2x4x_i3c_hub->settings.tp[hub_tp].mode == P3H2x4x_TP_MODE_SMBUS && > + p3h2x4x_i3c_hub->tp_bus[hub_tp].local_dev_count) { > + target_buffer_page = P3H2x4x_TARGET_AGENT_LOCAL_DEV + 4 * hub_tp; > + ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_PAGE_PTR, > + target_buffer_page); > + if (ret) { > + dev_err(dev, "Failed to configure local device settings\n"); > + break; > + } > + > + ret = regmap_bulk_write(p3h2x4x_i3c_hub->regmap, > + P3H2x4x_CONTROLLER_AGENT_BUFF, > + p3h2x4x_i3c_hub->tp_bus[hub_tp].local_dev_list, > + p3h2x4x_i3c_hub->tp_bus[hub_tp].local_dev_count); > + if (ret) { > + dev_err(dev, "Failed to add local devices\n"); > + break; > + } > + } > + } > + regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_PAGE_PTR, 0x00); > + return ret; > +} > + > +static int p3h2x4x_configure_hw(struct device *dev) > +{ > + int ret; > + > + ret = p3h2x4x_configure_ldo(dev); > + if (ret) > + return ret; > + > + ret = p3h2x4x_configure_io_strength(dev); > + if (ret) > + return ret; > + > + ret = p3h2x4x_configure_pullup(dev); > + if (ret) > + return ret; > + > + ret = p3h2x4x_configure_smbus_local_device(dev); > + if (ret) > + return ret; > + > + return p3h2x4x_configure_tp(dev); > +} > + > +static void p3h2x4x_of_get_tp_dt_conf(struct device *dev, > + const struct device_node *node) > +{ > + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = dev_get_drvdata(dev); > + struct device_node *dev_node; > + u64 tp_port; > + > + for_each_available_child_of_node(node, dev_node) { > + if (of_property_read_reg(dev_node, 0, &tp_port, NULL)) > + continue; > + > + if (tp_port < P3H2x4x_TP_MAX_COUNT) { > + p3h2x4x_i3c_hub->tp_bus[tp_port].of_node = dev_node; > + p3h2x4x_i3c_hub->tp_bus[tp_port].tp_mask = BIT(tp_port); > + p3h2x4x_i3c_hub->tp_bus[tp_port].p3h2x4x_i3c_hub = p3h2x4x_i3c_hub; > + p3h2x4x_i3c_hub->tp_bus[tp_port].tp_port = tp_port; > + } > + } > +} > + > +/* return true when backend node exist */ > +static bool p3h2x4x_is_backend_node_exist(int port, > + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub, > + u32 addr) > +{ > + struct smbus_device *backend = NULL; > + > + list_for_each_entry(backend, > + &p3h2x4x_i3c_hub->tp_bus[port].tp_device_entry, list) { > + if (backend->addr == addr) > + return true; > + } > + return false; > +} > + > +static int p3h2x4x_read_backend_from_dts(struct device_node *i3c_node_target, > + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub) > +{ > + struct device_node *tp_node; > + const char *compatible; > + u64 tp_port, addr_dts; > + int ret; > + > + struct smbus_device *backend; > + > + if (of_property_read_reg(i3c_node_target, 0, &tp_port, NULL)) > + return -EINVAL; > + > + if (tp_port >= P3H2x4x_TP_MAX_COUNT || tp_port < 0) > + return -ERANGE; > + > + INIT_LIST_HEAD(&p3h2x4x_i3c_hub->tp_bus[tp_port].tp_device_entry); > + > + if (p3h2x4x_i3c_hub->settings.tp[tp_port].mode == P3H2x4x_TP_MODE_I3C) > + return 0; > + > + for_each_available_child_of_node(i3c_node_target, tp_node) { > + ret = of_property_read_reg(tp_node, 0, &addr_dts, NULL); > + if (ret) > + return ret; > + > + if (p3h2x4x_is_backend_node_exist(tp_port, p3h2x4x_i3c_hub, addr_dts)) > + continue; > + > + ret = of_property_read_string(tp_node, "compatible", &compatible); > + if (ret) > + return ret; > + > + backend = kzalloc(sizeof(*backend), GFP_KERNEL); > + if (!backend) > + return -ENOMEM; > + > + backend->addr = addr_dts; > + backend->compatible = compatible; > + backend->tp_device_dt_node = tp_node; > + backend->client = NULL; > + > + list_add(&backend->list, > + &p3h2x4x_i3c_hub->tp_bus[tp_port].tp_device_entry); > + } > + > + return 0; > +} > + > +static void p3h2x4x_parse_dt_tp(struct device *dev, > + const struct device_node *i3c_node_hub, > + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub) > +{ > + struct device_node *i3c_node_target; > + int ret; > + > + for_each_available_child_of_node(i3c_node_hub, i3c_node_target) { > + ret = p3h2x4x_read_backend_from_dts(i3c_node_target, p3h2x4x_i3c_hub); > + if (ret) > + dev_warn(dev, "Skiping DTS node %s", i3c_node_target->name); > + } > +} > + > +static int p3h2x4x_get_tp_local_device_dt_setting(struct device *dev, > + const struct device_node *node, u32 id) > +{ > + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = dev_get_drvdata(dev); > + int ret; > + > + ret = of_property_read_variable_u8_array(node, "local-dev", > + p3h2x4x_i3c_hub->tp_bus[id].local_dev_list, > + sizeof(u8), P3H2x4x_TP_LOCAL_DEV); > + if (ret > 0 && ret <= P3H2x4x_TP_LOCAL_DEV) > + p3h2x4x_i3c_hub->tp_bus[id].local_dev_count = ret; > + else if (ret == -EOVERFLOW) > + dev_warn(dev, > + "local Devices list is out of range or invalid\n"); > + > + return ret; > +} > + > +static void p3h2x4x_get_tp_of_get_setting(struct device *dev, > + const struct device_node *node, > + struct tp_setting tp_setting[]) > +{ > + struct device_node *tp_node; > + u64 id; > + > + for_each_available_child_of_node(node, tp_node) { for_each_available_child_of_node_scoped() otherwise need call of_put_node() > + if (of_property_read_reg(tp_node, 0, &id, NULL)) > + continue; > + > + if (id >= P3H2x4x_TP_MAX_COUNT) { > + dev_warn(dev, "Invalid target port index found in DT: %lli\n", id); > + continue; > + } > + > + p3h2x4x_of_get_dt_setting(dev, tp_node, "mode", tp_mode_settings, > + ARRAY_SIZE(tp_mode_settings), > + &tp_setting[id].mode); > + > + tp_setting[id].pullup_en = > + of_property_read_bool(tp_node, "pullup-enable"); > + tp_setting[id].ibi_en = > + of_property_read_bool(tp_node, "ibi-enable"); > + tp_setting[id].always_enable = > + of_property_read_bool(tp_node, "hub_bridge_en"); > + > + p3h2x4x_get_tp_local_device_dt_setting(dev, tp_node, id); > + } > +} > + > +static void p3h2x4x_of_get_p3h2x4x_conf(struct device *dev, > + const struct device_node *node) > +{ > + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = dev_get_drvdata(dev); > + > + p3h2x4x_i3c_hub->settings.cp0_ldo_en = > + of_property_read_bool(node, "cp0-ldo-enable"); where use cp0_ldo_en? after change to regulator driver, cp0_ldo_en is not needned. > + p3h2x4x_i3c_hub->settings.cp1_ldo_en = > + of_property_read_bool(node, "cp1-ldo-enable"); > + p3h2x4x_i3c_hub->settings.tp0145_ldo_en = > + of_property_read_bool(node, "tp0145-ldo-enable"); > + p3h2x4x_i3c_hub->settings.tp2367_ldo_en = > + of_property_read_bool(node, "tp2367-ldo-enable"); > + > + of_property_read_u32(node, "cp0-ldo-microvolt", > + &p3h2x4x_i3c_hub->settings.cp0_ldo_volt); > + of_property_read_u32(node, "cp1-ldo-microvolt", > + &p3h2x4x_i3c_hub->settings.cp1_ldo_volt); > + of_property_read_u32(node, "tp0145-ldo-microvolt", > + &p3h2x4x_i3c_hub->settings.tp0145_ldo_volt); > + of_property_read_u32(node, "tp2367-ldo-microvolt", > + &p3h2x4x_i3c_hub->settings.tp2367_ldo_volt); > + of_property_read_u32(node, "tp0145-pullup-ohms", > + &p3h2x4x_i3c_hub->settings.tp0145_pullup); > + of_property_read_u32(node, "tp2367-pullup-ohms", > + &p3h2x4x_i3c_hub->settings.tp2367_pullup); > + of_property_read_u32(node, "cp0-io-strength-ohms", > + &p3h2x4x_i3c_hub->settings.cp0_io_strength); > + of_property_read_u32(node, "cp1-io-strength-ohms", > + &p3h2x4x_i3c_hub->settings.cp1_io_strength); > + of_property_read_u32(node, "tp0145-io-strength-ohms", > + &p3h2x4x_i3c_hub->settings.tp0145_io_strength); > + of_property_read_u32(node, "tp2367-io-strength-ohms", > + &p3h2x4x_i3c_hub->settings.tp2367_io_strength); > + > + p3h2x4x_get_tp_of_get_setting(dev, node, p3h2x4x_i3c_hub->settings.tp); > +} > + > +static void p3h2x4x_of_default_configuration(struct device *dev) > +{ > + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = dev_get_drvdata(dev); > + int tp_count; > + > + p3h2x4x_i3c_hub->settings.cp0_ldo_volt = P3H2x4x_DT_LDO_VOLT_NOT_SET; > + p3h2x4x_i3c_hub->settings.cp1_ldo_volt = P3H2x4x_DT_LDO_VOLT_NOT_SET; > + p3h2x4x_i3c_hub->settings.tp0145_ldo_volt = P3H2x4x_DT_LDO_VOLT_NOT_SET; > + p3h2x4x_i3c_hub->settings.tp2367_ldo_volt = P3H2x4x_DT_LDO_VOLT_NOT_SET; > + p3h2x4x_i3c_hub->settings.tp0145_pullup = P3H2x4x_TP_PULLUP_NOT_SET; > + p3h2x4x_i3c_hub->settings.tp2367_pullup = P3H2x4x_TP_PULLUP_NOT_SET; > + p3h2x4x_i3c_hub->settings.cp0_io_strength = P3H2x4x_IO_STRENGTH_NOT_SET; > + p3h2x4x_i3c_hub->settings.cp1_io_strength = P3H2x4x_IO_STRENGTH_NOT_SET; > + p3h2x4x_i3c_hub->settings.tp0145_io_strength = P3H2x4x_IO_STRENGTH_NOT_SET; > + p3h2x4x_i3c_hub->settings.tp2367_io_strength = P3H2x4x_IO_STRENGTH_NOT_SET; > + > + for (tp_count = 0; tp_count < P3H2x4x_TP_MAX_COUNT; ++tp_count) > + p3h2x4x_i3c_hub->settings.tp[tp_count].mode = P3H2x4x_TP_MODE_NOT_SET; > +} > + > +int p3h2x4x_i3c_hub_probe(struct platform_device *pdev) > +{ > + struct p3h2x4x_dev *p3h2x4x = dev_get_drvdata(pdev->dev.parent); > + struct device_node *node __free(device_node) = NULL; you just cp pointer from dev->of_node, needn't __free(device_node) > + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub; > + struct device *dev = &pdev->dev; > + int ret, i; > + > + p3h2x4x_i3c_hub = devm_kzalloc(dev, sizeof(*p3h2x4x_i3c_hub), GFP_KERNEL); > + if (!p3h2x4x_i3c_hub) > + return -ENOMEM; > + > + platform_set_drvdata(pdev, p3h2x4x_i3c_hub); > + > + p3h2x4x_i3c_hub->regmap = p3h2x4x->regmap; > + > + if (p3h2x4x->is_p3h2x4x_in_i3c) { > + p3h2x4x_i3c_hub->i3cdev = p3h2x4x->i3cdev; > + p3h2x4x_i3c_hub->driving_master = i3c_dev_get_master(p3h2x4x->i3cdev->desc); > + } > + > + p3h2x4x_i3c_hub->is_p3h2x4x_in_i3c = p3h2x4x->is_p3h2x4x_in_i3c; > + p3h2x4x_i3c_hub->dev = dev; > + > + device_set_of_node_from_dev(dev, dev->parent); > + > + p3h2x4x_of_default_configuration(dev); > + mutex_init(&p3h2x4x_i3c_hub->etx_mutex); > + > + for (i = 0; i < P3H2x4x_TP_MAX_COUNT; i++) > + mutex_init(&p3h2x4x_i3c_hub->tp_bus[i].port_mutex); > + > + /* get hub node from DT */ > + node = dev->of_node; > + if (!node) { > + dev_dbg(dev, "No DT entry - running with hardware defaults.\n"); how it is possible? > + } else { > + p3h2x4x_of_get_p3h2x4x_conf(dev, node); > + p3h2x4x_of_get_tp_dt_conf(dev, node); > + /* Parse DTS to find backend device on the SMBus target mode */ > + p3h2x4x_parse_dt_tp(dev, node, p3h2x4x_i3c_hub); > + } > + > + /* Unlock access to protected registers */ > + ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_DEV_REG_PROTECTION_CODE, > + P3H2x4x_REGISTERS_UNLOCK_CODE); > + if (ret) > + return dev_err_probe(dev, ret, "Failed to unlock HUB's protected registers\n"); > + > + ret = p3h2x4x_configure_hw(dev); > + if (ret) > + return dev_err_probe(dev, ret, "Failed to configure the HUB\n"); > + > + if (p3h2x4x->is_p3h2x4x_in_i3c) { > + /* Register logic for native vertual I3C ports */ > + for (i = 0; i < P3H2x4x_TP_MAX_COUNT; i++) { > + if (p3h2x4x_i3c_hub->settings.tp[i].mode == P3H2x4x_TP_MODE_I3C && > + !p3h2x4x_i3c_hub->settings.tp[i].always_enable) { > + ret = p3h2x4x_tp_i3c_algo(p3h2x4x_i3c_hub, i); > + if (ret) > + return dev_err_probe(dev, ret, > + "failed to register i3c bus tp %d\n", > + i); > + } > + } > + } > + > + for (i = 0; i < P3H2x4x_TP_MAX_COUNT; ++i) { > + if (p3h2x4x_i3c_hub->settings.tp[i].always_enable) > + p3h2x4x_i3c_hub->tp_always_enable_mask = > + (p3h2x4x_i3c_hub->tp_always_enable_mask | BIT(i)); > + } > + > + ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP_NET_CON_CONF, > + p3h2x4x_i3c_hub->tp_always_enable_mask); > + if (ret) > + return dev_err_probe(dev, ret, "Failed to open Target Port(s)\n"); > + > + if (p3h2x4x->is_p3h2x4x_in_i3c) { > + ret = i3c_master_do_daa(p3h2x4x_i3c_hub->driving_master); > + if (ret) > + dev_warn(dev, "Failed to run DAA\n"); > + > + /* > + * holding SDA low when both SMBus Target Agent received data buffers are full. > + * This feature can be used as a flow-control mechanism for MCTP applications to > + * avoid MCTP transmitters on Target Ports time out when the SMBus agent buffers > + * are not serviced in time by upstream controller and only receives write message > + * from its downstream ports. > + */ > + ret = regmap_update_bits(p3h2x4x_i3c_hub->regmap, > + P3H2x4x_ONCHIP_TD_AND_SMBUS_AGNT_CONF, > + P3H2x4x_TARGET_AGENT_DFT_IBI_CONF_MASK, > + P3H2x4x_TARGET_AGENT_DFT_IBI_CONF); > + if (ret) > + return dev_err_probe(dev, ret, "Failed to data buffer full config\n"); > + > + ret = i3c_device_request_ibi(p3h2x4x->i3cdev, &p3h2x4x_ibireq); > + if (ret) > + return dev_err_probe(dev, ret, "Failed to request IBI\n"); > + > + ret = i3c_device_enable_ibi(p3h2x4x->i3cdev); > + if (ret) > + return dev_err_probe(dev, ret, "Failed to Enable IBI\n"); > + } > + > + /* Register logic for native SMBus ports */ > + for (i = 0; i < P3H2x4x_TP_MAX_COUNT; i++) { > + if (p3h2x4x_i3c_hub->settings.tp[i].mode == P3H2x4x_TP_MODE_SMBUS) { > + ret = p3h2x4x_tp_smbus_algo(p3h2x4x_i3c_hub, i); > + if (ret) > + return dev_err_probe(dev, ret, > + "failed to add i2c adapter for tp %d\n", i); > + } > + } > + > + /* Lock access to protected registers */ > + ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_DEV_REG_PROTECTION_CODE, > + P3H2x4x_REGISTERS_LOCK_CODE); > + if (ret) > + return dev_err_probe(dev, ret, "Failed to lock HUB's protected registers\n"); > + > + return 0; > +} > + > +int p3h2x4x_i3c_hub_remove(struct platform_device *pdev) > +{ > + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = platform_get_drvdata(pdev); > + struct p3h2x4x_dev *p3h2x4x = dev_get_drvdata(pdev->dev.parent); > + struct i3c_master_controller *tp_controller; > + struct smbus_device *backend = NULL; > + struct i2c_adapter *tp_adap; > + u8 i; > + > + if (p3h2x4x_i3c_hub->rp3h2x4x.rcp0) > + regulator_disable(p3h2x4x_i3c_hub->rp3h2x4x.rcp0); > + > + if (p3h2x4x_i3c_hub->rp3h2x4x.rcp1) > + regulator_disable(p3h2x4x_i3c_hub->rp3h2x4x.rcp1); > + > + if (p3h2x4x_i3c_hub->rp3h2x4x.rtp0145) > + regulator_disable(p3h2x4x_i3c_hub->rp3h2x4x.rtp0145); > + > + if (p3h2x4x_i3c_hub->rp3h2x4x.rtp2367) > + regulator_disable(p3h2x4x_i3c_hub->rp3h2x4x.rtp2367); > + > + for (i = 0; i < P3H2x4x_TP_MAX_COUNT; i++) { > + tp_adap = p3h2x4x_i3c_hub->tp_bus[i].smbus_port_adapter; > + tp_controller = &p3h2x4x_i3c_hub->tp_bus[i].i3c_port_controller; > + > + if (p3h2x4x_i3c_hub->tp_bus[i].is_registered) { > + if (p3h2x4x_i3c_hub->settings.tp[i].mode == P3H2x4x_TP_MODE_SMBUS) { > + list_for_each_entry(backend, > + &p3h2x4x_i3c_hub->tp_bus[i].tp_device_entry, > + list) { > + i2c_unregister_device(backend->client); > + kfree(backend); > + } > + i2c_del_adapter(tp_adap); > + } else if (p3h2x4x_i3c_hub->settings.tp[i].mode == P3H2x4x_TP_MODE_I3C) { > + i3c_master_unregister(tp_controller); > + } > + } > + } > + if (p3h2x4x->is_p3h2x4x_in_i3c) { > + i3c_device_disable_ibi(p3h2x4x->i3cdev); > + i3c_device_free_ibi(p3h2x4x->i3cdev); > + } > + > + mutex_destroy(&p3h2x4x_i3c_hub->etx_mutex); > + for (i = 0; i < P3H2x4x_TP_MAX_COUNT; i++) > + mutex_destroy(&p3h2x4x_i3c_hub->tp_bus[i].port_mutex); > + > + return 0; > +} > + > +static struct platform_driver p3h2x4x_i3c_hub_driver = { > + .driver = { > + .name = "p3h2x4x-i3c-hub", > + .probe_type = PROBE_PREFER_ASYNCHRONOUS, > + }, > + .probe = p3h2x4x_i3c_hub_probe, > + .remove = p3h2x4x_i3c_hub_remove, > +}; > +module_platform_driver(p3h2x4x_i3c_hub_driver); > + > +MODULE_AUTHOR("Aman Kumar Pandey <aman.kumarpandey@nxp.com>"); > +MODULE_AUTHOR("vikash Bansal <vikash.bansal@nxp.com>"); > +MODULE_DESCRIPTION("P3H2x4x I3C HUB driver"); > +MODULE_LICENSE("GPL"); > diff --git a/drivers/i3c/hub/p3h2840_i3c_hub_i3c.c b/drivers/i3c/hub/p3h2840_i3c_hub_i3c.c > new file mode 100644 > index 000000000000..8903bc48a88c > --- /dev/null > +++ b/drivers/i3c/hub/p3h2840_i3c_hub_i3c.c > @@ -0,0 +1,345 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright 2025 NXP > + * This P3H2x4x driver file contain functions for I3C virtual Bus creation, connect/disconnect > + * hub network and read/write. > + */ > +#include <linux/mfd/p3h2840.h> > +#include <linux/regmap.h> > + > +#include "p3h2840_i3c_hub.h" > + > +static void p3h2x4x_en_p3h2x4x_ntwk_tp(struct tp_bus *bus) > +{ > + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = bus->p3h2x4x_i3c_hub; > + > + if (p3h2x4x_i3c_hub->settings.tp[bus->tp_port].always_enable) > + return; > + > + regmap_set_bits(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP_NET_CON_CONF, bus->tp_mask); > +} > + > +static void p3h2x4x_dis_p3h2x4x_ntwk_tp(struct tp_bus *bus) > +{ > + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = bus->p3h2x4x_i3c_hub; > + > + if (p3h2x4x_i3c_hub->settings.tp[bus->tp_port].always_enable) > + return; > + > + regmap_clear_bits(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP_NET_CON_CONF, bus->tp_mask); > +} > + > +static struct tp_bus *p3h2x4x_bus_from_i3c_desc(struct i3c_dev_desc *desc) > +{ > + struct i3c_master_controller *controller = i3c_dev_get_master(desc); > + > + return container_of(controller, struct tp_bus, i3c_port_controller); > +} > + > +static struct tp_bus *p3h2x4x_bus_from_i2c_desc(struct i2c_dev_desc *desc) > +{ > + struct i3c_master_controller *controller = i2c_dev_get_master(desc); > + > + return container_of(controller, struct tp_bus, i3c_port_controller); > +} > + > +static struct i3c_master_controller > +*get_parent_controller(struct i3c_master_controller *controller) > +{ > + struct tp_bus *bus = container_of(controller, struct tp_bus, i3c_port_controller); > + > + return bus->p3h2x4x_i3c_hub->driving_master; > +} > + > +static struct i3c_master_controller > +*get_parent_controller_from_i3c_desc(struct i3c_dev_desc *desc) > +{ > + struct i3c_master_controller *controller = i3c_dev_get_master(desc); > + struct tp_bus *bus = container_of(controller, struct tp_bus, i3c_port_controller); > + > + return bus->p3h2x4x_i3c_hub->driving_master; > +} > + > +static struct i3c_master_controller > +*get_parent_controller_from_i2c_desc(struct i2c_dev_desc *desc) > +{ > + struct i3c_master_controller *controller = desc->common.master; > + struct tp_bus *bus = container_of(controller, struct tp_bus, i3c_port_controller); > + > + return bus->p3h2x4x_i3c_hub->driving_master; > +} > + > +static struct i3c_master_controller > +*update_i3c_i2c_desc_parent(struct i3c_i2c_dev_desc *desc, > + struct i3c_master_controller *parent) > +{ > + struct i3c_master_controller *orig_parent = desc->master; > + > + desc->master = parent; > + > + return orig_parent; > +} > + > +static void restore_i3c_i2c_desc_parent(struct i3c_i2c_dev_desc *desc, > + struct i3c_master_controller *parent) > +{ > + desc->master = parent; > +} > + > +static int p3h2x4x_i3c_bus_init(struct i3c_master_controller *controller) > +{ > + struct tp_bus *bus = container_of(controller, struct tp_bus, i3c_port_controller); > + > + controller->this = bus->p3h2x4x_i3c_hub->i3cdev->desc; > + return 0; > +} > + > +static void p3h2x4x_i3c_bus_cleanup(struct i3c_master_controller *controller) > +{ > + controller->this = NULL; > +} > + > +static int p3h2x4x_attach_i3c_dev(struct i3c_dev_desc *dev) > +{ > + struct i3c_master_controller *parent = get_parent_controller_from_i3c_desc(dev); > + struct i3c_master_controller *orig_parent; > + int ret; > + > + orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent); > + ret = parent->ops->attach_i3c_dev(dev); > + restore_i3c_i2c_desc_parent(&dev->common, orig_parent); > + return ret; > +} > + > +static int p3h2x4x_reattach_i3c_dev(struct i3c_dev_desc *dev, u8 old_dyn_addr) > +{ > + struct i3c_master_controller *parent = get_parent_controller_from_i3c_desc(dev); > + struct i3c_master_controller *orig_parent; > + int ret; > + > + orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent); > + ret = parent->ops->reattach_i3c_dev(dev, old_dyn_addr); > + restore_i3c_i2c_desc_parent(&dev->common, orig_parent); > + return ret; > +} > + > +static void p3h2x4x_detach_i3c_dev(struct i3c_dev_desc *dev) > +{ > + struct i3c_master_controller *parent = get_parent_controller_from_i3c_desc(dev); > + struct i3c_master_controller *orig_parent; > + > + orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent); > + parent->ops->detach_i3c_dev(dev); > + restore_i3c_i2c_desc_parent(&dev->common, orig_parent); > +} > + > +static int p3h2x4x_do_daa(struct i3c_master_controller *controller) > +{ > + struct tp_bus *bus = container_of(controller, struct tp_bus, i3c_port_controller); > + struct i3c_master_controller *parent = get_parent_controller(controller); > + > + int ret; > + > + p3h2x4x_en_p3h2x4x_ntwk_tp(bus); > + down_write(&parent->bus.lock); > + ret = parent->ops->do_daa(parent); > + up_write(&parent->bus.lock); > + p3h2x4x_dis_p3h2x4x_ntwk_tp(bus); > + > + return ret; > +} > + > +static bool p3h2x4x_supports_ccc_cmd(struct i3c_master_controller *controller, > + const struct i3c_ccc_cmd *cmd) > +{ > + struct i3c_master_controller *parent = get_parent_controller(controller); > + > + return parent->ops->supports_ccc_cmd(parent, cmd); > +} > + > +static int p3h2x4x_send_ccc_cmd(struct i3c_master_controller *controller, > + struct i3c_ccc_cmd *cmd) > +{ > + struct tp_bus *bus = container_of(controller, struct tp_bus, i3c_port_controller); > + struct i3c_master_controller *parent = get_parent_controller(controller); > + int ret; > + > + if (cmd->id == I3C_CCC_RSTDAA(true)) > + return 0; > + > + p3h2x4x_en_p3h2x4x_ntwk_tp(bus); > + ret = parent->ops->send_ccc_cmd(parent, cmd); > + p3h2x4x_dis_p3h2x4x_ntwk_tp(bus); > + > + return ret; > +} > + > +static int p3h2x4x_priv_xfers(struct i3c_dev_desc *dev, > + struct i3c_priv_xfer *xfers, int nxfers) > +{ > + struct i3c_master_controller *parent = get_parent_controller_from_i3c_desc(dev); > + struct tp_bus *bus = p3h2x4x_bus_from_i3c_desc(dev); > + struct i3c_master_controller *orig_parent; > + int res; > + > + p3h2x4x_en_p3h2x4x_ntwk_tp(bus); > + orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent); > + res = parent->ops->priv_xfers(dev, xfers, nxfers); > + restore_i3c_i2c_desc_parent(&dev->common, orig_parent); > + p3h2x4x_dis_p3h2x4x_ntwk_tp(bus); > + > + return res; > +} > + > +static int p3h2x4x_attach_i2c_dev(struct i2c_dev_desc *dev) > +{ > + struct i3c_master_controller *parent = get_parent_controller_from_i2c_desc(dev); > + struct i3c_master_controller *orig_parent; > + int ret; > + > + orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent); > + ret = parent->ops->attach_i2c_dev(dev); > + restore_i3c_i2c_desc_parent(&dev->common, orig_parent); > + return ret; > +} > + > +static void p3h2x4x_detach_i2c_dev(struct i2c_dev_desc *dev) > +{ > + struct i3c_master_controller *parent = get_parent_controller_from_i2c_desc(dev); > + struct i3c_master_controller *orig_parent; > + > + orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent); > + parent->ops->detach_i2c_dev(dev); > + restore_i3c_i2c_desc_parent(&dev->common, orig_parent); > +} > + > +static int p3h2x4x_i2c_xfers(struct i2c_dev_desc *dev, > + const struct i2c_msg *xfers, int nxfers) > +{ > + struct i3c_master_controller *parent = get_parent_controller_from_i2c_desc(dev); > + struct tp_bus *bus = p3h2x4x_bus_from_i2c_desc(dev); > + struct i3c_master_controller *orig_parent; > + int ret; > + > + p3h2x4x_en_p3h2x4x_ntwk_tp(bus); > + orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent); > + ret = parent->ops->i2c_xfers(dev, xfers, nxfers); > + restore_i3c_i2c_desc_parent(&dev->common, orig_parent); > + p3h2x4x_dis_p3h2x4x_ntwk_tp(bus); > + return ret; > +} > + > +static int p3h2x4x_request_ibi(struct i3c_dev_desc *dev, > + const struct i3c_ibi_setup *req) > +{ > + struct i3c_master_controller *parent = get_parent_controller_from_i3c_desc(dev); > + struct tp_bus *bus = p3h2x4x_bus_from_i3c_desc(dev); > + struct i3c_master_controller *orig_parent; > + int ret; > + > + p3h2x4x_en_p3h2x4x_ntwk_tp(bus); > + orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent); > + ret = parent->ops->request_ibi(dev, req); > + restore_i3c_i2c_desc_parent(&dev->common, orig_parent); > + p3h2x4x_dis_p3h2x4x_ntwk_tp(bus); > + return ret; > +} > + > +static void p3h2x4x_free_ibi(struct i3c_dev_desc *dev) > +{ > + struct i3c_master_controller *parent = get_parent_controller_from_i3c_desc(dev); > + struct tp_bus *bus = p3h2x4x_bus_from_i3c_desc(dev); > + struct i3c_master_controller *orig_parent; > + > + p3h2x4x_en_p3h2x4x_ntwk_tp(bus); > + orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent); > + parent->ops->free_ibi(dev); > + restore_i3c_i2c_desc_parent(&dev->common, orig_parent); > + p3h2x4x_dis_p3h2x4x_ntwk_tp(bus); > +} > + > +static int p3h2x4x_enable_ibi(struct i3c_dev_desc *dev) > +{ > + struct i3c_master_controller *parent = get_parent_controller_from_i3c_desc(dev); > + struct tp_bus *bus = p3h2x4x_bus_from_i3c_desc(dev); > + struct i3c_master_controller *orig_parent; > + int ret; > + > + p3h2x4x_en_p3h2x4x_ntwk_tp(bus); > + orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent); > + ret = parent->ops->enable_ibi(dev); > + restore_i3c_i2c_desc_parent(&dev->common, orig_parent); > + p3h2x4x_dis_p3h2x4x_ntwk_tp(bus); > + return ret; > +} > + > +static int p3h2x4x_disable_ibi(struct i3c_dev_desc *dev) > +{ > + struct i3c_master_controller *parent = get_parent_controller_from_i3c_desc(dev); > + struct tp_bus *bus = p3h2x4x_bus_from_i3c_desc(dev); > + struct i3c_master_controller *orig_parent; > + int ret; > + > + p3h2x4x_en_p3h2x4x_ntwk_tp(bus); > + orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent); > + ret = parent->ops->disable_ibi(dev); > + restore_i3c_i2c_desc_parent(&dev->common, orig_parent); > + p3h2x4x_dis_p3h2x4x_ntwk_tp(bus); > + return ret; > +} > + > +static void p3h2x4x_recycle_ibi_slot(struct i3c_dev_desc *dev, > + struct i3c_ibi_slot *slot) > +{ > + struct i3c_master_controller *parent = get_parent_controller_from_i3c_desc(dev); > + struct i3c_master_controller *orig_parent; > + > + orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent); > + parent->ops->recycle_ibi_slot(dev, slot); > + restore_i3c_i2c_desc_parent(&dev->common, orig_parent); > +} > + > +static const struct i3c_master_controller_ops p3h2x4x_i3c_ops = { > + .bus_init = p3h2x4x_i3c_bus_init, > + .bus_cleanup = p3h2x4x_i3c_bus_cleanup, > + .attach_i3c_dev = p3h2x4x_attach_i3c_dev, > + .reattach_i3c_dev = p3h2x4x_reattach_i3c_dev, > + .detach_i3c_dev = p3h2x4x_detach_i3c_dev, > + .do_daa = p3h2x4x_do_daa, > + .supports_ccc_cmd = p3h2x4x_supports_ccc_cmd, > + .send_ccc_cmd = p3h2x4x_send_ccc_cmd, > + .priv_xfers = p3h2x4x_priv_xfers, > + .attach_i2c_dev = p3h2x4x_attach_i2c_dev, > + .detach_i2c_dev = p3h2x4x_detach_i2c_dev, > + .i2c_xfers = p3h2x4x_i2c_xfers, > + .request_ibi = p3h2x4x_request_ibi, > + .free_ibi = p3h2x4x_free_ibi, > + .enable_ibi = p3h2x4x_enable_ibi, > + .disable_ibi = p3h2x4x_disable_ibi, > + .recycle_ibi_slot = p3h2x4x_recycle_ibi_slot, > +}; > + > +/** > + * p3h2x4x_tp_i3c_algo - register i3c master for target port who > + * configured as i3c. > + * @p3h2x4x_i3c_hub: p3h2x4x device structure. > + * @tp: target port. > + * > + * Return: 0 in case of success, a negative EINVAL code if the error. > + */ > +int p3h2x4x_tp_i3c_algo(struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub, int tp) > +{ > + int ret; > + > + p3h2x4x_i3c_hub->dev->of_node = p3h2x4x_i3c_hub->tp_bus[tp].of_node; > + ret = i3c_master_register(&p3h2x4x_i3c_hub->tp_bus[tp].i3c_port_controller, > + p3h2x4x_i3c_hub->dev, &p3h2x4x_i3c_ops, false); > + if (ret) { > + dev_warn(p3h2x4x_i3c_hub->dev, > + "Failed to register i3c controller for port:%i\n", tp); > + return ret; > + } > + p3h2x4x_i3c_hub->tp_bus[tp].is_registered = true; > + > + return 0; > +} > diff --git a/drivers/i3c/hub/p3h2840_i3c_hub_smbus.c b/drivers/i3c/hub/p3h2840_i3c_hub_smbus.c > new file mode 100644 > index 000000000000..3416cbfd5675 > --- /dev/null > +++ b/drivers/i3c/hub/p3h2840_i3c_hub_smbus.c > @@ -0,0 +1,627 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright 2025 NXP > + * This P3H2x4x driver file contain functions for SMBus/I2C virtual Bus creation and read/write. > + */ > +#include <linux/mfd/p3h2840.h> > +#include <linux/regmap.h> > + > +#include "p3h2840_i3c_hub.h" > + > +#if IS_ENABLED(CONFIG_I2C_SLAVE) > +static void p3h2x4x_read_smbus_agent_rx_buf(struct i3c_device *i3cdev, enum p3h2x4x_rcv_buf rfbuf, > + enum p3h2x4x_tp tp, bool is_of) > +{ > + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = dev_get_drvdata(&i3cdev->dev); > + u8 slave_rx_buffer[P3H2x4x_SMBUS_TARGET_PAYLOAD_SIZE] = { 0 }; > + u8 target_buffer_page, flag_clear = 0x0f, temp, i; > + struct smbus_device *backend = NULL; > + u32 packet_len, slave_address, ret; > + > + target_buffer_page = (((rfbuf) ? P3H2x4x_TARGET_BUFF_1_PAGE : P3H2x4x_TARGET_BUFF_0_PAGE) > + + (P3H2x4x_NO_PAGE_PER_TP * tp)); > + ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_PAGE_PTR, target_buffer_page); > + if (ret) > + goto ibi_err; > + > + /* read buffer length */ > + ret = regmap_read(p3h2x4x_i3c_hub->regmap, P3H2x4x_TARGET_BUFF_LENGTH, &packet_len); > + if (ret) > + goto ibi_err; > + > + if (packet_len) > + packet_len = packet_len - 1; > + > + if (packet_len > P3H2x4x_SMBUS_TARGET_PAYLOAD_SIZE) { > + dev_err(&i3cdev->dev, "Received message too big for p3h2x4x buffer\n"); > + return; > + } > + > + /* read slave address */ > + ret = regmap_read(p3h2x4x_i3c_hub->regmap, P3H2x4x_TARGET_BUFF_ADDRESS, &slave_address); > + if (ret) > + goto ibi_err; > + > + /* read data */ > + if (packet_len) { > + ret = regmap_bulk_read(p3h2x4x_i3c_hub->regmap, P3H2x4x_TARGET_BUFF_DATA, > + slave_rx_buffer, packet_len); > + if (ret) > + goto ibi_err; > + } > + > + if (is_of) > + flag_clear = BUF_RECEIVED_FLAG_TF_MASK; > + else > + flag_clear = (((rfbuf == RCV_BUF_0) ? P3H2x4x_TARGET_BUF_0_RECEIVE : > + P3H2x4x_TARGET_BUF_1_RECEIVE)); > + > + /* notify slave driver about received data */ > + list_for_each_entry(backend, &p3h2x4x_i3c_hub->tp_bus[tp].tp_device_entry, list) { > + if (p3h2x4x_i3c_hub->tp_bus[tp].is_slave_registered && > + slave_address >> 1 == backend->addr) { > + i2c_slave_event(backend->client, I2C_SLAVE_WRITE_REQUESTED, > + (u8 *)&slave_address); > + for (i = 0; i < packet_len; i++) { > + temp = slave_rx_buffer[i]; > + i2c_slave_event(backend->client, I2C_SLAVE_WRITE_RECEIVED, > + &temp); > + } > + i2c_slave_event(backend->client, I2C_SLAVE_STOP, &temp); > + break; > + } > + } > + > +ibi_err: > + regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_PAGE_PTR, 0x00); > + regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP0_SMBUS_AGNT_STS + tp, flag_clear); > +} > +#endif > + > +/** > + * p3h2x4x_ibi_handler - IBI handler. > + * @i3cdev: i3c device. > + * @payload: two byte IBI payload data. > + * > + */ > +void p3h2x4x_ibi_handler(struct i3c_device *i3cdev, > + const struct i3c_ibi_payload *payload) > +{ > + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = dev_get_drvdata(&i3cdev->dev); > + u32 target_port_status, payload_byte_one, payload_byte_two; > + u32 ret, i; > + > + payload_byte_one = (*(int *)payload->data); > + payload_byte_two = (*(int *)(payload->data + 4)); > + > + if (!(payload_byte_one & P3H2x4x_SMBUS_AGENT_EVENT_FLAG_STATUS)) > + goto err; > + > +#if IS_ENABLED(CONFIG_I2C_SLAVE) > + guard(mutex)(&p3h2x4x_i3c_hub->etx_mutex); > + ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP_SMBUS_AGNT_IBI_CONFIG, > + P3H2x4x_ALL_TP_IBI_DIS); > + if (ret) { > + dev_err(&i3cdev->dev, "Failed to Disable IBI\n"); > + goto err; > + } > + > + for (i = 0; i < P3H2x4x_TP_MAX_COUNT; ++i) { > + if (p3h2x4x_i3c_hub->tp_bus[i].is_registered && (payload_byte_two >> i) & 0x01) { > + ret = regmap_read(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP0_SMBUS_AGNT_STS + i, > + &target_port_status); > + if (ret) { > + dev_err(&i3cdev->dev, "target port read status failed %d\n", ret); > + goto err; > + } > + > + /* process data receive buffer */ > + switch (target_port_status & BUF_RECEIVED_FLAG_MASK) { > + case P3H2x4x_TARGET_BUF_CA_TF: > + break; > + case P3H2x4x_TARGET_BUF_0_RECEIVE: > + p3h2x4x_read_smbus_agent_rx_buf(i3cdev, RCV_BUF_0, i, false); > + break; > + case P3H2x4x_TARGET_BUF_1_RECEIVE: > + p3h2x4x_read_smbus_agent_rx_buf(i3cdev, RCV_BUF_1, i, false); > + break; > + case P3H2x4x_TARGET_BUF_0_1_RECEIVE: > + p3h2x4x_read_smbus_agent_rx_buf(i3cdev, RCV_BUF_0, i, false); > + p3h2x4x_read_smbus_agent_rx_buf(i3cdev, RCV_BUF_1, i, false); > + break; > + case P3H2x4x_TARGET_BUF_OVRFL: > + p3h2x4x_read_smbus_agent_rx_buf(i3cdev, RCV_BUF_0, i, false); > + p3h2x4x_read_smbus_agent_rx_buf(i3cdev, RCV_BUF_1, i, true); > + dev_err(&i3cdev->dev, "Overflow, reading buffer zero and one\n"); > + break; > + default: > + regmap_write(p3h2x4x_i3c_hub->regmap, > + P3H2x4x_TP0_SMBUS_AGNT_STS + i, > + BUF_RECEIVED_FLAG_TF_MASK); > + break; > + } > + } > + } > +#endif > +err: > + regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP_SMBUS_AGNT_IBI_CONFIG, > + p3h2x4x_i3c_hub->tp_ibi_mask); > +} > + > +static int p3h2x4x_read_smbus_transaction_status(struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub, > + u8 target_port_status, > + u8 data_length) > +{ > + u32 status_read; > + int ret; > + > + mutex_unlock(&p3h2x4x_i3c_hub->etx_mutex); > + fsleep(P3H2x4x_SMBUS_400kHz_TRANSFER_TIMEOUT(data_length)); why need sleep here? > + mutex_lock(&p3h2x4x_i3c_hub->etx_mutex); > + > + ret = regmap_read(p3h2x4x_i3c_hub->regmap, target_port_status, &status_read); > + if (ret) > + return ret; > + > + if (status_read & P3H2x4x_SMBUS_TRANSACTION_FINISH_FLAG) { > + switch (status_read >> P3H2x4x_SMBUS_CNTRL_STATUS_TXN_SHIFT) { > + case P3H2x4x_SMBUS_CNTRL_STATUS_TXN_OK: > + ret = 0; > + break; > + case P3H2x4x_SMBUS_CNTRL_STATUS_TXN_ADDR_NAK: > + ret = -ENXIO; > + break; > + case P3H2x4x_SMBUS_CNTRL_STATUS_TXN_DATA_NAK: > + ret = -EIO; > + break; > + case P3H2x4x_SMBUS_CNTRL_STATUS_TXN_ARB_LOSS: > + ret = -EAGAIN; > + break; > + case P3H2x4x_SMBUS_CNTRL_STATUS_TXN_SCL_TO: > + ret = -ETIMEDOUT; > + break; > + case P3H2x4x_SMBUS_CNTRL_STATUS_TXN_WTR_NAK: > + ret = -ENXIO; > + break; > + default: > + ret = -EIO; > + break; > + } > + } > + return ret; > +} > + > +/** > + * p3h2x4x_tp_i2c_xfer_msg() - This starts a SMBus write transaction by writing a descriptor > + * and a message to the p3h2x4x registers. Controller buffer page is determined by multiplying the > + * target port index by four and adding the base page number to it. > + */ > +static int p3h2x4x_tp_i2c_xfer_msg(struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub, > + struct i2c_msg *xfers, > + u8 target_port, > + u8 nxfers_i, u8 rw) > +{ > + u8 controller_buffer_page = P3H2x4x_CONTROLLER_BUFFER_PAGE + 4 * target_port; > + u8 target_port_code = p3h2x4x_i3c_hub->tp_bus[target_port].tp_mask; > + u8 target_port_status = P3H2x4x_TP0_SMBUS_AGNT_STS + target_port; > + u8 desc[P3H2x4x_SMBUS_DESCRIPTOR_SIZE] = { 0 }; > + u8 transaction_type = P3H2x4x_SMBUS_400kHz; > + int write_length = xfers[nxfers_i].len; > + int read_length = xfers[nxfers_i].len; > + u8 addr = xfers[nxfers_i].addr; > + u8 rw_address = 2 * addr; > + int ret; > + > + if (rw) { > + rw_address |= BIT(0); > + write_length = 0; > + } else { > + read_length = 0; > + } > + > + desc[0] = rw_address; > + desc[1] = transaction_type; > + desc[2] = write_length; > + desc[3] = read_length; > + > + ret = regmap_write(p3h2x4x_i3c_hub->regmap, target_port_status, > + P3H2x4x_TP_BUFFER_STATUS_MASK); > + if (ret) > + return ret; > + > + ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_PAGE_PTR, controller_buffer_page); > + if (ret) > + return ret; > + > + ret = regmap_bulk_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_CONTROLLER_AGENT_BUFF, > + desc, P3H2x4x_SMBUS_DESCRIPTOR_SIZE); > + if (ret) > + return ret; > + > + if (!rw) { > + ret = regmap_bulk_write(p3h2x4x_i3c_hub->regmap, > + P3H2x4x_CONTROLLER_AGENT_BUFF_DATA, > + xfers[nxfers_i].buf, xfers[nxfers_i].len); > + if (ret) > + return ret; > + } > + > + ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP_SMBUS_AGNT_TRANS_START, > + target_port_code); > + if (ret) > + return ret; > + > + ret = p3h2x4x_read_smbus_transaction_status(p3h2x4x_i3c_hub, target_port_status, > + (write_length + read_length)); > + if (ret) > + return ret; > + > + if (rw) { > + ret = regmap_bulk_read(p3h2x4x_i3c_hub->regmap, > + P3H2x4x_CONTROLLER_AGENT_BUFF_DATA, > + xfers[nxfers_i].buf, xfers[nxfers_i].len); > + if (ret) > + return ret; > + } > + > + ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_PAGE_PTR, 0x00); > + if (ret) > + return ret; > + > + return 0; > +} > + > +/* > + * This function will be called whenever you call I2C read, write APIs like > + * i2c_master_send(), i2c_master_recv() etc. > + */ > +static s32 p3h2x4x_tp_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) > +{ > + int ret_sum = 0, ret; > + u8 msg_count, rw; > + > + struct tp_bus *bus = i2c_get_adapdata(adap); > + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = bus->p3h2x4x_i3c_hub; > + > + guard(mutex)(&p3h2x4x_i3c_hub->etx_mutex); > + guard(mutex)(&bus->port_mutex); > + > + if (p3h2x4x_i3c_hub->is_p3h2x4x_in_i3c) { > + ret = i3c_device_disable_ibi(p3h2x4x_i3c_hub->i3cdev); > + if (ret) { > + dev_err(p3h2x4x_i3c_hub->dev, "Failed to disable IBI, ret :- %d\n", ret); > + return ret; > + } > + } > + > + for (msg_count = 0; msg_count < num; msg_count++) { > + if (msgs[msg_count].len > P3H2x4x_SMBUS_PAYLOAD_SIZE) { > + dev_err(p3h2x4x_i3c_hub->dev, > + "Message nr. %d not sent - length over %d bytes.\n", > + msg_count, P3H2x4x_SMBUS_PAYLOAD_SIZE); > + continue; > + } > + rw = msgs[msg_count].flags % 2; > + > + ret = p3h2x4x_tp_i2c_xfer_msg(p3h2x4x_i3c_hub, > + msgs, > + bus->tp_port, > + msg_count, rw); > + > + if (ret) > + goto error; > + > + ret_sum++; > + } > + > +error: > + if (p3h2x4x_i3c_hub->is_p3h2x4x_in_i3c) { > + ret = i3c_device_enable_ibi(p3h2x4x_i3c_hub->i3cdev); > + if (ret) { > + dev_err(p3h2x4x_i3c_hub->dev, "Failed to enable IBI, ret %d\n", ret); > + return ret; > + } > + } > + return ret_sum; > +} > + > +static int p3h2x4x_tp_smbus_xfer_msg(struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub, > + u8 target_port, > + u8 addr, > + u8 rw, > + u8 cmd, > + int sz, > + union i2c_smbus_data *data) > +{ > + u8 controller_buffer_page = P3H2x4x_CONTROLLER_BUFFER_PAGE + 4 * target_port; > + u8 target_port_code = p3h2x4x_i3c_hub->tp_bus[target_port].tp_mask; > + u8 target_port_status = P3H2x4x_TP0_SMBUS_AGNT_STS + target_port; > + u8 desc[P3H2x4x_SMBUS_DESCRIPTOR_SIZE] = { 0 }; > + u8 transaction_type = P3H2x4x_SMBUS_400kHz; > + u8 buf[I2C_SMBUS_BLOCK_MAX + 2] = {0}; > + u8 read_length = 0, write_length = 0; > + u8 rw_address = 2 * addr; > + int ret, i; > + > + /* Map the size to what the chip understands */ > + switch (sz) { > + case I2C_SMBUS_QUICK: > + case I2C_SMBUS_BYTE: > + if (rw) { > + buf[0] = data->byte; > + read_length = ONE_BYTE_SIZE; > + write_length = 0; > + rw_address |= BIT(0); > + } else { > + buf[0] = cmd; > + write_length = ONE_BYTE_SIZE; > + read_length = 0; > + } > + break; > + case I2C_SMBUS_BYTE_DATA: > + if (rw) { /* read write */ > + buf[0] = cmd; > + write_length = ONE_BYTE_SIZE; > + read_length = ONE_BYTE_SIZE; > + transaction_type |= BIT(0); > + } else { /* only write */ > + buf[0] = cmd; > + buf[1] = data->byte; > + write_length = ONE_BYTE_SIZE + 1; > + read_length = 0; > + } > + break; > + case I2C_SMBUS_WORD_DATA: > + if (rw) { /* read write */ > + buf[0] = cmd; > + write_length = ONE_BYTE_SIZE; > + read_length = 2; > + transaction_type |= BIT(0); > + } else { /* only write */ > + buf[0] = cmd; > + buf[1] = data->word & 0xff; > + buf[2] = (data->word & 0xff00) >> 8; > + write_length = ONE_BYTE_SIZE + 2; > + read_length = 0; > + } > + break; > + case I2C_SMBUS_BLOCK_DATA: > + if (rw) { /* read write */ > + buf[0] = cmd; > + write_length = ONE_BYTE_SIZE; > + read_length = data->block[0] + 1; > + transaction_type |= BIT(0); > + } else { /* only write */ > + buf[0] = cmd; > + for (i = 0 ; i <= data->block[0]; i++) > + buf[i + 1] = data->block[i]; > + > + write_length = data->block[0] + 2; > + read_length = 0; > + } > + break; > + case I2C_SMBUS_I2C_BLOCK_DATA: > + if (rw) { /* read write */ > + buf[0] = cmd; > + write_length = ONE_BYTE_SIZE; > + read_length = data->block[0]; > + transaction_type |= BIT(0); > + } else { /* only write */ > + buf[0] = cmd; > + for (i = 0 ; i < data->block[0]; i++) > + buf[i + 1] = data->block[i + 1]; > + > + write_length = data->block[0] + 1; > + read_length = 0; > + } > + break; > + default: > + dev_warn(p3h2x4x_i3c_hub->dev, "Unsupported transaction %d\n", sz); > + break; > + } > + > + desc[0] = rw_address; > + desc[1] = transaction_type; > + desc[2] = write_length; > + desc[3] = read_length; > + > + ret = regmap_write(p3h2x4x_i3c_hub->regmap, target_port_status, > + P3H2x4x_TP_BUFFER_STATUS_MASK); > + if (ret) > + return ret; > + > + ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_PAGE_PTR, controller_buffer_page); > + if (ret) > + return ret; > + > + ret = regmap_bulk_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_CONTROLLER_AGENT_BUFF, > + desc, P3H2x4x_SMBUS_DESCRIPTOR_SIZE); > + if (ret) > + return ret; > + > + if (write_length) { > + ret = regmap_bulk_write(p3h2x4x_i3c_hub->regmap, > + P3H2x4x_CONTROLLER_AGENT_BUFF_DATA, > + buf, write_length); > + if (ret) > + return ret; > + } > + > + ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_TP_SMBUS_AGNT_TRANS_START, > + target_port_code); > + if (ret) > + return ret; > + > + ret = p3h2x4x_read_smbus_transaction_status(p3h2x4x_i3c_hub, target_port_status, > + (write_length + read_length)); > + if (ret) > + return ret; > + > + if (rw) { > + switch (sz) { > + case I2C_SMBUS_QUICK: > + case I2C_SMBUS_BYTE: > + case I2C_SMBUS_BYTE_DATA: > + { > + ret = regmap_bulk_read(p3h2x4x_i3c_hub->regmap, > + P3H2x4x_CONTROLLER_AGENT_BUFF_DATA + > + write_length, > + &data->byte, read_length); > + break; > + } > + case I2C_SMBUS_WORD_DATA: > + { > + ret = regmap_bulk_read(p3h2x4x_i3c_hub->regmap, > + P3H2x4x_CONTROLLER_AGENT_BUFF_DATA + > + write_length, > + (u8 *)&data->word, read_length); > + break; > + } > + case I2C_SMBUS_BLOCK_DATA: > + { > + ret = regmap_bulk_read(p3h2x4x_i3c_hub->regmap, > + P3H2x4x_CONTROLLER_AGENT_BUFF_DATA + > + write_length, > + data->block, read_length); > + break; > + } > + case I2C_SMBUS_I2C_BLOCK_DATA: > + { > + ret = regmap_bulk_read(p3h2x4x_i3c_hub->regmap, > + P3H2x4x_CONTROLLER_AGENT_BUFF_DATA + > + write_length, > + data->block + 1, read_length); > + break; > + } > + default: > + dev_warn(p3h2x4x_i3c_hub->dev, "Unsupported transaction %d\n", sz); > + break; > + } > + > + if (ret) > + return ret; > + } > + > + ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_PAGE_PTR, 0x00); > + if (ret) > + return ret; > + > + return 0; > +} > + > +static s32 p3h2x4x_tp_smbus_xfer(struct i2c_adapter *adap, u16 addr, unsigned short flags, > + char read_write, u8 command, int size, > + union i2c_smbus_data *data) > +{ > + struct tp_bus *bus = i2c_get_adapdata(adap); > + > + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = bus->p3h2x4x_i3c_hub; > + int ret, ret_status; > + > + guard(mutex)(&p3h2x4x_i3c_hub->etx_mutex); > + guard(mutex)(&bus->port_mutex); > + > + if (p3h2x4x_i3c_hub->is_p3h2x4x_in_i3c) { > + ret = i3c_device_disable_ibi(p3h2x4x_i3c_hub->i3cdev); > + if (ret) { > + dev_err(p3h2x4x_i3c_hub->dev, "Failed to disable IBI, ret :- %d\n", ret); > + return ret; > + } > + } > + > + ret_status = p3h2x4x_tp_smbus_xfer_msg(p3h2x4x_i3c_hub, > + (u8)bus->tp_port, > + (u8)addr, > + (u8)read_write, > + (u8)command, > + size, > + data); > + > + if (p3h2x4x_i3c_hub->is_p3h2x4x_in_i3c) { > + ret = i3c_device_enable_ibi(p3h2x4x_i3c_hub->i3cdev); > + if (ret) { > + dev_err(p3h2x4x_i3c_hub->dev, "Failed to enable IBI, ret %d\n", ret); > + return ret; > + } > + } > + > + return ret_status; > +} > + > +static u32 p3h2x4x_tp_smbus_funcs(struct i2c_adapter *adapter) > +{ > + return I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA | > + I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_WORD_DATA | > + I2C_FUNC_SMBUS_I2C_BLOCK | I2C_FUNC_SMBUS_BLOCK_DATA | > + I2C_FUNC_I2C; > +} > + > +#if IS_ENABLED(CONFIG_I2C_SLAVE) > +static int p3h2x4x_tp_i2c_reg_slave(struct i2c_client *slave) > +{ > + struct tp_bus *bus = i2c_get_adapdata(slave->adapter); > + > + bus->is_slave_registered = true; > + > + return 0; > +} > + > +static int p3h2x4x_tp_i2c_unreg_slave(struct i2c_client *slave) > +{ > + struct tp_bus *bus = i2c_get_adapdata(slave->adapter); > + > + bus->is_slave_registered = false; > + > + return 0; > +} > +#endif > + > +/* > + * I2C algorithm Structure > + */ > +static struct i2c_algorithm p3h2x4x_tp_i2c_algorithm = { > + .master_xfer = p3h2x4x_tp_i2c_xfer, > + .smbus_xfer = p3h2x4x_tp_smbus_xfer, > +#if IS_ENABLED(CONFIG_I2C_SLAVE) > + .reg_slave = p3h2x4x_tp_i2c_reg_slave, > + .unreg_slave = p3h2x4x_tp_i2c_unreg_slave, > +#endif > + .functionality = p3h2x4x_tp_smbus_funcs, > +}; > + > +/** > + * p3h2x4x_tp_smbus_algo - add i2c adapter for target port who > + * configured as SMBus. > + * @p3h2x4x_i3c_hub: p3h2x4x device structure. > + * @tp: target port. > + * Return: 0 in case of success, a negative EINVAL code if the error. > + */ > +int p3h2x4x_tp_smbus_algo(struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub, int tp) > +{ > + struct i2c_adapter *smbus_adapter; > + int ret; > + > + smbus_adapter = devm_kzalloc(p3h2x4x_i3c_hub->dev, sizeof(*smbus_adapter), GFP_KERNEL); > + if (!smbus_adapter) > + return -ENOMEM; > + > + smbus_adapter->owner = THIS_MODULE; > + smbus_adapter->class = I2C_CLASS_HWMON; > + smbus_adapter->algo = &p3h2x4x_tp_i2c_algorithm; > + smbus_adapter->dev.parent = p3h2x4x_i3c_hub->dev; > + smbus_adapter->dev.of_node = p3h2x4x_i3c_hub->tp_bus->of_node; > + sprintf(smbus_adapter->name, "p3h2x4x-i3c-hub.tp-port-%d", tp); > + > + i2c_set_adapdata(smbus_adapter, &p3h2x4x_i3c_hub->tp_bus[tp]); > + > + ret = i2c_add_adapter(smbus_adapter); > + if (ret) { > + dev_warn(p3h2x4x_i3c_hub->dev, "Failed to register i2c adapter for port:%i\n", tp); > + devm_kfree(p3h2x4x_i3c_hub->dev, smbus_adapter); > + return ret; > + } > + > + p3h2x4x_i3c_hub->tp_bus[tp].is_registered = true; > + p3h2x4x_i3c_hub->tp_bus[tp].smbus_port_adapter = smbus_adapter; > + return 0; > +} > diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig > index 392b60c7c8dc..9f850a86ca2c 100644 > --- a/drivers/mfd/Kconfig > +++ b/drivers/mfd/Kconfig > @@ -2414,5 +2414,17 @@ config MFD_RSMU_SPI > Additional drivers must be enabled in order to use the functionality > of the device. > > +config MFD_P3H2X4X > + tristate "NXP P3H2X4X I3C Hub Device" > + depends on I3C > + select MFD_CORE > + select REGMAP_I3C > + help > + Enable Support for NXP P3H244x/P3H284x I3C HUB device using I3C/I2c > + communication interface. > + > + This driver provides support for I3C Hub and regulator, additional > + drivers must be enabled in order to use the functionality of the device. > + > endmenu > endif > diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile > index cdbd3ef0c462..2fa6a5bf6a73 100644 > --- a/drivers/mfd/Makefile > +++ b/drivers/mfd/Makefile > @@ -294,3 +294,4 @@ rsmu-i2c-objs := rsmu_core.o rsmu_i2c.o > rsmu-spi-objs := rsmu_core.o rsmu_spi.o > obj-$(CONFIG_MFD_RSMU_I2C) += rsmu-i2c.o > obj-$(CONFIG_MFD_RSMU_SPI) += rsmu-spi.o > +obj-$(CONFIG_MFD_P3H2X4X) += p3h2840.o > diff --git a/drivers/mfd/p3h2840.c b/drivers/mfd/p3h2840.c > new file mode 100644 > index 000000000000..535c9a272ce7 > --- /dev/null > +++ b/drivers/mfd/p3h2840.c > @@ -0,0 +1,129 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright 2025 NXP > + * P3H2x4x i3c hub and regulator device. > + */ > + > +#include <linux/i3c/master.h> > +#include <linux/i2c.h> > +#include <linux/mfd/core.h> > +#include <linux/regmap.h> > +#include <linux/mfd/p3h2840.h> > + > +static const struct mfd_cell p3h2x4x_devs[] = { > + { > + .name = "p3h2x4x-regulator", > + }, > + { > + .name = "p3h2x4x-i3c-hub", > + }, > +}; > + > +static const struct regmap_config p3h2x4x_regmap_config = { > + .reg_bits = P3H2x4x_REG_BITS, > + .val_bits = P3H2x4x_VAL_BITS, > + .max_register = 0xFF, > +}; > + > +static int p3h2x4x_device_probe_i3c(struct i3c_device *i3cdev) > +{ > + struct p3h2x4x_dev *p3h2x4x; > + int ret; > + > + p3h2x4x = devm_kzalloc(&i3cdev->dev, sizeof(*p3h2x4x), GFP_KERNEL); > + if (!p3h2x4x) > + return -ENOMEM; > + > + i3cdev_set_drvdata(i3cdev, p3h2x4x); > + > + p3h2x4x->regmap = devm_regmap_init_i3c(i3cdev, &p3h2x4x_regmap_config); > + if (IS_ERR(p3h2x4x->regmap)) { > + ret = PTR_ERR(p3h2x4x->regmap); > + return dev_err_probe(&i3cdev->dev, ret, "Failed to register I3C HUB regmap\n"); > + } > + > + p3h2x4x->is_p3h2x4x_in_i3c = true; > + p3h2x4x->i3cdev = i3cdev; > + > + ret = devm_mfd_add_devices(&i3cdev->dev, PLATFORM_DEVID_NONE, > + p3h2x4x_devs, ARRAY_SIZE(p3h2x4x_devs), > + NULL, 0, NULL); > + if (ret) > + return dev_err_probe(&i3cdev->dev, ret, "Failed to add sub devices\n"); > + > + return 0; > +} > + > +static int p3h2x4x_device_probe_i2c(struct i2c_client *client) > +{ > + struct p3h2x4x_dev *p3h2x4x; > + int ret; > + > + p3h2x4x = devm_kzalloc(&client->dev, sizeof(*p3h2x4x), GFP_KERNEL); > + if (!p3h2x4x) > + return -ENOMEM; > + > + i2c_set_clientdata(client, p3h2x4x); > + > + p3h2x4x->regmap = devm_regmap_init_i2c(client, &p3h2x4x_regmap_config); > + if (IS_ERR(p3h2x4x->regmap)) { > + ret = PTR_ERR(p3h2x4x->regmap); > + return dev_err_probe(&client->dev, ret, "Failed to register I3C HUB regmap\n"); > + } > + > + p3h2x4x->is_p3h2x4x_in_i3c = false; > + p3h2x4x->i2cdev = client; > + > + ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, > + p3h2x4x_devs, ARRAY_SIZE(p3h2x4x_devs), > + NULL, 0, NULL); > + if (ret) > + return dev_err_probe(&client->dev, ret, "Failed to add sub devices\n"); > + > + return 0; > +} > + > +/* p3h2x4x ids (i3c) */ > +static const struct i3c_device_id p3h2x4x_i3c_ids[] = { > + I3C_CLASS(I3C_DCR_HUB, NULL), > + { /* sentinel */ }, > +}; > +MODULE_DEVICE_TABLE(i3c, p3h2x4x_i3c_ids); > + > +/* p3h2x4x ids (i2c) */ > +static const struct i2c_device_id p3h2x4x_i2c_id_table[] = { > + { "nxp-i3c-hub" }, > + { /* sentinel */ } > +}; > +MODULE_DEVICE_TABLE(i2c, p3h2x4x_i2c_id_table); > + > +static const struct of_device_id p3h2x4x_i2c_of_match[] = { > + { .compatible = "nxp,p3h2840", }, > + { /* sentinel */ } > +}; > + > +MODULE_DEVICE_TABLE(of, p3h2x4x_i2c_of_match); > +static struct i3c_driver p3h2x4x_i3c = { > + .driver = { > + .name = "p3h2x4x_i3c_drv", > + }, > + .probe = p3h2x4x_device_probe_i3c, > + .id_table = p3h2x4x_i3c_ids, > +}; > + > +static struct i2c_driver p3h2x4x_i2c = { > + .driver = { > + .name = "p3h2x4x_i2c_drv", > + .of_match_table = p3h2x4x_i2c_of_match, > + }, > + .probe = p3h2x4x_device_probe_i2c, > + .id_table = p3h2x4x_i2c_id_table, > +}; > + > +module_i3c_i2c_driver(p3h2x4x_i3c, &p3h2x4x_i2c); > + > +MODULE_AUTHOR("Aman Kumar Pandey <aman.kumarpandey@nxp.com>"); > +MODULE_AUTHOR("vikash Bansal <vikash.bansal@nxp.com>"); > +MODULE_DESCRIPTION("P3H2x4x I3C HUB driver"); > +MODULE_LICENSE("GPL"); > + > diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig > index b3513900ccb5..966317b21276 100644 > --- a/drivers/regulator/Kconfig > +++ b/drivers/regulator/Kconfig > @@ -1005,6 +1005,16 @@ config REGULATOR_PF8X00 > on the NXP PF8100/PF8121A/PF8200 PMIC. The module will be named > "pf8x00-regulator". > > +config REGULATOR_P3H2X4X > + tristate "P3H2X4X regulator support" > + depends on MFD_P3H2X4X > + help > + This driver provides support for the voltage regulators of the > + P3H244x/P3H284x multi-function I3C Hub device. > + > + Say M here if you want to include support for this regulator as > + a module. The module will be named "p3h2840_i3c_hub_regulator". > + > config REGULATOR_PF1550_RPMSG > tristate "Freescale PF1550 rpmsg regulator driver" > depends on RPMSG > diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile > index c284babb2345..8feeacb67d2c 100644 > --- a/drivers/regulator/Makefile > +++ b/drivers/regulator/Makefile > @@ -201,5 +201,6 @@ obj-$(CONFIG_REGULATOR_WM831X) += wm831x-ldo.o > obj-$(CONFIG_REGULATOR_WM8350) += wm8350-regulator.o > obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o > obj-$(CONFIG_REGULATOR_WM8994) += wm8994-regulator.o > +obj-$(CONFIG_REGULATOR_P3H2X4X) += p3h2840_i3c_hub_regulator.o > > ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG > diff --git a/drivers/regulator/p3h2840_i3c_hub_regulator.c b/drivers/regulator/p3h2840_i3c_hub_regulator.c > new file mode 100644 > index 000000000000..f01dbc0aa691 > --- /dev/null > +++ b/drivers/regulator/p3h2840_i3c_hub_regulator.c > @@ -0,0 +1,163 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright 2025 NXP > + * This P3H2x4x driver file contain functions for enable/disable regulator and voltage set/get. > + */ > +#include <linux/regulator/driver.h> > +#include <linux/platform_device.h> > +#include <linux/of.h> > +#include <linux/mfd/p3h2840.h> > + > +#define P3H2x4x_LDO_AND_PULLUP_CONF 0x19 > +#define P3H2x4x_LDO_ENABLE_DISABLE_MASK GENMASK(3, 0) > +#define P3H2x4x_CP0_EN_LDO BIT(0) > +#define P3H2x4x_CP1_EN_LDO BIT(1) > +#define P3H2x4x_TP0145_EN_LDO BIT(2) > +#define P3H2x4x_TP2367_EN_LDO BIT(3) > + > +#define P3H2x4x_NET_OPER_MODE_CONF 0x15 > +#define P3H2x4x_VCCIO_LDO_CONF 0x16 > +#define P3H2x4x_CP0_VCCIO_LDO_VOLTAGE_MASK GENMASK(1, 0) > +#define P3H2x4x_CP0_VCCIO_LDO_VOLTAGE(x) \ > + FIELD_PREP(P3H2x4x_CP0_VCCIO_LDO_VOLTAGE_MASK, x) > +#define P3H2x4x_CP1_VCCIO_LDO_VOLTAGE_MASK GENMASK(3, 2) > +#define P3H2x4x_CP1_VCCIO_LDO_VOLTAGE(x) \ > + FIELD_PREP(P3H2x4x_CP1_VCCIO_LDO_VOLTAGE_MASK, x) > +#define P3H2x4x_TP0145_VCCIO_LDO_VOLTAGE_MASK GENMASK(5, 4) > +#define P3H2x4x_TP0145_VCCIO_LDO_VOLTAGE(x) \ > + FIELD_PREP(P3H2x4x_TP0145_VCCIO_LDO_VOLTAGE_MASK, x) > +#define P3H2x4x_TP2367_VCCIO_LDO_VOLTAGE_MASK GENMASK(7, 6) > +#define P3H2x4x_TP2367_VCCIO_LDO_VOLTAGE(x) \ > + FIELD_PREP(P3H2x4x_TP2367_VCCIO_LDO_VOLTAGE_MASK, x) > +#define P3H2x4x_LDO_COUNT 4 > + > +struct p3h2x4x_regulator_dev { > + struct regulator_dev *rp3h2x4x_dev[P3H2x4x_LDO_COUNT]; > + struct regmap *regmap; > +}; > + > +static const struct regulator_ops p3h2x4x_ldo_ops = { > + .list_voltage = regulator_list_voltage_table, > + .map_voltage = regulator_map_voltage_iterate, > + .set_voltage_sel = regulator_set_voltage_sel_regmap, > + .get_voltage_sel = regulator_get_voltage_sel_regmap, > + .enable = regulator_enable_regmap, > + .disable = regulator_disable_regmap, > + .is_enabled = regulator_is_enabled_regmap, > +}; > + > +static const unsigned int p3h2x4x_voltage_table[] = { > + 1000000, > + 1100000, > + 1200000, > + 1800000, > +}; > + > +static struct regulator_desc p3h2x4x_regulators[] = { > + { > + .name = "cp0", > + .of_match = of_match_ptr("cp0"), > + .regulators_node = of_match_ptr("regulators"), > + .volt_table = p3h2x4x_voltage_table, > + .n_voltages = ARRAY_SIZE(p3h2x4x_voltage_table), > + .ops = &p3h2x4x_ldo_ops, > + .type = REGULATOR_VOLTAGE, > + .owner = THIS_MODULE, > + .enable_reg = P3H2x4x_LDO_AND_PULLUP_CONF, > + .enable_mask = P3H2x4x_CP0_EN_LDO, > + .vsel_reg = P3H2x4x_VCCIO_LDO_CONF, > + .vsel_mask = P3H2x4x_CP0_VCCIO_LDO_VOLTAGE_MASK, > + }, > + { > + .name = "cp1", > + .of_match = of_match_ptr("cp1"), > + .regulators_node = of_match_ptr("regulators"), > + .volt_table = p3h2x4x_voltage_table, > + .n_voltages = ARRAY_SIZE(p3h2x4x_voltage_table), > + .ops = &p3h2x4x_ldo_ops, > + .type = REGULATOR_VOLTAGE, > + .owner = THIS_MODULE, > + .enable_reg = P3H2x4x_LDO_AND_PULLUP_CONF, > + .enable_mask = P3H2x4x_CP1_EN_LDO, > + .vsel_reg = P3H2x4x_VCCIO_LDO_CONF, > + .vsel_mask = P3H2x4x_CP1_VCCIO_LDO_VOLTAGE_MASK, > + }, > + { > + .name = "tp0145", > + .of_match = of_match_ptr("tp0145"), > + .regulators_node = of_match_ptr("regulators"), > + .volt_table = p3h2x4x_voltage_table, > + .n_voltages = ARRAY_SIZE(p3h2x4x_voltage_table), > + .ops = &p3h2x4x_ldo_ops, > + .type = REGULATOR_VOLTAGE, > + .owner = THIS_MODULE, > + .enable_reg = P3H2x4x_LDO_AND_PULLUP_CONF, > + .enable_mask = P3H2x4x_TP0145_EN_LDO, > + .vsel_reg = P3H2x4x_VCCIO_LDO_CONF, > + .vsel_mask = P3H2x4x_TP0145_VCCIO_LDO_VOLTAGE_MASK, > + }, > + { > + .name = "tp2367", > + .of_match = of_match_ptr("tp2367"), > + .regulators_node = of_match_ptr("regulators"), > + .volt_table = p3h2x4x_voltage_table, > + .n_voltages = ARRAY_SIZE(p3h2x4x_voltage_table), > + .ops = &p3h2x4x_ldo_ops, > + .type = REGULATOR_VOLTAGE, > + .owner = THIS_MODULE, > + .enable_reg = P3H2x4x_LDO_AND_PULLUP_CONF, > + .enable_mask = P3H2x4x_TP2367_EN_LDO, > + .vsel_reg = P3H2x4x_VCCIO_LDO_CONF, > + .vsel_mask = P3H2x4x_TP2367_VCCIO_LDO_VOLTAGE_MASK, > + }, > +}; > + > +int p3h2x4x_regulator_probe(struct platform_device *pdev) > +{ > + struct p3h2x4x_dev *p3h2x4x = dev_get_drvdata(pdev->dev.parent); > + struct p3h2x4x_regulator_dev *p3h2x4x_regulator; > + struct regulator_config rcfg = { }; > + struct device *dev = &pdev->dev; > + struct regulator_dev *rdev; > + int ret, i; > + > + p3h2x4x_regulator = devm_kzalloc(dev, sizeof(*p3h2x4x_regulator), GFP_KERNEL); > + if (!p3h2x4x_regulator) > + return -ENOMEM; > + > + platform_set_drvdata(pdev, p3h2x4x_regulator); > + > + p3h2x4x_regulator->regmap = p3h2x4x->regmap; > + device_set_of_node_from_dev(dev, dev->parent); > + > + rcfg.dev = dev; > + rcfg.dev->of_node = dev->of_node; > + rcfg.regmap = p3h2x4x_regulator->regmap; > + rcfg.driver_data = p3h2x4x_regulator; > + > + for (i = 0; i < ARRAY_SIZE(p3h2x4x_regulators); i++) { > + rdev = devm_regulator_register(&pdev->dev, &p3h2x4x_regulators[i], &rcfg); > + if (IS_ERR(rdev)) { > + ret = PTR_ERR(rdev); > + dev_err(dev, "Failed to register %s\n", p3h2x4x_regulators[i].name); > + return ret; > + } > + p3h2x4x_regulator->rp3h2x4x_dev[i] = rdev; > + } > + return 0; > +} > + > +static struct platform_driver p3h2x4x_regulator_driver = { > + .driver = { > + .name = "p3h2x4x-regulator", > + .probe_type = PROBE_PREFER_ASYNCHRONOUS, > + }, > + .probe = p3h2x4x_regulator_probe, > +}; > +module_platform_driver(p3h2x4x_regulator_driver); > + > +MODULE_AUTHOR("Aman Kumar Pandey <aman.kumarpandey@nxp.com>"); > +MODULE_AUTHOR("vikash Bansal <vikash.bansal@nxp.com>"); > +MODULE_DESCRIPTION("P3H2x4x I3C HUB driver"); > +MODULE_LICENSE("GPL"); > + > diff --git a/include/linux/i3c/device.h b/include/linux/i3c/device.h > index ef6217da8253..f43feda2fc09 100644 > --- a/include/linux/i3c/device.h > +++ b/include/linux/i3c/device.h > @@ -77,6 +77,7 @@ struct i3c_priv_xfer { > */ > enum i3c_dcr { > I3C_DCR_GENERIC_DEVICE = 0, > + I3C_DCR_HUB = 194, > }; > > #define I3C_PID_MANUF_ID(pid) (((pid) & GENMASK_ULL(47, 33)) >> 33) > diff --git a/include/linux/mfd/p3h2840.h b/include/linux/mfd/p3h2840.h > new file mode 100644 > index 000000000000..4e3c737eb8a8 > --- /dev/null > +++ b/include/linux/mfd/p3h2840.h > @@ -0,0 +1,22 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* > + * Copyright 2025 NXP > + * This header file contain private Reg address and its bit mapping etc. > + */ > + > +#ifndef P3H2840_H > +#define P3H2840_H > + > +#include <linux/types.h> > + > +/* Reg config for Regmap */ > +#define P3H2x4x_REG_BITS 8 > +#define P3H2x4x_VAL_BITS 8 > + > +struct p3h2x4x_dev { > + struct i3c_device *i3cdev; > + struct i2c_client *i2cdev; > + struct regmap *regmap; > + bool is_p3h2x4x_in_i3c; > +}; > +#endif /* P3H2840_H */ > -- > 2.25.1 > ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v3 2/2] mfd: i3c: Add driver for NXP P3H2x4x i3c-hub device 2025-08-08 13:20 ` [PATCH v3 2/2] mfd: i3c: Add driver for NXP P3H2x4x i3c-hub device Aman Kumar Pandey 2025-08-08 20:38 ` Frank Li @ 2025-08-11 7:01 ` Krzysztof Kozlowski 1 sibling, 0 replies; 7+ messages in thread From: Krzysztof Kozlowski @ 2025-08-11 7:01 UTC (permalink / raw) To: Aman Kumar Pandey Cc: linux-kernel, linux-i3c, alexandre.belloni, krzk+dt, robh, conor+dt, devicetree, broonie, lee, vikash.bansal, priyanka.jain, shashank.rebbapragada, Frank.Li On Fri, Aug 08, 2025 at 04:20:33PM +0300, Aman Kumar Pandey wrote: > +static int p3h2x4x_device_probe_i3c(struct i3c_device *i3cdev) > +{ > + struct p3h2x4x_dev *p3h2x4x; > + int ret; > + > + p3h2x4x = devm_kzalloc(&i3cdev->dev, sizeof(*p3h2x4x), GFP_KERNEL); > + if (!p3h2x4x) > + return -ENOMEM; > + > + i3cdev_set_drvdata(i3cdev, p3h2x4x); > + > + p3h2x4x->regmap = devm_regmap_init_i3c(i3cdev, &p3h2x4x_regmap_config); > + if (IS_ERR(p3h2x4x->regmap)) { > + ret = PTR_ERR(p3h2x4x->regmap); Drop, don't create useless assignments. > + return dev_err_probe(&i3cdev->dev, ret, "Failed to register I3C HUB regmap\n"); Best regards, Krzysztof ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v3 1/2] dt-bindings: i3c: Add NXP P3H2x4x i3c-hub support 2025-08-08 13:20 [PATCH v3 1/2] dt-bindings: i3c: Add NXP P3H2x4x i3c-hub support Aman Kumar Pandey 2025-08-08 13:20 ` [PATCH v3 2/2] mfd: i3c: Add driver for NXP P3H2x4x i3c-hub device Aman Kumar Pandey @ 2025-08-11 6:56 ` Krzysztof Kozlowski 2025-08-11 7:55 ` Krzysztof Kozlowski 2025-08-11 12:35 ` Rob Herring (Arm) 3 siblings, 0 replies; 7+ messages in thread From: Krzysztof Kozlowski @ 2025-08-11 6:56 UTC (permalink / raw) To: Aman Kumar Pandey Cc: linux-kernel, linux-i3c, alexandre.belloni, krzk+dt, robh, conor+dt, devicetree, broonie, lee, vikash.bansal, priyanka.jain, shashank.rebbapragada, Frank.Li On Fri, Aug 08, 2025 at 04:20:32PM +0300, Aman Kumar Pandey wrote: > Add bindings for the NXP P3H2x4x (P3H2440/P3H2441/P3H2840/P3H2841) > multiport I3C hub family. These devices connect to a host via > I3C/I2C/SMBus and allow communication with multiple downstream > peripherals. > > Signed-off-by: Aman Kumar Pandey <aman.kumarpandey@nxp.com> > Signed-off-by: Vikash Bansal <vikash.bansal@nxp.com> > > --- > V1 -> V2: Cleaned up coding style and addressed review comments Which ones? You must be precise here. > V2 -> V3: Added mfd device support for i3c hub and regulator. Still not tested, still fails. :/ I will not review this, please reach to your colleagues in NXP to explain how to test code before sending upstream. Best regards, Krzysztof ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v3 1/2] dt-bindings: i3c: Add NXP P3H2x4x i3c-hub support 2025-08-08 13:20 [PATCH v3 1/2] dt-bindings: i3c: Add NXP P3H2x4x i3c-hub support Aman Kumar Pandey 2025-08-08 13:20 ` [PATCH v3 2/2] mfd: i3c: Add driver for NXP P3H2x4x i3c-hub device Aman Kumar Pandey 2025-08-11 6:56 ` [PATCH v3 1/2] dt-bindings: i3c: Add NXP P3H2x4x i3c-hub support Krzysztof Kozlowski @ 2025-08-11 7:55 ` Krzysztof Kozlowski 2025-08-11 12:35 ` Rob Herring (Arm) 3 siblings, 0 replies; 7+ messages in thread From: Krzysztof Kozlowski @ 2025-08-11 7:55 UTC (permalink / raw) To: Aman Kumar Pandey Cc: linux-kernel, linux-i3c, alexandre.belloni, krzk+dt, robh, conor+dt, devicetree, broonie, lee, vikash.bansal, priyanka.jain, shashank.rebbapragada, Frank.Li On Fri, Aug 08, 2025 at 04:20:32PM +0300, Aman Kumar Pandey wrote: > + hub@70,236153000c2 { > + reg = <0x70 0x236 0x3000c2>; > + #address-cells = <1>; > + #size-cells = <0>; > + assigned-address = <0x50>; > + > + cp0-ldo-microvolt = <1800000>; So you just duplicated regulators? > + cp1-ldo-microvolt = <1800000>; > + tp0145-ldo-microvolt = <1800000>; > + tp2367-ldo-microvolt = <1800000>; > + tp0145-pullup-ohm = <1000>; > + tp2367-pullup-ohm = <1000>; > + cp0-io-strength-ohm = <50>; > + cp1-io-strength-ohm = <50>; > + tp0145-io-strength-ohm = <50>; > + tp2367-io-strength-ohm = <50>; > + cp0-supply = <&cp0>; > + tp0145-supply = <&tp0145>; > + > + regulators { > + cp0 { Messed indentation > + regulator-name = "cp0"; > + regulator-min-microvolt = <1000000>; > + regulator-max-microvolt = <1800000>; > + }; > + > + cp1 { > + regulator-name = "cp1"; > + regulator-min-microvolt = <1000000>; > + regulator-max-microvolt = <1800000>; > + }; > + > + tp0145 { > + regulator-name = "tp0145"; > + regulator-min-microvolt = <1000000>; > + regulator-max-microvolt = <1800000>; > + }; > + > + tp2367 { > + regulator-name = "tp2367"; > + regulator-min-microvolt = <1000000>; > + regulator-max-microvolt = <1800000>; > + }; > + }; > + > + target-port@0 { > + reg = <0x0>; > + #address-cells = <1>; > + #size-cells = <0>; > + mode = "smbus"; > + pullup-enable; > + > + eeprom@57 { > + compatible = "atmel,24c32"; > + reg = <0x57>; > + pagesize = <32>; > + wp-gpios = <&gpio2 2 0>; > + num-addresses = <8>; > + }; > + }; > + > + target-port@2 { > + reg = <0x2>; > + #address-cells = <1>; > + #size-cells = <0>; > + mode = "i3c"; > + pullup-enable; > + > + sensor@68,39200144004 { > + reg = <0x68 0x392 0x144004>; > + assigned-address = <0xa>; > + }; > + }; > + }; > + }; > diff --git a/MAINTAINERS b/MAINTAINERS > index 4262e8d833c4..b33e90030188 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -15477,6 +15477,14 @@ S: Maintained > F: Documentation/devicetree/bindings/sound/nxp,tfa989x.yaml > F: sound/soc/codecs/tfa989x.c > > +NXP P3H2X4X I3C-HUB DRIVER Does not look ordered according to rules in this file. Best regards, Krzysztof ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v3 1/2] dt-bindings: i3c: Add NXP P3H2x4x i3c-hub support 2025-08-08 13:20 [PATCH v3 1/2] dt-bindings: i3c: Add NXP P3H2x4x i3c-hub support Aman Kumar Pandey ` (2 preceding siblings ...) 2025-08-11 7:55 ` Krzysztof Kozlowski @ 2025-08-11 12:35 ` Rob Herring (Arm) 3 siblings, 0 replies; 7+ messages in thread From: Rob Herring (Arm) @ 2025-08-11 12:35 UTC (permalink / raw) To: Aman Kumar Pandey Cc: krzk+dt, alexandre.belloni, devicetree, linux-i3c, Frank.Li, priyanka.jain, vikash.bansal, linux-kernel, conor+dt, lee, broonie, shashank.rebbapragada On Fri, 08 Aug 2025 16:20:32 +0300, Aman Kumar Pandey wrote: > Add bindings for the NXP P3H2x4x (P3H2440/P3H2441/P3H2840/P3H2841) > multiport I3C hub family. These devices connect to a host via > I3C/I2C/SMBus and allow communication with multiple downstream > peripherals. > > Signed-off-by: Aman Kumar Pandey <aman.kumarpandey@nxp.com> > Signed-off-by: Vikash Bansal <vikash.bansal@nxp.com> > > --- > V1 -> V2: Cleaned up coding style and addressed review comments > V2 -> V3: Added mfd device support for i3c hub and regulator. > --- > .../bindings/mfd/nxp,p3h2840-i3c-hub.yaml | 320 ++++++++++++++++++ > MAINTAINERS | 8 + > 2 files changed, 328 insertions(+) > create mode 100644 Documentation/devicetree/bindings/mfd/nxp,p3h2840-i3c-hub.yaml > My bot found errors running 'make dt_binding_check' on your patch: yamllint warnings/errors: ./Documentation/devicetree/bindings/mfd/nxp,p3h2840-i3c-hub.yaml:32:4: [warning] wrong indentation: expected 4 but found 3 (indentation) ./Documentation/devicetree/bindings/mfd/nxp,p3h2840-i3c-hub.yaml:166:111: [warning] line too long (118 > 110 characters) (line-length) dtschema/dtc warnings/errors: /builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/mfd/nxp,p3h2840-i3c-hub.yaml: Error in referenced schema matching $id: http://devicetree.org/schemas/mfd/i3c.yaml Tried these paths (check schema $id if path is wrong): /builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/mfd/i3c.yaml /usr/local/lib/python3.11/dist-packages/dtschema/schemas/mfd/i3c.yaml doc reference errors (make refcheckdocs): Warning: MAINTAINERS references a file that doesn't exist: Documentation/devicetree/bindings/i3c/p3h2x4x_i3c_hub.yaml MAINTAINERS: Documentation/devicetree/bindings/i3c/p3h2x4x_i3c_hub.yaml See https://patchwork.ozlabs.org/project/devicetree-bindings/patch/20250808132033.3996614-1-aman.kumarpandey@nxp.com The base for the series is generally the latest rc1. A different dependency should be noted in *this* patch. If you already ran 'make dt_binding_check' and didn't see the above error(s), then make sure 'yamllint' is installed and dt-schema is up to date: pip3 install dtschema --upgrade Please check and re-submit after running the above command yourself. Note that DT_SCHEMA_FILES can be set to your schema file to speed up checking your schema. However, it must be unset to test all examples with your schema. ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2025-08-11 12:35 UTC | newest] Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2025-08-08 13:20 [PATCH v3 1/2] dt-bindings: i3c: Add NXP P3H2x4x i3c-hub support Aman Kumar Pandey 2025-08-08 13:20 ` [PATCH v3 2/2] mfd: i3c: Add driver for NXP P3H2x4x i3c-hub device Aman Kumar Pandey 2025-08-08 20:38 ` Frank Li 2025-08-11 7:01 ` Krzysztof Kozlowski 2025-08-11 6:56 ` [PATCH v3 1/2] dt-bindings: i3c: Add NXP P3H2x4x i3c-hub support Krzysztof Kozlowski 2025-08-11 7:55 ` Krzysztof Kozlowski 2025-08-11 12:35 ` Rob Herring (Arm)
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®