* Re: [PATCH v2 2/4] dt-bindings: phy: nuvoton,ma35d1-usb2-phy: extend for dual-port OTG support
From: sashiko-bot @ 2026-06-25 2:58 UTC (permalink / raw)
To: Joey Lu
Cc: vkoul, devicetree, neil.armstrong, conor+dt, olteanv, linux-phy,
robh
In-Reply-To: <20260625023958.569299-3-a0987203069@gmail.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The commit message claims multiple driver behavioral changes that are missing from the patch, leaving the driver incompatible with the new binding.
--
commit f557b8622653f3df69b040020ac61c52fd0498d6
Author: Joey Lu <a0987203069@gmail.com>
dt-bindings: phy: nuvoton,ma35d1-usb2-phy: extend for dual-port OTG support
This commit updates the device tree binding for the MA35D1 USB2 PHY to support
dual-port OTG. It changes the PHY node to be a child of a syscon node, removes
the nuvoton,sys and clocks properties, and updates #phy-cells to 1.
> - The PHY node is now a child of the system-management syscon node
> with a reg property. The nuvoton,sys phandle and clocks properties
> are removed; the driver derives the regmap from its parent, and
> clock gating is owned by each individual USB controller.
>
> - #phy-cells changes from 0 to 1: the cell selects the PHY port.
> diff --git a/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml b/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml
> index fff858c909a06..a20d03c80932b 100644
> --- a/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml
> +++ b/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml
[ ... ]
> @@ -8,38 +8,72 @@ title: Nuvoton MA35D1 USB2 phy
[ ... ]
> "#phy-cells":
> - const: 0
> + const: 1
> + description:
> + The single cell selects the PHY port. 0 selects the OTG port (USB0,
> + shared with DWC2 gadget controller) and 1 selects the host-only port
> + (USB1).
>
> - clocks:
> - maxItems: 1
>
> - nuvoton,sys:
> - $ref: /schemas/types.yaml#/definitions/phandle
> + nuvoton,rcalcode:
[Severity: High]
Does this patch introduce a regression by updating the binding without the
corresponding driver updates?
The commit message indicates the driver derives the regmap from its parent and
handles the #phy-cells change, but this patch does not contain driver
modifications.
If a device tree is updated to comply with this new binding, will the existing
driver fail to probe? Looking at ma35_usb_phy_probe() in
drivers/phy/nuvoton/phy-ma35d1-usb2.c, it unconditionally requires the
nuvoton,sys property:
drivers/phy/nuvoton/phy-ma35d1-usb2.c:ma35_usb_phy_probe() {
...
p_phy->sysreg = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
"nuvoton,sys");
if (IS_ERR(p_phy->sysreg))
...
}
Additionally, the unmodified driver still uses of_phy_simple_xlate and
registers a single PHY, which is structurally incompatible with the binding
change to #phy-cells = <1>:
drivers/phy/nuvoton/phy-ma35d1-usb2.c:ma35_usb_phy_probe() {
...
provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate);
...
}
Should the driver changes be included in this patch or submitted together as
a series to avoid breaking USB initialization at boot time for compliant device
trees?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260625023958.569299-1-a0987203069@gmail.com?part=2
^ permalink raw reply
* Re: [PATCH v2 3/4] arm64: dts: nuvoton: ma35d1: add USB controllers and dual-port PHY node
From: sashiko-bot @ 2026-06-25 2:56 UTC (permalink / raw)
To: Joey Lu
Cc: linux-phy, conor+dt, olteanv, robh, vkoul, devicetree,
neil.armstrong
In-Reply-To: <20260625023958.569299-4-a0987203069@gmail.com>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] The PHY driver fails to probe unconditionally because the new device tree node lacks required properties.
- [High] The PHY driver does not support multi-PHY indexing, causing the second USB port to remain uninitialized.
- [Low] The `sys` node lacks a `ranges` property, breaking standard device tree address translation for its children.
--
commit 9dc588096b739f5b70f171c1eb14764b230dfb57
Author: Joey Lu <a0987203069@gmail.com>
arm64: dts: nuvoton: ma35d1: add USB controllers and dual-port PHY node
This commit adds device tree nodes for the MA35D1 USB subsystem, including
the simple-mfd sys node, the usb-phy child node, and the DWC2, EHCI0/1,
and OHCI0/1 controllers. It also enables them and sets up pinctrl in the
board files.
> diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
> index e51b98f5bdce4..a6a354f283117 100644
> --- a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
> +++ b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
> @@ -83,9 +83,18 @@ soc {
> ranges;
>
> sys: system-management@40460000 {
> - compatible = "nuvoton,ma35d1-reset", "syscon";
> + compatible = "nuvoton,ma35d1-reset", "syscon", "simple-mfd";
> reg = <0x0 0x40460000 0x0 0x200>;
> #reset-cells = <1>;
> + #address-cells = <1>;
> + #size-cells = <1>;
[Severity: Low]
Since this sys node is acting as a bus for usb_phy and now defines
#address-cells and #size-cells, does it also need a "ranges;" property?
Standard device tree specification requires a ranges property in the
parent to map the child's address space. Omitting it here breaks standard
device tree address translation and can trigger dtbs_check warnings.
> +
> + usb_phy: usb-phy@60 {
> + compatible = "nuvoton,ma35d1-usb2-phy";
> + reg = <0x60 0x14>;
> + #phy-cells = <1>;
> + status = "disabled";
> + };
[Severity: High]
Will this PHY node fail to probe since it lacks the "nuvoton,sys" and
"clocks" properties?
Looking at ma35_usb_phy_probe() in drivers/phy/nuvoton/phy-ma35d1-usb2.c,
it unconditionally requests both of these:
p_phy->sysreg = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
"nuvoton,sys");
if (IS_ERR(p_phy->sysreg))
return dev_err_probe(&pdev->dev, PTR_ERR(p_phy->sysreg),
"Failed to get SYS registers\n");
Because they are omitted from this node, won't the lookup return -ENODEV
and cause all dependent USB controllers (DWC2, EHCI0/1, OHCI0/1) to defer
indefinitely?
> };
>
> clk: clock-controller@40460200 {
[ ... ]
> + ehci1: usb@401c0000 {
> + compatible = "generic-ehci";
> + reg = <0x0 0x401c0000 0x0 0x1000>;
> + interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH>;
> + clocks = <&clk HUSBH1_GATE>;
> + phys = <&usb_phy 1>;
[Severity: High]
Does the current driver actually support indexing the PHY like this?
The device tree maps controllers to <&usb_phy 0> and <&usb_phy 1>, but
looking at ma35_usb_phy_probe(), the driver registers with a simple xlate
that ignores the index:
provider = devm_of_phy_provider_register(&pdev->dev,
of_phy_simple_xlate);
Since of_phy_simple_xlate returns the first PHY object for all requests,
and the power routines like ma35_usb_phy_power_on() are hardcoded to only
manipulate PHY0 bits:
regmap_update_bits(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, 0x7,
(PHY0POR | PHY0SUSPEND));
Will the hardware for PHY1 (the dedicated host port) remain completely
unpowered and fail to initialize when ehci1 and ohci1 attempt to use it?
> + phy-names = "usb";
> + companion = <&ohci1>;
> + status = "disabled";
> + };
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260625023958.569299-1-a0987203069@gmail.com?part=3
^ permalink raw reply
* Re: [PATCH v2 1/4] dt-bindings: reset: nuvoton,ma35d1-reset: add simple-mfd and child node support
From: sashiko-bot @ 2026-06-25 2:55 UTC (permalink / raw)
To: Joey Lu
Cc: conor+dt, devicetree, linux-phy, vkoul, robh, neil.armstrong,
olteanv
In-Reply-To: <20260625023958.569299-2-a0987203069@gmail.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The child node pattern forces a unit address and `reg` property, but the intended child device's binding strictly prohibits `reg`, creating a validation Catch-22.
--
commit 64487b3a088a3cb8b25dc58df4266bfb284945b3
Author: Joey Lu <a0987203069@gmail.com>
dt-bindings: reset: nuvoton,ma35d1-reset: add simple-mfd and child node support
This commit updates the MA35D1 reset binding to allow simple-mfd and child
nodes. It adds #address-cells, #size-cells, and a pattern property to support
instantiating children like the USB PHY.
> diff --git a/Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml b/Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml
> index 3ce7dcecd87ae..1fda7e8f4b5d1 100644
> --- a/Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml
> +++ b/Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml
[ ... ]
> @@ -26,6 +28,16 @@ properties:
> '#reset-cells':
> const: 1
>
> + '#address-cells':
> + const: 1
> +
> + '#size-cells':
> + const: 1
> +
> +patternProperties:
> + "^.*@[0-9a-f]+$":
> + type: object
[Severity: Medium]
This pattern property forces child nodes to have a unit address, which
inherently requires a reg property to avoid a dtc warning ("node has a unit
name, but no reg or ranges property").
However, looking at the child PHY binding
(Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml), it
strictly prohibits reg via additionalProperties: false.
If we provide the reg property required by the parent's unit address, it will
fail the PHY schema validation.
Additionally, the PHY driver doesn't seem to act as a true MFD child. Looking
at drivers/phy/nuvoton/phy-ma35d1-usb2.c:ma35_usb_phy_probe():
p_phy->sysreg = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "nuvoton,sys");
The driver hardcodes its offset and uses a phandle instead of expecting a reg
property.
Could this create a validation failure for the child PHY node, and are the
bindings and the driver structurally out of sync here?
> +
> required:
> - compatible
> - reg
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260625023958.569299-1-a0987203069@gmail.com?part=1
^ permalink raw reply
* [PATCH v2 4/4] phy: nuvoton: phy-ma35d1-usb2: extend to dual-port with OTG support
From: Joey Lu @ 2026-06-25 2:39 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann,
Catalin Marinas, Jacky Huang, Shan-Chun Hung, Hui-Ping Chen,
Joey Lu, linux-phy, devicetree, linux-arm-kernel, linux-kernel,
Joey Lu
In-Reply-To: <20260625023958.569299-1-a0987203069@gmail.com>
The existing driver handled only PHY0 in device mode (DWC2 gadget).
Extend it to manage both PHY ports and integrate OTG support, per
reviewer suggestion to reuse the existing driver rather than add a
separate one.
The MA35D1 SoC has two USB PHY ports:
- PHY0 (USB0): OTG port shared between the DWC2 gadget controller
and EHCI0/OHCI0 host controllers. A hardware mux in the SoC
automatically routes the USB0 signals to the appropriate
controller based on the USB ID pin state.
- PHY1 (USB1): dedicated host-only port for EHCI1/OHCI1.
Key changes:
Dual-port support
A loop in probe() creates two struct phy objects, one per port,
each with its own phy_set_drvdata() context. A custom xlate
function selects the correct phy by the single #phy-cells argument.
Unified .init callback
A single ma35_usb_phy_init() handles both ports using parametric
register macros (USBPMISCR_PHY_*(n)). If the SUSPEND bit is
already set the init is skipped entirely, preventing the shared
PHY0 from being reset while a live link is active. On cold boot,
PHY0 polls for either host-mode clocks (HSTCKSTB + CK12MSTB) or
device-mode clock (DEVCKSTB) since the hardware selects the role
automatically; PHY1 polls for host-mode clocks only.
Clock management removed
.power_on/.power_off and all struct clk handling are removed.
Each USB controller (DWC2, EHCI, OHCI) already gates its own
clock directly through its DTS clocks binding. Having the PHY
driver redundantly enable the same gates added unnecessary
coupling without benefit.
OTG role switch for PHY0
A read-only USB role switch is registered, reporting the current
OTG role by reading the USB ID pin state from PWRONOTP[16].
.set returns -EOPNOTSUPP since the hardware mux is fully
automatic. allow_userspace_control is kept true to preserve the
sysfs attribute for observation; writes are rejected by .set.
syscon regmap via parent
The driver obtains the regmap by calling
syscon_node_to_regmap(pdev->dev.parent->of_node), removing the
need for the nuvoton,sys phandle.
Signed-off-by: Joey Lu <a0987203069@gmail.com>
---
drivers/phy/nuvoton/phy-ma35d1-usb2.c | 267 ++++++++++++++++++--------
1 file changed, 192 insertions(+), 75 deletions(-)
diff --git a/drivers/phy/nuvoton/phy-ma35d1-usb2.c b/drivers/phy/nuvoton/phy-ma35d1-usb2.c
index 9a459b700ed4..19242b10cee3 100644
--- a/drivers/phy/nuvoton/phy-ma35d1-usb2.c
+++ b/drivers/phy/nuvoton/phy-ma35d1-usb2.c
@@ -1,11 +1,15 @@
// SPDX-License-Identifier: GPL-2.0
/*
- * Copyright (C) 2024 Nuvoton Technology Corp.
+ * Nuvoton MA35D1 USB 2.0 PHY driver
+ *
+ * Supports PHY0 (USB0 OTG port, shared between DWC2 gadget and EHCI0/OHCI0)
+ * and PHY1 (USB1 host-only port, used by EHCI1/OHCI1). The hardware mux on
+ * PHY0 switches automatically via the USB ID pin.
+ *
+ * Copyright (C) 2026 Nuvoton Technology Corp.
*/
#include <linux/bitfield.h>
-#include <linux/clk.h>
#include <linux/delay.h>
-#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
@@ -13,131 +17,244 @@
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
+#include <linux/usb/role.h>
-/* USB PHY Miscellaneous Control Register */
-#define MA35_SYS_REG_USBPMISCR 0x60
-#define PHY0POR BIT(0) /* PHY Power-On Reset Control Bit */
-#define PHY0SUSPEND BIT(1) /* PHY Suspend; 0: suspend, 1: operaion */
-#define PHY0COMN BIT(2) /* PHY Common Block Power-Down Control */
-#define PHY0DEVCKSTB BIT(10) /* PHY 60 MHz UTMI clock stable bit */
+#define MA35_SYS_PWRONOTP 0x04
+#define PWRONOTP_USBP0ID BIT(16) /* USB0 ID pin state */
+
+#define MA35_SYS_USBPMISCR 0x60
+#define USBPMISCR_PHY_POR(n) BIT(0 + (n) * 16)
+#define USBPMISCR_PHY_SUSPEND(n) BIT(1 + (n) * 16)
+#define USBPMISCR_PHY_COMN(n) BIT(2 + (n) * 16)
+#define USBPMISCR_PHY_HSTCKSTB(n) BIT(8 + (n) * 16)
+#define USBPMISCR_PHY_CK12MSTB(n) BIT(9 + (n) * 16)
+#define USBPMISCR_PHY_DEVCKSTB(n) BIT(10 + (n) * 16)
+/* Mask for control bits (POR, SUSPEND, COMN) of one PHY */
+#define USBPMISCR_PHY_CTL_MASK(n) (0x7u << ((n) * 16))
+/* Host-mode ready: SUSPEND set */
+#define USBPMISCR_PHY_HOST_READY(n) (USBPMISCR_PHY_SUSPEND(n) | \
+ USBPMISCR_PHY_HSTCKSTB(n) | \
+ USBPMISCR_PHY_CK12MSTB(n))
+/* Device-mode ready: SUSPEND set */
+#define USBPMISCR_PHY_DEV_READY(n) (USBPMISCR_PHY_SUSPEND(n) | \
+ USBPMISCR_PHY_DEVCKSTB(n))
+/* RCALCODE: 4-bit resistor trim at bits [15:12] (PHY0) or [31:28] (PHY1) */
+#define USBPMISCR_RCAL_SHIFT(n) (12 + (n) * 16)
+#define USBPMISCR_RCAL_MASK(n) GENMASK(USBPMISCR_RCAL_SHIFT(n) + 3, \
+ USBPMISCR_RCAL_SHIFT(n))
+
+#define MA35_SYS_MISCFCR0 0x70
+/* Bit 12: USB host over-current detect polarity (shared, both ports) */
+#define MISCFCR0_UHOVRCURH BIT(12)
+
+#define MA35_PHY_NUM 2
+
+struct ma35_phy_port {
+ struct phy *phy;
+ unsigned int idx;
+};
struct ma35_usb_phy {
- struct clk *clk;
struct device *dev;
struct regmap *sysreg;
+ struct ma35_phy_port port[MA35_PHY_NUM];
+ struct usb_role_switch *role_sw;
};
-static int ma35_usb_phy_power_on(struct phy *phy)
+static int ma35_usb_phy_init(struct phy *phy)
{
- struct ma35_usb_phy *p_phy = phy_get_drvdata(phy);
+ struct ma35_phy_port *port = phy_get_drvdata(phy);
+ struct ma35_usb_phy *p = container_of(port - port->idx,
+ struct ma35_usb_phy, port[0]);
+ unsigned int n = port->idx;
unsigned int val;
int ret;
- ret = clk_prepare_enable(p_phy->clk);
- if (ret < 0) {
- dev_err(p_phy->dev, "Failed to enable PHY clock: %d\n", ret);
- return ret;
- }
+ regmap_read(p->sysreg, MA35_SYS_USBPMISCR, &val);
- regmap_read(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, &val);
- if (val & PHY0SUSPEND) {
- /*
- * USB PHY0 is in operation mode already
- * make sure USB PHY 60 MHz UTMI Interface Clock ready
- */
- ret = regmap_read_poll_timeout(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, val,
- val & PHY0DEVCKSTB, 10, 1000);
- if (ret == 0)
- return 0;
- }
+ if (val & USBPMISCR_PHY_SUSPEND(n))
+ return 0;
- /*
- * reset USB PHY0.
- * wait until USB PHY0 60 MHz UTMI Interface Clock ready
- */
- regmap_update_bits(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, 0x7, (PHY0POR | PHY0SUSPEND));
+ regmap_update_bits(p->sysreg, MA35_SYS_USBPMISCR,
+ USBPMISCR_PHY_CTL_MASK(n),
+ USBPMISCR_PHY_POR(n) | USBPMISCR_PHY_SUSPEND(n));
udelay(20);
- /* make USB PHY0 enter operation mode */
- regmap_update_bits(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, 0x7, PHY0SUSPEND);
+ regmap_update_bits(p->sysreg, MA35_SYS_USBPMISCR,
+ USBPMISCR_PHY_CTL_MASK(n),
+ USBPMISCR_PHY_SUSPEND(n));
+
+ if (n == 0) {
+ ret = regmap_read_poll_timeout(p->sysreg, MA35_SYS_USBPMISCR,
+ val,
+ ((val & USBPMISCR_PHY_HOST_READY(0)) ==
+ USBPMISCR_PHY_HOST_READY(0)) ||
+ ((val & USBPMISCR_PHY_DEV_READY(0)) ==
+ USBPMISCR_PHY_DEV_READY(0)),
+ 10, 1000);
+ } else {
+ ret = regmap_read_poll_timeout(p->sysreg, MA35_SYS_USBPMISCR,
+ val,
+ (val & USBPMISCR_PHY_HOST_READY(n)) ==
+ USBPMISCR_PHY_HOST_READY(n),
+ 10, 1000);
+ }
- /* make sure USB PHY 60 MHz UTMI Interface Clock ready */
- ret = regmap_read_poll_timeout(p_phy->sysreg, MA35_SYS_REG_USBPMISCR, val,
- val & PHY0DEVCKSTB, 10, 1000);
- if (ret == -ETIMEDOUT) {
- dev_err(p_phy->dev, "Check PHY clock, Timeout: %d\n", ret);
- clk_disable_unprepare(p_phy->clk);
+ if (ret) {
+ dev_err(p->dev, "USB PHY%u clock not stable (USBPMISCR=0x%08x)\n",
+ n, val);
return ret;
}
return 0;
}
-static int ma35_usb_phy_power_off(struct phy *phy)
+static const struct phy_ops ma35_usb_phy_ops = {
+ .init = ma35_usb_phy_init,
+ .owner = THIS_MODULE,
+};
+
+static int ma35_role_sw_set(struct usb_role_switch *sw, enum usb_role role)
+{
+ return -EOPNOTSUPP;
+}
+
+static enum usb_role ma35_role_sw_get(struct usb_role_switch *sw)
+{
+ struct ma35_usb_phy *p = usb_role_switch_get_drvdata(sw);
+ u32 val;
+
+ regmap_read(p->sysreg, MA35_SYS_PWRONOTP, &val);
+
+ return (val & PWRONOTP_USBP0ID) ? USB_ROLE_HOST : USB_ROLE_DEVICE;
+}
+
+static int ma35_role_switch_init(struct platform_device *pdev,
+ struct ma35_usb_phy *p)
{
- struct ma35_usb_phy *p_phy = phy_get_drvdata(phy);
+ struct usb_role_switch_desc sw_desc = {0};
+
+ sw_desc.set = ma35_role_sw_set;
+ sw_desc.get = ma35_role_sw_get;
+ sw_desc.allow_userspace_control = true;
+ sw_desc.driver_data = p;
+ sw_desc.fwnode = dev_fwnode(&pdev->dev);
+
+ p->role_sw = usb_role_switch_register(&pdev->dev, &sw_desc);
+ if (IS_ERR(p->role_sw))
+ return dev_err_probe(&pdev->dev, PTR_ERR(p->role_sw),
+ "failed to register role switch\n");
- clk_disable_unprepare(p_phy->clk);
return 0;
}
-static const struct phy_ops ma35_usb_phy_ops = {
- .power_on = ma35_usb_phy_power_on,
- .power_off = ma35_usb_phy_power_off,
- .owner = THIS_MODULE,
-};
+static void ma35_role_switch_exit(struct ma35_usb_phy *p)
+{
+ if (p->role_sw) {
+ usb_role_switch_unregister(p->role_sw);
+ p->role_sw = NULL;
+ }
+}
+
+static struct phy *ma35_usb_phy_xlate(struct device *dev,
+ const struct of_phandle_args *args)
+{
+ struct ma35_usb_phy *p = dev_get_drvdata(dev);
+
+ if (args->args[0] >= MA35_PHY_NUM)
+ return ERR_PTR(-EINVAL);
+
+ return p->port[args->args[0]].phy;
+}
static int ma35_usb_phy_probe(struct platform_device *pdev)
{
struct phy_provider *provider;
- struct ma35_usb_phy *p_phy;
- struct phy *phy;
+ struct ma35_usb_phy *p;
+ int n, ret;
+ u32 code;
- p_phy = devm_kzalloc(&pdev->dev, sizeof(*p_phy), GFP_KERNEL);
- if (!p_phy)
+ p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL);
+ if (!p)
return -ENOMEM;
- p_phy->dev = &pdev->dev;
- platform_set_drvdata(pdev, p_phy);
+ p->dev = &pdev->dev;
+ platform_set_drvdata(pdev, p);
+
+ p->sysreg = syscon_node_to_regmap(pdev->dev.parent->of_node);
+ if (IS_ERR(p->sysreg))
+ return dev_err_probe(&pdev->dev, PTR_ERR(p->sysreg),
+ "failed to get parent SYS regmap\n");
+
+ for (n = 0; n < MA35_PHY_NUM; n++) {
+ if (of_property_read_u32_index(pdev->dev.of_node,
+ "nuvoton,rcalcode", n, &code))
+ continue;
- p_phy->sysreg = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "nuvoton,sys");
- if (IS_ERR(p_phy->sysreg))
- return dev_err_probe(&pdev->dev, PTR_ERR(p_phy->sysreg),
- "Failed to get SYS registers\n");
+ if (code > 15)
+ return dev_err_probe(&pdev->dev, -EINVAL,
+ "rcalcode[%d] %u out of range (0-15)\n",
+ n, code);
- p_phy->clk = of_clk_get(pdev->dev.of_node, 0);
- if (IS_ERR(p_phy->clk))
- return dev_err_probe(&pdev->dev, PTR_ERR(p_phy->clk),
- "failed to find usb_phy clock\n");
+ regmap_update_bits(p->sysreg, MA35_SYS_USBPMISCR,
+ USBPMISCR_RCAL_MASK(n),
+ code << USBPMISCR_RCAL_SHIFT(n));
+ }
+
+ if (of_property_read_bool(pdev->dev.of_node, "nuvoton,oc-active-high"))
+ regmap_update_bits(p->sysreg, MA35_SYS_MISCFCR0,
+ MISCFCR0_UHOVRCURH, MISCFCR0_UHOVRCURH);
+
+ for (n = 0; n < MA35_PHY_NUM; n++) {
+ p->port[n].idx = n;
- phy = devm_phy_create(&pdev->dev, NULL, &ma35_usb_phy_ops);
- if (IS_ERR(phy))
- return dev_err_probe(&pdev->dev, PTR_ERR(phy), "Failed to create PHY\n");
+ p->port[n].phy = devm_phy_create(&pdev->dev, pdev->dev.of_node,
+ &ma35_usb_phy_ops);
+ if (IS_ERR(p->port[n].phy))
+ return dev_err_probe(&pdev->dev, PTR_ERR(p->port[n].phy),
+ "failed to create PHY%d\n", n);
- phy_set_drvdata(phy, p_phy);
+ phy_set_drvdata(p->port[n].phy, &p->port[n]);
+ }
+
+ ret = ma35_role_switch_init(pdev, p);
+ if (ret)
+ return ret;
- provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate);
- if (IS_ERR(provider))
+ provider = devm_of_phy_provider_register(&pdev->dev, ma35_usb_phy_xlate);
+ if (IS_ERR(provider)) {
+ ma35_role_switch_exit(p);
return dev_err_probe(&pdev->dev, PTR_ERR(provider),
- "Failed to register PHY provider\n");
+ "failed to register PHY provider\n");
+ }
+
return 0;
}
+static void ma35_usb_phy_remove(struct platform_device *pdev)
+{
+ struct ma35_usb_phy *p = platform_get_drvdata(pdev);
+
+ ma35_role_switch_exit(p);
+}
+
static const struct of_device_id ma35_usb_phy_of_match[] = {
- { .compatible = "nuvoton,ma35d1-usb2-phy", },
- { },
+ { .compatible = "nuvoton,ma35d1-usb2-phy" },
+ { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, ma35_usb_phy_of_match);
static struct platform_driver ma35_usb_phy_driver = {
.probe = ma35_usb_phy_probe,
- .driver = {
- .name = "ma35d1-usb2-phy",
- .of_match_table = ma35_usb_phy_of_match,
+ .remove = ma35_usb_phy_remove,
+ .driver = {
+ .name = "ma35d1-usb2-phy",
+ .of_match_table = ma35_usb_phy_of_match,
},
};
module_platform_driver(ma35_usb_phy_driver);
MODULE_DESCRIPTION("Nuvoton ma35d1 USB2.0 PHY driver");
MODULE_AUTHOR("Hui-Ping Chen <hpchen0nvt@gmail.com>");
+MODULE_AUTHOR("Joey Lu <a0987203069@gmail.com>");
MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related
* [PATCH v2 3/4] arm64: dts: nuvoton: ma35d1: add USB controllers and dual-port PHY node
From: Joey Lu @ 2026-06-25 2:39 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann,
Catalin Marinas, Jacky Huang, Shan-Chun Hung, Hui-Ping Chen,
Joey Lu, linux-phy, devicetree, linux-arm-kernel, linux-kernel,
Joey Lu
In-Reply-To: <20260625023958.569299-1-a0987203069@gmail.com>
Add device tree nodes for the MA35D1 USB subsystem:
- sys node gains simple-mfd + address/size-cells so it can contain
the usb-phy@60 child.
- usb-phy@60 is added as a child of sys, using the combined
nuvoton,ma35d1-usb2-phy driver with #phy-cells = <1>. No clock
properties: clock gating is handled by each controller node.
- DWC2 gadget (usb@40200000), EHCI0/1, and OHCI0/1 nodes are
added. Each controller names its clock gate directly and
references the PHY by index (0 for the OTG port, 1 for the
dedicated host port).
- Board files (ma35d1-som-256m.dts, ma35d1-iot-512m.dts) enable the
PHY, dwc2, ehci0/1, and ohci0/1 nodes and add pinctrl for the
HSUSB signals (VBUSVLD, PWREN, OVC).
Signed-off-by: Joey Lu <a0987203069@gmail.com>
---
.../boot/dts/nuvoton/ma35d1-iot-512m.dts | 36 ++++++++++
.../boot/dts/nuvoton/ma35d1-som-256m.dts | 36 ++++++++++
arch/arm64/boot/dts/nuvoton/ma35d1.dtsi | 68 ++++++++++++++++++-
3 files changed, 139 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts b/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts
index 9482bec1aa57..32fea36da7f4 100644
--- a/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts
+++ b/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts
@@ -95,6 +95,16 @@ pinctrl_uart14: uart14-pins {
power-source = <1>;
};
};
+
+ hsusb {
+ pinctrl_hsusb: hsusb-pins {
+ nuvoton,pins = <5 15 1>, /* VBUSVLD */
+ <11 12 9>, /* PWREN */
+ <11 13 9>; /* OVC */
+ bias-disable;
+ power-source = <1>;
+ };
+ };
};
&uart0 {
@@ -126,3 +136,29 @@ &uart14 {
pinctrl-0 = <&pinctrl_uart14>;
status = "okay";
};
+
+&usb_phy {
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_hsusb>;
+ status = "okay";
+};
+
+&usb {
+ status = "okay";
+};
+
+&ehci0 {
+ status = "okay";
+};
+
+&ehci1 {
+ status = "okay";
+};
+
+&ohci0 {
+ status = "okay";
+};
+
+&ohci1 {
+ status = "okay";
+};
diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts b/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts
index f6f20a17e501..85d1c5db8bd9 100644
--- a/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts
+++ b/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts
@@ -98,6 +98,16 @@ pinctrl_uart16: uart16-pins {
power-source = <1>;
};
};
+
+ hsusb {
+ pinctrl_hsusb: hsusb-pins {
+ nuvoton,pins = <5 15 1>, /* VBUSVLD */
+ <11 12 9>, /* PWREN */
+ <11 13 9>; /* OVC */
+ bias-disable;
+ power-source = <1>;
+ };
+ };
};
&uart0 {
@@ -129,3 +139,29 @@ &uart16 {
pinctrl-0 = <&pinctrl_uart16>;
status = "okay";
};
+
+&usb_phy {
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_hsusb>;
+ status = "okay";
+};
+
+&usb {
+ status = "okay";
+};
+
+&ehci0 {
+ status = "okay";
+};
+
+&ehci1 {
+ status = "okay";
+};
+
+&ohci0 {
+ status = "okay";
+};
+
+&ohci1 {
+ status = "okay";
+};
diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
index e51b98f5bdce..a6a354f28311 100644
--- a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
+++ b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
@@ -83,9 +83,18 @@ soc {
ranges;
sys: system-management@40460000 {
- compatible = "nuvoton,ma35d1-reset", "syscon";
+ compatible = "nuvoton,ma35d1-reset", "syscon", "simple-mfd";
reg = <0x0 0x40460000 0x0 0x200>;
#reset-cells = <1>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ usb_phy: usb-phy@60 {
+ compatible = "nuvoton,ma35d1-usb2-phy";
+ reg = <0x60 0x14>;
+ #phy-cells = <1>;
+ status = "disabled";
+ };
};
clk: clock-controller@40460200 {
@@ -379,5 +388,62 @@ uart16: serial@40880000 {
clocks = <&clk UART16_GATE>;
status = "disabled";
};
+
+ usb: usb@40200000 {
+ compatible = "snps,dwc2";
+ reg = <0x0 0x40200000 0x0 0x1000>;
+ interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk USBD_GATE>;
+ clock-names = "otg";
+ phys = <&usb_phy 0>;
+ phy-names = "usb2-phy";
+ dr_mode = "peripheral";
+ g-np-tx-fifo-size = <16>;
+ g-rx-fifo-size = <0x100>;
+ g-tx-fifo-size = <256 256 64 64 64 32 32 32>;
+ status = "disabled";
+ };
+
+ ehci0: usb@40140000 {
+ compatible = "generic-ehci";
+ reg = <0x0 0x40140000 0x0 0x1000>;
+ interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk HUSBH0_GATE>;
+ phys = <&usb_phy 0>;
+ phy-names = "usb";
+ companion = <&ohci0>;
+ status = "disabled";
+ };
+
+ ehci1: usb@401c0000 {
+ compatible = "generic-ehci";
+ reg = <0x0 0x401c0000 0x0 0x1000>;
+ interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk HUSBH1_GATE>;
+ phys = <&usb_phy 1>;
+ phy-names = "usb";
+ companion = <&ohci1>;
+ status = "disabled";
+ };
+
+ ohci0: usb@40150000 {
+ compatible = "generic-ohci";
+ reg = <0x0 0x40150000 0x0 0x1000>;
+ interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk HUSBH0_GATE>;
+ phys = <&usb_phy 0>;
+ phy-names = "usb";
+ status = "disabled";
+ };
+
+ ohci1: usb@401d0000 {
+ compatible = "generic-ohci";
+ reg = <0x0 0x401d0000 0x0 0x1000>;
+ interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk HUSBH1_GATE>;
+ phys = <&usb_phy 1>;
+ phy-names = "usb";
+ status = "disabled";
+ };
};
};
--
2.43.0
^ permalink raw reply related
* [PATCH v2 2/4] dt-bindings: phy: nuvoton,ma35d1-usb2-phy: extend for dual-port OTG support
From: Joey Lu @ 2026-06-25 2:39 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann,
Catalin Marinas, Jacky Huang, Shan-Chun Hung, Hui-Ping Chen,
Joey Lu, linux-phy, devicetree, linux-arm-kernel, linux-kernel,
Joey Lu
In-Reply-To: <20260625023958.569299-1-a0987203069@gmail.com>
The MA35D1 has two USB PHY ports managed by the same hardware block:
- PHY0 (index 0): OTG port shared between the DWC2 gadget controller
and EHCI0/OHCI0 host controllers. A hardware mux follows the USB
ID pin automatically.
- PHY1 (index 1): dedicated host-only port for EHCI1/OHCI1.
Extend the existing binding to cover both ports:
- The PHY node is now a child of the system-management syscon node
with a reg property. The nuvoton,sys phandle and clocks properties
are removed; the driver derives the regmap from its parent, and
clock gating is owned by each individual USB controller.
- #phy-cells changes from 0 to 1: the cell selects the PHY port.
- Two optional board-tuning properties are added: nuvoton,rcalcode
for per-port resistor trim and nuvoton,oc-active-high for
over-current polarity.
Signed-off-by: Joey Lu <a0987203069@gmail.com>
---
.../bindings/phy/nuvoton,ma35d1-usb2-phy.yaml | 62 ++++++++++++++-----
1 file changed, 48 insertions(+), 14 deletions(-)
diff --git a/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml b/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml
index fff858c909a0..a20d03c80932 100644
--- a/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml
+++ b/Documentation/devicetree/bindings/phy/nuvoton,ma35d1-usb2-phy.yaml
@@ -8,38 +8,72 @@ title: Nuvoton MA35D1 USB2 phy
maintainers:
- Hui-Ping Chen <hpchen0nvt@gmail.com>
+ - Joey Lu <yclu4@nuvoton.com>
+
+description:
+ USB 2.0 PHY for the Nuvoton MA35D1 SoC. The PHY node is a child of the
+ system-management syscon node and covers both PHY ports.
+
+ PHY0 (index 0) is the OTG port whose signals are routed to either the DWC2
+ gadget controller or the EHCI0/OHCI0 host controller by a hardware mux that
+ follows the USB ID pin automatically.
+
+ PHY1 (index 1) is a dedicated host-only port used by EHCI1/OHCI1.
properties:
compatible:
enum:
- nuvoton,ma35d1-usb2-phy
+ reg:
+ maxItems: 1
+
"#phy-cells":
- const: 0
+ const: 1
+ description:
+ The single cell selects the PHY port. 0 selects the OTG port (USB0,
+ shared with DWC2 gadget controller) and 1 selects the host-only port
+ (USB1).
- clocks:
- maxItems: 1
+ nuvoton,rcalcode:
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ minItems: 1
+ maxItems: 2
+ items:
+ minimum: 0
+ maximum: 15
+ description:
+ Resistor calibration trim codes for PHY0 and PHY1 respectively.
+ Each 4-bit value is written to the RCALCODE field in USBPMISCR and
+ adjusts the PHY's internal termination resistance. Both entries are
+ optional; when absent the hardware reset default is used.
- nuvoton,sys:
- $ref: /schemas/types.yaml#/definitions/phandle
+ nuvoton,oc-active-high:
+ type: boolean
description:
- phandle to syscon for checking the PHY clock status.
+ When present, the over-current detect input from the VBUS power switch
+ is treated as active-high. The default (property absent) is active-low.
+ This setting is shared by both USB host ports.
required:
- compatible
+ - reg
- "#phy-cells"
- - clocks
- - nuvoton,sys
additionalProperties: false
examples:
- |
- #include <dt-bindings/clock/nuvoton,ma35d1-clk.h>
+ system-management@40460000 {
+ compatible = "nuvoton,ma35d1-reset", "syscon", "simple-mfd";
+ reg = <0x40460000 0x200>;
+ #reset-cells = <1>;
+ #address-cells = <1>;
+ #size-cells = <1>;
- usb_phy: usb-phy {
- compatible = "nuvoton,ma35d1-usb2-phy";
- clocks = <&clk USBD_GATE>;
- nuvoton,sys = <&sys>;
- #phy-cells = <0>;
+ usb-phy@60 {
+ compatible = "nuvoton,ma35d1-usb2-phy";
+ reg = <0x60 0x14>;
+ #phy-cells = <1>;
+ };
};
--
2.43.0
^ permalink raw reply related
* [PATCH v2 1/4] dt-bindings: reset: nuvoton,ma35d1-reset: add simple-mfd and child node support
From: Joey Lu @ 2026-06-25 2:39 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann,
Catalin Marinas, Jacky Huang, Shan-Chun Hung, Hui-Ping Chen,
Joey Lu, linux-phy, devicetree, linux-arm-kernel, linux-kernel,
Joey Lu
In-Reply-To: <20260625023958.569299-1-a0987203069@gmail.com>
The MA35D1 system-management syscon node hosts the USB PHY register
block at offset 0x60. To model usb-phy@60 as a DT child of the syscon
node the binding must allow:
- simple-mfd as an optional third compatible so the MFD core can
instantiate child platform devices.
- #address-cells and #size-cells (each const: 1) so child nodes can
carry a reg property.
- An open child-node pattern (patternProperties "^.*@[0-9a-f]+$")
to pass dt-schema validation.
Signed-off-by: Joey Lu <a0987203069@gmail.com>
---
.../bindings/reset/nuvoton,ma35d1-reset.yaml | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml b/Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml
index 3ce7dcecd87a..1fda7e8f4b5d 100644
--- a/Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml
+++ b/Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml
@@ -19,6 +19,8 @@ properties:
items:
- const: nuvoton,ma35d1-reset
- const: syscon
+ - const: simple-mfd
+ minItems: 2
reg:
maxItems: 1
@@ -26,6 +28,16 @@ properties:
'#reset-cells':
const: 1
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 1
+
+patternProperties:
+ "^.*@[0-9a-f]+$":
+ type: object
+
required:
- compatible
- reg
@@ -43,4 +55,3 @@ examples:
#reset-cells = <1>;
};
...
-
--
2.43.0
^ permalink raw reply related
* [PATCH v2 0/4] phy: nuvoton: extend MA35D1 USB2 PHY driver for dual-port OTG support
From: Joey Lu @ 2026-06-25 2:39 UTC (permalink / raw)
To: Vinod Koul, Neil Armstrong
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Arnd Bergmann,
Catalin Marinas, Jacky Huang, Shan-Chun Hung, Hui-Ping Chen,
Joey Lu, linux-phy, devicetree, linux-arm-kernel, linux-kernel,
Joey Lu
The MA35D1 SoC has two USB PHY ports managed by a shared hardware block:
- PHY0 (USB0): OTG port shared between the DWC2 gadget controller and
the EHCI0/OHCI0 host controllers. A hardware mux automatically routes
USB0 signals to the correct controller based on the USB ID pin.
- PHY1 (USB1): dedicated host-only port for EHCI1/OHCI1.
A previous series [1] added a separate phy-ma35d1-otg.c driver for this.
Following reviewer suggestion to reuse the existing phy-ma35d1-usb2.c
driver rather than introduce a new one, that series has been dropped and
this series instead extends the existing driver.
Changes in this series:
Patch 1 (new) adds simple-mfd support to the nuvoton,ma35d1-reset
syscon binding. The sys node needs to act as an MFD parent so that
usb-phy@60 can be its DT child. This patch is a prerequisite for
patch 2 and has no functional impact on existing users of the syscon.
Patch 2 updates the nuvoton,ma35d1-usb2-phy binding: the PHY node
becomes a child of the syscon node (reg = <0x60 0x14>), nuvoton,sys
phandle and clocks are removed, and #phy-cells changes from 0 to 1
for per-port selection. Optional nuvoton,rcalcode and
nuvoton,oc-active-high properties are added.
Patch 3 updates the MA35D1 DTS: sys gains simple-mfd, usb-phy@60 is
added as a syscon child, and DWC2/EHCI0/EHCI1/OHCI0/OHCI1 nodes are
added. Board files enable the nodes and add pinctrl for the HSUSB
signals (VBUSVLD, PWREN, OVC).
Patch 4 extends phy-ma35d1-usb2.c: a loop creates two struct phy
objects; a unified .init handles both ports with parametric register
macros; clock management is removed (each controller gates its own
clock); a read-only USB role switch is registered for PHY0 reporting
the USB ID pin via PWRONOTP[16].
Changes since v1:
- New patch 1: nuvoton,ma35d1-reset binding extended for simple-mfd
and child node support; required as a prerequisite for the PHY
binding which places usb-phy@60 as a syscon child.
- Patch 2 (was patch 1): nuvoton,rcalcode description updated to
clarify that both PHY entries are individually optional; example
reg corrected from 4-cell to 2-cell format.
- Patch 3 (was patch 2): ehci1 node address corrected to lowercase
(401c0000).
- Patch 4 (was patch 3): register definition section header comments
added; HOST_READY and DEV_READY macro comments made more
descriptive; ma35_role_switch_exit() added to the PHY provider
registration error path to prevent a role switch leak.
Link: [1] https://lore.kernel.org/linux-phy/20260604101220.1092822-1-a0987203069@gmail.com/T/#t
Joey Lu (4):
dt-bindings: reset: nuvoton,ma35d1-reset: add simple-mfd and child
node support
dt-bindings: phy: nuvoton,ma35d1-usb2-phy: extend for dual-port OTG
support
arm64: dts: nuvoton: ma35d1: add USB controllers and dual-port PHY
node
phy: nuvoton: phy-ma35d1-usb2: extend to dual-port with OTG support
.../bindings/phy/nuvoton,ma35d1-usb2-phy.yaml | 62 +++-
.../bindings/reset/nuvoton,ma35d1-reset.yaml | 13 +-
.../boot/dts/nuvoton/ma35d1-iot-512m.dts | 36 +++
.../boot/dts/nuvoton/ma35d1-som-256m.dts | 36 +++
arch/arm64/boot/dts/nuvoton/ma35d1.dtsi | 68 ++++-
drivers/phy/nuvoton/phy-ma35d1-usb2.c | 267 +++++++++++++-----
6 files changed, 391 insertions(+), 91 deletions(-)
--
2.43.0
^ permalink raw reply
* Re: [PATCH 1/2] regulator: dt-bindings: rtq2208: Label mtp-sel-high property as deprecated
From: sashiko-bot @ 2026-06-25 2:26 UTC (permalink / raw)
To: cy_huang; +Cc: conor+dt, devicetree, robh
In-Reply-To: <d111d67d40cc182a2ab8c6292e4ffff9c7dad9ba.1782353659.git.cy_huang@richtek.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The deprecated property `richtek,mtp-sel-high` remains in the YAML example block.
--
commit 34889eeeff15702a135fa4ac9e6b9f3ab88ee425
Author: ChiYuan Huang <cy_huang@richtek.com>
regulator: dt-bindings: rtq2208: Label mtp-sel-high property as deprecated
Since it can be identified by hardware register, label the unnecessary
property 'richtek,mtp-sel-high' as deprecated.
> diff --git a/Documentation/devicetree/bindings/regulator/richtek,rtq2208.yaml b/Documentation/devicetree/bindings/regulator/richtek,rtq2208.yaml
> --- a/Documentation/devicetree/bindings/regulator/richtek,rtq2208.yaml
> +++ b/Documentation/devicetree/bindings/regulator/richtek,rtq2208.yaml
> @@ -42,11 +42,14 @@ properties:
>
> richtek,mtp-sel-high:
> type: boolean
> + deprecated: true
> description:
> vout register selection based on this boolean value.
> false - Using DVS0 register setting to adjust vout
> true - Using DVS1 register setting to adjust vout
>
> + The property is now deprecated. Will be identified by RG HW register.
> +
> regulators:
[Severity: Low]
Since this property is now marked as deprecated, should it also be removed
from the example device tree in this binding?
Documentation/devicetree/bindings/regulator/richtek,rtq2208.yaml:examples:
...
pmic@10 {
compatible = "richtek,rtq2208";
reg = <0x10>;
interrupts-extended = <&gpio26 0 IRQ_TYPE_LEVEL_LOW>;
richtek,mtp-sel-high;
regulators {
...
Leaving the deprecated property in the example encourages its continued use
by developers who might copy and paste binding examples into their device
trees.
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1782353659.git.cy_huang@richtek.com?part=1
^ permalink raw reply
* Re: [PATCH 1/8] clk: qcom: dispcc-sm8450: Fix mdss clocks
From: Esteban Urrutia @ 2026-06-25 2:22 UTC (permalink / raw)
To: Konrad Dybcio, Bjorn Andersson, Michael Turquette, Stephen Boyd,
Brian Masney, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Rob Clark, Will Deacon, Robin Murphy,
Joerg Roedel (AMD), Vinod Koul, Neil Armstrong
Cc: linux-arm-msm, linux-clk, linux-kernel, devicetree, iommu,
linux-arm-kernel, linux-phy
In-Reply-To: <65873506-1a9a-40ec-ac67-60f61a0b4b4c@oss.qualcomm.com>
On 6/23/26 11:50 AM, Konrad Dybcio wrote:
> This can also be fixed by migrating to use qcom_cc_driver_data,
> which takes a list of alpha PLLs to be configured, and thenthere's
> a switch-statement in clk-alpha-pll.c that always assigns the
> correct function
If this is done, should a patch that migrates to qcom_cc_driver_data and a
patch that fixes the issue be sent, or should only a single patch be sent?
Regards,
Esteban
^ permalink raw reply
* [PATCH 0/2] rtq2208: Remove the unnecessary MTP_SEL property
From: cy_huang @ 2026-06-25 2:18 UTC (permalink / raw)
To: Mark Brown, Krzysztof Kozlowski
Cc: Rob Herring, Conor Dooley, Liam Girdwood, ChiYuan Huang,
Yoon Dong Min, edward_kim, devicetree, linux-kernel
From: ChiYuan Huang <cy_huang@richtek.com>
This patch series remove the 'richtek,mtp-sel-high' property usage.
ChiYuan Huang (2):
regulator: dt-bindings: rtq2208: Label mtp-sel-high property as
deprecated
regualtor: rtq2208: Initiate the default MTP_SEL state by hardware
register
.../devicetree/bindings/regulator/richtek,rtq2208.yaml | 3 +++
drivers/regulator/rtq2208-regulator.c | 7 ++++++-
2 files changed, 9 insertions(+), 1 deletion(-)
--
2.43.0
^ permalink raw reply
* [PATCH 1/2] regulator: dt-bindings: rtq2208: Label mtp-sel-high property as deprecated
From: cy_huang @ 2026-06-25 2:18 UTC (permalink / raw)
To: Mark Brown, Krzysztof Kozlowski
Cc: Rob Herring, Conor Dooley, Liam Girdwood, ChiYuan Huang,
Yoon Dong Min, edward_kim, devicetree, linux-kernel
In-Reply-To: <cover.1782353659.git.cy_huang@richtek.com>
From: ChiYuan Huang <cy_huang@richtek.com>
Since it can be identified by hardware register, label the unnecessary
property 'richtek,mtp-sel-high' as deprecated.
Signed-off-by: ChiYuan Huang <cy_huang@richtek.com>
---
.../devicetree/bindings/regulator/richtek,rtq2208.yaml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/Documentation/devicetree/bindings/regulator/richtek,rtq2208.yaml b/Documentation/devicetree/bindings/regulator/richtek,rtq2208.yaml
index 022c1f197364..08648916e1fb 100644
--- a/Documentation/devicetree/bindings/regulator/richtek,rtq2208.yaml
+++ b/Documentation/devicetree/bindings/regulator/richtek,rtq2208.yaml
@@ -42,11 +42,14 @@ properties:
richtek,mtp-sel-high:
type: boolean
+ deprecated: true
description:
vout register selection based on this boolean value.
false - Using DVS0 register setting to adjust vout
true - Using DVS1 register setting to adjust vout
+ The property is now deprecated. Will be identified by RG HW register.
+
regulators:
type: object
additionalProperties: false
--
2.43.0
^ permalink raw reply related
* [PATCH 2/2] regualtor: rtq2208: Initiate the default MTP_SEL state by hardware register
From: cy_huang @ 2026-06-25 2:18 UTC (permalink / raw)
To: Mark Brown, Krzysztof Kozlowski
Cc: Rob Herring, Conor Dooley, Liam Girdwood, ChiYuan Huang,
Yoon Dong Min, edward_kim, devicetree, linux-kernel
In-Reply-To: <cover.1782353659.git.cy_huang@richtek.com>
From: ChiYuan Huang <cy_huang@richtek.com>
Read the initial MTP_SEL state by hardware register to prevent the wrong
specified property value from the conflict of hardware pin assignment.
Signed-off-by: ChiYuan Huang <cy_huang@richtek.com>
---
drivers/regulator/rtq2208-regulator.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/drivers/regulator/rtq2208-regulator.c b/drivers/regulator/rtq2208-regulator.c
index f669a562f036..7fe082def494 100644
--- a/drivers/regulator/rtq2208-regulator.c
+++ b/drivers/regulator/rtq2208-regulator.c
@@ -12,6 +12,7 @@
#include <linux/mod_devicetable.h>
/* Register */
+#define RTQ2208_REG_FSOUTB_CNTL 0x11
#define RTQ2208_REG_GLOBAL_INT1 0x12
#define RTQ2208_REG_FLT_RECORDBUCK_CB 0x18
#define RTQ2208_REG_GLOBAL_INT1_MASK 0x1D
@@ -34,6 +35,7 @@
#define RTQ2208_REG_HIDDEN1 0xFF
/* Mask */
+#define RTQ2208_MTP_SEL_RO_MASK BIT(7)
#define RTQ2208_BUCK_NR_MTP_SEL_MASK GENMASK(7, 0)
#define RTQ2208_BUCK_EN_NR_MTP_SEL0_MASK BIT(0)
#define RTQ2208_BUCK_EN_NR_MTP_SEL1_MASK BIT(1)
@@ -465,10 +467,13 @@ static int rtq2208_parse_regulator_dt_data(int n_regulator, const unsigned int *
struct rtq2208_regulator_desc *rdesc[RTQ2208_LDO_MAX], struct device *dev,
unsigned int ldo1_fixed, unsigned int ldo2_fixed)
{
+ struct regmap *regmap = dev_get_regmap(dev, NULL);
int mtp_sel, i, idx;
/* get mtp_sel0 or mtp_sel1 */
- mtp_sel = device_property_read_bool(dev, "richtek,mtp-sel-high");
+ mtp_sel = regmap_test_bits(regmap, RTQ2208_REG_FSOUTB_CNTL, RTQ2208_MTP_SEL_RO_MASK);
+ if (mtp_sel < 0)
+ return dev_err_probe(dev, mtp_sel, "Failed to init mtp_sel state\n");
for (i = 0; i < n_regulator; i++) {
idx = regulator_idx_table[i];
--
2.43.0
^ permalink raw reply related
* Re: [PATCH v2 3/3] input: misc: Add Qualcomm SPMI PMIC haptics driver
From: sashiko-bot @ 2026-06-25 2:13 UTC (permalink / raw)
To: Fenglin Wu; +Cc: linux-input, dmitry.torokhov, conor+dt, devicetree, robh
In-Reply-To: <20260624-qcom-spmi-haptics-v2-3-b9118e60f3e3@oss.qualcomm.com>
Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [High] Concurrent effect uploads or erasures free `fifo_data` while it is actively used by the playback worker or IRQ handler, leading to a Use-After-Free.
- [High] System suspend races with the asynchronous playback worker, leaving the hardware active during sleep and leaking a PM runtime reference.
- [High] Incorrect teardown ordering allows userspace to trigger Use-After-Free of driver structures after removal.
- [High] The driver fails to terminate FIFO playback when finished, permanently leaking a PM runtime reference.
- [Medium] Unbounded user-controlled kmalloc size allows memory exhaustion.
- [Medium] Missing signedness check on the custom play rate allows negative values to bypass boundary checks.
- [Medium] Lockless, non-atomic accesses to `play_request` and `cur_effect_id` cause new play requests to be lost.
- [Medium] The runtime PM usage counter is leaked during device removal if playback is active.
--
commit 06679c85ddc3489b6ff4ed3aec7d938287e570cd
Author: Fenglin Wu <fenglin.wu@oss.qualcomm.com>
input: misc: Add Qualcomm SPMI PMIC haptics driver
Add an initial driver for the Qualcomm PMIH0108 PMIC haptics module,
named as HAP530_HV. This module supports several play modes, including
DIRECT_PLAY, FIFO, PAT_MEM, and SWR, each with distinct data sourcing
and hardware data handling logic. Currently, the driver provides support
for two play modes using the input force-feedback framework: FF_CONSTANT
effect for DIRECT_PLAY mode and FF_PERIODIC effect with FF_CUSTOM
waveform for FIFO mode.
> diff --git a/drivers/input/misc/qcom-spmi-haptics.c b/drivers/input/misc/qcom-spmi-haptics.c
> new file mode 100644
> index 0000000000000..4b27638df9605
> --- /dev/null
> +++ b/drivers/input/misc/qcom-spmi-haptics.c
[ ... ]
> +static irqreturn_t haptics_fifo_empty_irq(int irq, void *dev_id)
> +{
> + struct qcom_haptics *h = dev_id;
> + u32 sts, to_write;
> + int ret;
> +
> + ret = regmap_read(h->regmap,
> + h->cfg_base + HAP_CFG_INT_RT_STS_REG, &sts);
> + if (ret || !(sts & FIFO_EMPTY_BIT))
> + return IRQ_HANDLED;
> +
> + mutex_lock(&h->fifo_lock);
> +
> + if (!h->fifo_data) {
> + mutex_unlock(&h->fifo_lock);
> + return IRQ_HANDLED;
> + }
> +
> + if (h->data_done) {
> + ptn_write(h, HAP_PTN_FIFO_EMPTY_CFG_REG, 0);
> + h->fifo_data = NULL;
> + h->play_request = false;
> + schedule_work(&h->play_work);
> + mutex_unlock(&h->fifo_lock);
> + return IRQ_HANDLED;
> + }
> +
> + /* Refill: write the next chunk, conservatively sized to the threshold */
> + to_write = min_t(u32, h->data_len - h->data_written,
> + h->fifo_len - FIFO_EMPTY_THRESH);
> + haptics_write_fifo_chunk(h, &h->fifo_data[h->data_written], to_write);
[Severity: High]
Does this code dereference a freed pointer?
A userspace process can start a FIFO effect, then concurrently call
EVIOCSFF (to re-upload) or EVIOCRMFF (to erase) the effect.
The input core calls haptics_erase() or haptics_upload_effect(), which
immediately execute kfree(priv->fifo_data).
Since the IRQ handler or worker continues to use h->fifo_data under
h->fifo_lock, it appears it will dereference the freed memory.
> + h->data_written += to_write;
> +
> + if (h->data_written >= h->data_len) {
> + /* Last chunk enqueued; disable threshold to stop further IRQs */
> + h->data_done = true;
> + ptn_write(h, HAP_PTN_FIFO_EMPTY_CFG_REG, 0);
[Severity: High]
Does this fail to terminate FIFO playback when finished?
When the final chunk is written, the code sets FIFO_EMPTY_CFG_REG to 0,
disabling the empty interrupt.
Because the interrupt is disabled, the handler will never fire again to
process the h->data_done block at the start of the function. This leaves
the hardware active indefinitely and leaks the PM runtime usage counter.
> + }
> +
> + mutex_unlock(&h->fifo_lock);
> + return IRQ_HANDLED;
> +}
[ ... ]
> +static int haptics_upload_effect(struct input_dev *dev,
> + struct ff_effect *effect,
> + struct ff_effect *old)
> +{
> + struct qcom_haptics *h = input_get_drvdata(dev);
> + struct qcom_haptics_effect *priv;
> + int id = effect->id;
> + u32 data_len;
> + s16 *buf;
> + s8 *fifo;
> +
> + if (id < 0 || id >= HAPTICS_MAX_EFFECTS)
> + return -EINVAL;
> +
> + priv = &h->effects[id];
> +
> + switch (effect->type) {
> + case FF_CONSTANT:
> + kfree(priv->fifo_data);
> + priv->fifo_data = NULL;
> + priv->data_len = 0;
> + priv->mode = HAPTICS_DIRECT_PLAY;
> + return 0;
> +
> + case FF_PERIODIC:
> + if (effect->u.periodic.waveform != FF_CUSTOM)
> + return -EINVAL;
> + /*
> + * Minimum 3 elements: play-rate code + vmax + at least one sample.
> + * No upper bound: the FIFO is refilled continuously from the IRQ
> + * handler, so any length of PCM data is supported.
> + */
> + if (effect->u.periodic.custom_len < 3)
> + return -EINVAL;
> +
> + buf = memdup_array_user(effect->u.periodic.custom_data,
> + effect->u.periodic.custom_len,
> + sizeof(s16));
[Severity: Medium]
Is there a missing upper bound check on custom_len?
A local user with write access to the input device can provide a very
large custom_len, and this directly allocates kernel memory without an
upper bound, which could lead to memory exhaustion.
> + if (IS_ERR(buf))
> + return PTR_ERR(buf);
> +
> + if (buf[CUSTOM_DATA_RATE_IDX] > PLAY_RATE_MAX) {
[Severity: Medium]
Can negative values bypass this boundary check?
buf is an array of signed 16-bit integers. A user passing a negative value
would bypass this check, get cast to u8 later, and be written to the
hardware registers as an out-of-bounds unsigned value.
> + kfree(buf);
> + return -EINVAL;
> + }
[ ... ]
> +static int haptics_playback(struct input_dev *dev, int effect_id, int val)
> +{
> + struct qcom_haptics *h = input_get_drvdata(dev);
> +
> + h->cur_effect_id = effect_id;
> + h->play_request = (val > 0);
[Severity: Medium]
Are play requests occasionally lost due to lockless accesses?
If the IRQ handler overwrites h->play_request to false immediately after a
new play event from userspace sets it to true here, the new request seems
to be lost. The worker will see play_request == false and stop the
hardware instead of starting the new effect.
> + schedule_work(&h->play_work);
> +
> + return 0;
> +}
[ ... ]
> +static void qcom_haptics_remove(struct platform_device *pdev)
> +{
> + struct qcom_haptics *h = platform_get_drvdata(pdev);
> + int i;
> +
> + pm_runtime_disable(&pdev->dev);
> + pm_runtime_set_suspended(&pdev->dev);
> +
> + mutex_lock(&h->play_lock);
> + haptics_stop_locked(h);
> + mutex_unlock(&h->play_lock);
[Severity: Medium]
Does this leak the runtime PM usage counter?
If remove is invoked while a haptics effect is actively playing,
h->pm_ref_held is true. This stops the hardware but does not call
pm_runtime_put_noidle(dev) to release the reference, leaving the PM
state unbalanced.
> +
> + haptics_enable_module(h, false);
> + cancel_work_sync(&h->play_work);
> + for (i = 0; i < HAPTICS_MAX_EFFECTS; i++) {
> + kfree(h->effects[i].fifo_data);
> + h->effects[i].fifo_data = NULL;
> + }
> +
> + input_unregister_device(h->input);
[Severity: High]
Is it safe to tear down resources before unregistering the input device?
Because the input device is still registered and active, an unprivileged
user can concurrently issue an ioctl (like EVIOCSFF or EVIOCRMFF) on the
open event node. This could access the freed effect structures or schedule
h->play_work after they have been destroyed.
> +}
[ ... ]
> +static int qcom_haptics_suspend(struct device *dev)
> +{
> + struct qcom_haptics *h = dev_get_drvdata(dev);
> +
> + mutex_lock(&h->play_lock);
> + haptics_stop_locked(h);
> + if (h->pm_ref_held) {
> + pm_runtime_put_noidle(dev);
> + h->pm_ref_held = false;
> + }
> + mutex_unlock(&h->play_lock);
[Severity: High]
Does this system suspend race with the asynchronous playback worker?
If userspace requests playback right before system suspend, h->play_request
is set to true and play_work is scheduled.
This suspend function stops the hardware but forgets to clear
h->play_request.
When this drops the lock, play_work can execute, see play_request is still
true, and re-enable the hardware just before the system suspends.
> +
> + cancel_work_sync(&h->play_work);
> + return pm_runtime_force_suspend(dev);
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260624-qcom-spmi-haptics-v2-0-b9118e60f3e3@oss.qualcomm.com?part=3
^ permalink raw reply
* RE: [PATCH v6 9/9] arm64: dts: freescale: imx95: Add video codec node
From: Nas Chung @ 2026-06-25 2:11 UTC (permalink / raw)
To: Francesco Dolcini
Cc: mchehab@kernel.org, hverkuil@xs4all.nl, robh@kernel.org,
krzk+dt@kernel.org, conor+dt@kernel.org, shawnguo@kernel.org,
s.hauer@pengutronix.de, linux-media@vger.kernel.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-imx@nxp.com, linux-arm-kernel@lists.infradead.org,
jackson.lee, lafley.kim, marek.vasut@mailbox.org
In-Reply-To: <20260624115050.GA38214@francesco-nb>
Hi, Francesco.
>-----Original Message-----
>From: Francesco Dolcini <francesco@dolcini.it>
>Sent: Wednesday, June 24, 2026 8:51 PM
>To: Nas Chung <nas.chung@chipsnmedia.com>
>Cc: mchehab@kernel.org; hverkuil@xs4all.nl; robh@kernel.org;
>krzk+dt@kernel.org; conor+dt@kernel.org; shawnguo@kernel.org;
>s.hauer@pengutronix.de; linux-media@vger.kernel.org;
>devicetree@vger.kernel.org; linux-kernel@vger.kernel.org; linux-imx@nxp.com;
>linux-arm-kernel@lists.infradead.org; jackson.lee
><jackson.lee@chipsnmedia.com>; lafley.kim <lafley.kim@chipsnmedia.com>;
>marek.vasut@mailbox.org
>Subject: Re: [PATCH v6 9/9] arm64: dts: freescale: imx95: Add video codec
>node
>
>On Wed, Jun 24, 2026 at 04:20:43PM +0900, Nas Chung wrote:
>> Add the Chips and Media wave633 video codec node on IMX95 SoCs.
>>
>> Signed-off-by: Nas Chung <nas.chung@chipsnmedia.com>
>> ---
>> .../boot/dts/freescale/imx95-19x19-evk.dts | 11 ++++++
>> arch/arm64/boot/dts/freescale/imx95.dtsi | 36 +++++++++++++++++++
>> 2 files changed, 47 insertions(+)
>>
>> diff --git a/arch/arm64/boot/dts/freescale/imx95-19x19-evk.dts
>b/arch/arm64/boot/dts/freescale/imx95-19x19-evk.dts
>> index 041fd838fabb..7edd1c69966a 100644
>> --- a/arch/arm64/boot/dts/freescale/imx95-19x19-evk.dts
>> +++ b/arch/arm64/boot/dts/freescale/imx95-19x19-evk.dts
>> @@ -76,6 +76,11 @@ linux_cma: linux,cma {
>> linux,cma-default;
>> reusable;
>> };
>> +
>> + vpu_boot: memory@a0000000 {
>> + reg = <0 0xa0000000 0 0x100000>;
>> + no-map;
>> + };
>> };
>>
>> flexcan1_phy: can-phy0 {
>> @@ -1142,3 +1147,9 @@ &tpm6 {
>> pinctrl-0 = <&pinctrl_tpm6>;
>> status = "okay";
>> };
>> +
>> +&vpu {
>> + memory-region = <&vpu_boot>;
>> + sram = <&sram1>;
>
>Can the `sram` node moved to imx95.dtsi or not?
Thanks for your feedback.
Agreed, I'll move the sram property to imx95.dtsi in v7.
Thanks.
Nas.
>
>Francesco
^ permalink raw reply
* Re: [PATCH v6 1/2] dt-bindings: bridge: Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver
From: Sunyun Yang @ 2026-06-25 2:01 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
dmitry.baryshkov, maarten.lankhorst, rfoss, mripard,
Laurent.pinchart, tzimmermann, jonas, jernej.skrabec, devicetree,
dri-devel, linux-kernel, xmzhu, xmzhu, rlyu, xbpeng
In-Reply-To: <CAFQXuNajWT31q1MccwTDa074_7=6tfaz-FOmP-tx_q83R60QfQ@mail.gmail.com>
Sunyun Yang <syyang@lontium.com> 于2026年6月25日周四 08:52写道:
>
> Krzysztof Kozlowski <krzk@kernel.org> 于2026年6月24日周三 22:05写道:
> >
> > On 11/05/2026 05:28, Sunyun Yang wrote:
> > > <syyang@lontium.com> 于2026年5月8日周五 22:25写道:
> > >>
> > >> From: Sunyun Yang <syyang@lontium.com>
> > >>
> > >> LT9611C(EX/UXD) is an I2C-controlled chip that Receiver signal/dual port
> > >> mipi dsi and output hdmi, differences in hardware features:
> > >> - LT9611C: supports 1-port mipi dsi to hdmi 1.4
> > >> - LT9611EX: supports 2-port mipi dsi to hdmi 1.4
> > >> - LT9611UXD: supports 2-port mipi dsi to hdmi 1.4/2.0
> > >>
> > >> Signed-off-by: Sunyun Yang <syyang@lontium.com>
> > >> ---
> > >> .../bindings/display/bridge/lontium,lt9611.yaml | 8 ++++++--
> > >> 1 file changed, 6 insertions(+), 2 deletions(-)
> > >>
> > >> diff --git a/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml b/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml
> > >> index 429a06057ae8..e0821a63d9d7 100644
> > >> --- a/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml
> > >> +++ b/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml
> > >> @@ -4,19 +4,23 @@
> > >> $id: http://devicetree.org/schemas/display/bridge/lontium,lt9611.yaml#
> > >> $schema: http://devicetree.org/meta-schemas/core.yaml#
> > >>
> > >> -title: Lontium LT9611(UXC) 2 Port MIPI to HDMI Bridge
> > >> +title: Lontium LT9611(UXC/C/EX/UXD) 2 Port MIPI DSI to HDMI Bridge
> > >>
> > >> maintainers:
> > >> - Vinod Koul <vkoul@kernel.org>
> > >>
> > >> description: |
> > >> - The LT9611 and LT9611UXC are bridge devices which convert DSI to HDMI
> > >> + The LT9611、LT9611UXC、LT9611C、LT9611EX and LT9611UXD
> > >> + are bridge devices which convert DSI to HDMI
> > >>
> > >> properties:
> > >> compatible:
> > >> enum:
> > >> - lontium,lt9611
> > >> + - lontium,lt9611c
> > >> + - lontium,lt9611ex
> > >> - lontium,lt9611uxc
> > >> + - lontium,lt9611uxd
> > >>
> > >> reg:
> > >> maxItems: 1
> > >> --
> > >
> > > Gentle ping.
> > > Thanks.
> >
> > Except mess with threading, your patchset does not build, when applied
> > on next-20260618.
> >
> > What is the base of this?
> >
>
> Thanks for testing my patchset on next-20260618.
>
> The base of this series is v7.0.
>
Krzysztof, Is Sashiko AI review required before merging a patch now?
> > Best regards,
> > Krzysztof
^ permalink raw reply
* [PATCH v2 3/3] input: misc: Add Qualcomm SPMI PMIC haptics driver
From: Fenglin Wu @ 2026-06-25 2:00 UTC (permalink / raw)
To: linux-arm-msm, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Lee Jones, Stephen Boyd, Bjorn Andersson,
Konrad Dybcio
Cc: David Collins, Subbaraman Narayanamurthy, Kamal Wadhwa, kernel,
linux-input, devicetree, linux-kernel, Fenglin Wu
In-Reply-To: <20260624-qcom-spmi-haptics-v2-0-b9118e60f3e3@oss.qualcomm.com>
Add an initial driver for the Qualcomm PMIH0108 PMIC haptics module,
named as HAP530_HV. This module supports several play modes, including
DIRECT_PLAY, FIFO, PAT_MEM, and SWR, each with distinct data sourcing
and hardware data handling logic. Currently, the driver provides support
for two play modes using the input force-feedback framework: FF_CONSTANT
effect for DIRECT_PLAY mode and FF_PERIODIC effect with FF_CUSTOM
waveform for FIFO mode.
Assisted-by: Claude:claude-4-6-sonnet
Signed-off-by: Fenglin Wu <fenglin.wu@oss.qualcomm.com>
---
drivers/input/misc/Kconfig | 11 +
drivers/input/misc/Makefile | 1 +
drivers/input/misc/qcom-spmi-haptics.c | 838 +++++++++++++++++++++++++++++++++
3 files changed, 850 insertions(+)
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 1f6c57dba030..4f40940973e4 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -236,6 +236,17 @@ config INPUT_PMIC8XXX_PWRKEY
To compile this driver as a module, choose M here: the
module will be called pmic8xxx-pwrkey.
+config INPUT_QCOM_SPMI_HAPTICS
+ tristate "Qualcomm SPMI PMIC haptics support"
+ depends on MFD_SPMI_PMIC
+ help
+ Say Y to enable support for the Qualcomm PMIH0108 SPMI PMIC haptics
+ module. Supports DIRECT_PLAY, FIFO streaming play modes via the
+ Linux input force-feedback framework.
+
+ To compile this driver as a module, choose M here: the module will
+ be called qcom-spmi-haptics.
+
config INPUT_SPARCSPKR
tristate "SPARC Speaker support"
depends on PCI && SPARC64
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 2281d6803fce..c5c9aa139a11 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -69,6 +69,7 @@ obj-$(CONFIG_INPUT_PMIC8XXX_PWRKEY) += pmic8xxx-pwrkey.o
obj-$(CONFIG_INPUT_POWERMATE) += powermate.o
obj-$(CONFIG_INPUT_PWM_BEEPER) += pwm-beeper.o
obj-$(CONFIG_INPUT_PWM_VIBRA) += pwm-vibra.o
+obj-$(CONFIG_INPUT_QCOM_SPMI_HAPTICS) += qcom-spmi-haptics.o
obj-$(CONFIG_INPUT_QNAP_MCU) += qnap-mcu-input.o
obj-$(CONFIG_INPUT_RAVE_SP_PWRBUTTON) += rave-sp-pwrbutton.o
obj-$(CONFIG_INPUT_RB532_BUTTON) += rb532_button.o
diff --git a/drivers/input/misc/qcom-spmi-haptics.c b/drivers/input/misc/qcom-spmi-haptics.c
new file mode 100644
index 000000000000..4b27638df960
--- /dev/null
+++ b/drivers/input/misc/qcom-spmi-haptics.c
@@ -0,0 +1,838 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/pm_runtime.h>
+#include <linux/uaccess.h>
+#include <linux/workqueue.h>
+
+/* HAP_CFG register offsets, bit fields, value constants */
+#define HAP_CFG_INT_RT_STS_REG 0x10
+#define FIFO_EMPTY_BIT BIT(1)
+#define HAP_CFG_EN_CTL_REG 0x46
+#define HAPTICS_EN_BIT BIT(7)
+#define HAP_CFG_VMAX_REG 0x48
+#define VMAX_STEP_MV 50
+#define VMAX_MV_MAX 10000
+#define HAP_CFG_SPMI_PLAY_REG 0x4C
+#define PLAY_EN_BIT BIT(7)
+#define PAT_SRC_MASK GENMASK(2, 0)
+#define PAT_SRC_FIFO 0
+#define PAT_SRC_DIRECT_PLAY 1
+#define HAP_CFG_TLRA_OL_HIGH_REG 0x5C
+#define TLRA_OL_MSB_MASK GENMASK(3, 0)
+#define TLRA_STEP_US 5
+#define HAP_CFG_TLRA_OL_LOW_REG 0x5D
+#define HAP_CFG_DRV_DUTY_CFG_REG 0x60
+#define ADT_DRV_DUTY_EN_BIT BIT(7)
+#define ADT_BRK_DUTY_EN_BIT BIT(6)
+#define DRV_DUTY_MASK GENMASK(5, 3)
+#define AUTORES_DRV_DUTY_62P5 2
+#define BRK_DUTY_MASK GENMASK(2, 0)
+#define AUTORES_BRK_DUTY_62P5 5
+#define HAP_CFG_ZX_WIND_CFG_REG 0x62
+#define ZX_DEBOUNCE_MASK GENMASK(6, 4)
+#define AUTORES_ZX_DEBOUNCE 3
+#define ZX_WIN_HEIGHT_MASK GENMASK(2, 0)
+#define AUTORES_ZX_WIN_HEIGHT 2
+#define HAP_CFG_AUTORES_CFG_REG 0x63
+#define AUTORES_EN_BIT BIT(7)
+#define AUTORES_EN_DLY_MASK GENMASK(6, 2)
+#define AUTORES_EN_DLY_CYCLES 10
+#define AUTORES_ERR_WIN_MASK GENMASK(1, 0)
+#define AUTORES_ERR_WIN_25PCT 1
+#define HAP_CFG_FAULT_CLR_REG 0x66
+#define ZX_TO_FAULT_CLR_BIT BIT(4)
+#define SC_CLR_BIT BIT(2)
+#define AUTO_RES_ERR_CLR_BIT BIT(1)
+#define HPWR_RDY_FAULT_CLR_BIT BIT(0)
+#define FAULT_CLR_ALL (ZX_TO_FAULT_CLR_BIT | SC_CLR_BIT | \
+ AUTO_RES_ERR_CLR_BIT | HPWR_RDY_FAULT_CLR_BIT)
+#define HAP_CFG_RAMP_DN_CFG2_REG 0x86
+#define AUTORES_PRE_HIZ_DLY_10US 1
+
+/* HAP_PTN register offsets, bit fields, value constants */
+#define HAP_PTN_REVISION2_REG 0x01
+#define HAP_PTN_FIFO_DIN_0_REG 0x20
+#define HAP_PTN_FIFO_PLAY_RATE_REG 0x24
+#define FIFO_PLAY_RATE_MASK GENMASK(3, 0)
+#define HAP_PTN_DIRECT_PLAY_REG 0x26
+#define HAP_PTN_FIFO_EMPTY_CFG_REG 0x2A
+#define FIFO_THRESH_LSB 64
+#define HAP_PTN_FIFO_DIN_1B_REG 0x2C
+#define HAP_PTN_MEM_OP_ACCESS_REG 0x2D
+#define MEM_FLUSH_RELOAD_BIT BIT(0)
+#define HAP_PTN_MMAP_FIFO_REG 0xA0
+#define MMAP_FIFO_EXIST_BIT BIT(7)
+#define MMAP_FIFO_LEN_MASK GENMASK(6, 0)
+#define HAP_PTN_PATX_PLAY_CFG_REG 0xA2
+
+#define HAP530_MEM_TOTAL_BYTES 8192
+#define FIFO_EMPTY_THRESH 280
+#define FIFO_INIT_FILL 320
+
+#define HAPTICS_AUTOSUSPEND_MS 1000
+
+/*
+ * FF_CUSTOM data layout (custom_data[] of type s16):
+ * [0] = play rate (PLAY_RATE_*)
+ * [1] = vmax in mV (0 = use device default from qcom,vmax-microvolt)
+ * [2..N-1] = signed 8-bit PCM samples packed one per s16 (lower byte used)
+ */
+#define CUSTOM_DATA_RATE_IDX 0
+#define CUSTOM_DATA_VMAX_IDX 1
+#define CUSTOM_DATA_SAMPLE_START 2
+
+#define HAPTICS_MAX_EFFECTS 8
+
+enum qcom_haptics_mode {
+ HAPTICS_DIRECT_PLAY,
+ HAPTICS_FIFO,
+};
+
+enum qcom_haptics_play_rate {
+ PLAY_RATE_T_LRA = 0,
+ PLAY_RATE_T_LRA_DIV_2 = 1,
+ PLAY_RATE_T_LRA_DIV_4 = 2,
+ PLAY_RATE_T_LRA_DIV_8 = 3,
+ /* 4–7 are reserved */
+ PLAY_RATE_F_8KHZ = 8,
+ PLAY_RATE_F_16KHZ = 9,
+ PLAY_RATE_F_24KHZ = 10,
+ PLAY_RATE_F_32KHZ = 11,
+ PLAY_RATE_F_44P1KHZ = 12,
+ PLAY_RATE_F_48KHZ = 13,
+ PLAY_RATE_MAX = PLAY_RATE_F_48KHZ,
+};
+
+struct qcom_haptics_effect {
+ enum qcom_haptics_mode mode;
+ enum qcom_haptics_play_rate play_rate;
+ u32 vmax_mv;
+ s8 *fifo_data;
+ u32 data_len;
+};
+
+/**
+ * struct qcom_haptics
+ * @dev: underlying SPMI device
+ * @regmap: regmap for SPMI register access
+ * @input: input device exposing the FF interface
+ * @cfg_base: base address of the CFG peripheral
+ * @ptn_base: base address of the PTN peripheral
+ * @t_lra_us: LRA resonance period in microseconds
+ * @vmax_mv: maximum actuator drive voltage in millivolts
+ * @fifo_len: programmed HW FIFO depth in bytes
+ * @gain: playback gain scaler
+ * @play_work: deferred work item that starts or stops playback
+ * @play_lock: mutex lock to serialize playbacks
+ * @cur_effect_id: index into @effects[] identifying the active effect
+ * @fifo_empty_irq: IRQ number for the FIFO-empty interrupt
+ * @play_request: true when a playback is requested
+ * @pm_ref_held: true while a pm_runtime_get is held
+ * @irq_enabled: true if fifo_empty_irq is enabled
+ * @fifo_lock: mutex protecting the FIFO streaming data
+ * @fifo_data: pointer of the data buffer for FIFO streaming
+ * @data_len: length of the data buffer for current effect
+ * @data_written: number of samples written to the hardware FIFO
+ * @data_done: flag to indicate that all samples have been written
+ * @effects: table of the effects
+ */
+struct qcom_haptics {
+ struct device *dev;
+ struct regmap *regmap;
+ struct input_dev *input;
+
+ u32 cfg_base;
+ u32 ptn_base;
+ u32 t_lra_us;
+ u32 vmax_mv;
+ u32 fifo_len;
+ u16 gain;
+
+ struct work_struct play_work;
+ struct mutex play_lock; /* mutex used to serialize playbacks */
+ int cur_effect_id;
+ int fifo_empty_irq;
+ bool play_request;
+ bool pm_ref_held;
+ bool irq_enabled;
+
+ struct mutex fifo_lock; /* protect the FIFO data during play */
+ const s8 *fifo_data;
+ u32 data_len;
+ u32 data_written;
+ bool data_done;
+
+ struct qcom_haptics_effect effects[HAPTICS_MAX_EFFECTS];
+};
+
+static int cfg_write(struct qcom_haptics *h, u32 off, u32 val)
+{
+ return regmap_write(h->regmap, h->cfg_base + off, val);
+}
+
+static int cfg_update_bits(struct qcom_haptics *h, u32 off, u32 mask, u32 val)
+{
+ return regmap_update_bits(h->regmap, h->cfg_base + off, mask, val);
+}
+
+static int ptn_write(struct qcom_haptics *h, u32 off, u32 val)
+{
+ return regmap_write(h->regmap, h->ptn_base + off, val);
+}
+
+static int ptn_update_bits(struct qcom_haptics *h, u32 off, u32 mask, u32 val)
+{
+ return regmap_update_bits(h->regmap, h->ptn_base + off, mask, val);
+}
+
+static int ptn_bulk_write(struct qcom_haptics *h, u32 off,
+ const void *buf, size_t count)
+{
+ return regmap_bulk_write(h->regmap, h->ptn_base + off, buf, count);
+}
+
+static int haptics_clear_faults(struct qcom_haptics *h)
+{
+ return cfg_write(h, HAP_CFG_FAULT_CLR_REG, FAULT_CLR_ALL);
+}
+
+static int haptics_set_vmax(struct qcom_haptics *h, u32 vmax_mv)
+{
+ return cfg_write(h, HAP_CFG_VMAX_REG, vmax_mv / VMAX_STEP_MV);
+}
+
+static int haptics_config_lra_period(struct qcom_haptics *h)
+{
+ u32 tmp = h->t_lra_us / TLRA_STEP_US;
+ int ret;
+
+ ret = cfg_write(h, HAP_CFG_TLRA_OL_HIGH_REG, (tmp >> 8) & TLRA_OL_MSB_MASK);
+ if (ret)
+ return ret;
+
+ return cfg_write(h, HAP_CFG_TLRA_OL_LOW_REG, tmp & 0xFF);
+}
+
+static int haptics_enable_module(struct qcom_haptics *h, bool enable)
+{
+ return cfg_update_bits(h, HAP_CFG_EN_CTL_REG, HAPTICS_EN_BIT,
+ enable ? HAPTICS_EN_BIT : 0);
+}
+
+static int haptics_configure_autores(struct qcom_haptics *h)
+{
+ int ret;
+
+ /* AUTORES_CFG: enable, 10-cycle delay, 25% error window */
+ ret = cfg_write(h, HAP_CFG_AUTORES_CFG_REG,
+ AUTORES_EN_BIT |
+ FIELD_PREP(AUTORES_EN_DLY_MASK, AUTORES_EN_DLY_CYCLES) |
+ FIELD_PREP(AUTORES_ERR_WIN_MASK, AUTORES_ERR_WIN_25PCT));
+ if (ret)
+ return ret;
+
+ /* DRV_DUTY: adaptive drive/brake duty cycles at 62.5% */
+ ret = cfg_write(h, HAP_CFG_DRV_DUTY_CFG_REG,
+ ADT_DRV_DUTY_EN_BIT | ADT_BRK_DUTY_EN_BIT |
+ FIELD_PREP(DRV_DUTY_MASK, AUTORES_DRV_DUTY_62P5) |
+ FIELD_PREP(BRK_DUTY_MASK, AUTORES_BRK_DUTY_62P5));
+ if (ret)
+ return ret;
+
+ /* Pre-HIZ delay: 10 µs */
+ ret = cfg_write(h, HAP_CFG_RAMP_DN_CFG2_REG, AUTORES_PRE_HIZ_DLY_10US);
+ if (ret)
+ return ret;
+
+ /* Zero-cross window: debounce 3, no hysteresis, height 2 */
+ return cfg_write(h, HAP_CFG_ZX_WIND_CFG_REG,
+ FIELD_PREP(ZX_DEBOUNCE_MASK, AUTORES_ZX_DEBOUNCE) |
+ FIELD_PREP(ZX_WIN_HEIGHT_MASK, AUTORES_ZX_WIN_HEIGHT));
+}
+
+static int haptics_write_fifo_chunk(struct qcom_haptics *h,
+ const s8 *data, u32 len)
+{
+ u32 bulk_len = ALIGN_DOWN(len, 4);
+ int i, ret;
+
+ /*
+ * FIFO data writing supports both 4-byte bulk writes using registers
+ * [HAP_PTN_FIFO_DIN_0_REG ... HAP_PTN_FIFO_DIN_3_REG], and 1-byte writes
+ * using the HAP_PTN_FIFO_DIN_1B_REG register. The 4-byte bulk write is more
+ * efficient, so use 4-byte writes for the initial 4-byte aligned data,
+ * and 1-byte writes for any trailing remainder.
+ */
+ for (i = 0; i < bulk_len; i += 4) {
+ ret = ptn_bulk_write(h, HAP_PTN_FIFO_DIN_0_REG, &data[i], 4);
+ if (ret)
+ return ret;
+ }
+
+ for (; i < len; i++) {
+ ret = ptn_write(h, HAP_PTN_FIFO_DIN_1B_REG, (u8)data[i]);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+/*
+ * Configure the hardware FIFO memory boundary.
+ * FIFO occupies addresses [0, fifo_len).
+ */
+static int haptics_configure_fifo_mmap(struct qcom_haptics *h)
+{
+ u32 fifo_len, fifo_units;
+
+ /* Config all memory space for FIFO usage for now */
+ fifo_len = HAP530_MEM_TOTAL_BYTES;
+ fifo_len = ALIGN_DOWN(fifo_len, 64);
+ fifo_units = fifo_len / 64;
+ h->fifo_len = fifo_len;
+
+ return ptn_write(h, HAP_PTN_MMAP_FIFO_REG,
+ MMAP_FIFO_EXIST_BIT |
+ FIELD_PREP(MMAP_FIFO_LEN_MASK, fifo_units - 1));
+}
+
+static u32 haptics_gain_scaled_vmax(struct qcom_haptics *h, u32 vmax_mv)
+{
+ u32 v = mult_frac(vmax_mv, h->gain, 0xFFFF);
+
+ return max_t(u32, v, VMAX_STEP_MV);
+}
+
+static void haptics_fifo_irq_enable(struct qcom_haptics *h, bool enable)
+{
+ if (h->irq_enabled == enable)
+ return;
+
+ if (enable)
+ enable_irq(h->fifo_empty_irq);
+ else
+ disable_irq(h->fifo_empty_irq);
+
+ h->irq_enabled = enable;
+}
+
+/*
+ * Must be called with play_lock held.
+ * Clears PLAY_EN and resets any FIFO-specific state.
+ */
+static void haptics_stop_locked(struct qcom_haptics *h)
+{
+ int id = h->cur_effect_id;
+
+ cfg_write(h, HAP_CFG_SPMI_PLAY_REG, 0);
+
+ if (h->effects[id].mode == HAPTICS_FIFO) {
+ ptn_write(h, HAP_PTN_FIFO_EMPTY_CFG_REG, 0);
+ haptics_fifo_irq_enable(h, false);
+ mutex_lock(&h->fifo_lock);
+ h->fifo_data = NULL;
+ mutex_unlock(&h->fifo_lock);
+ }
+}
+
+static int haptics_start_direct_play(struct qcom_haptics *h, int effect_id)
+{
+ struct ff_effect *ffe = &h->input->ff->effects[effect_id];
+ u8 amplitude = (u8)mult_frac((u32)abs(ffe->u.constant.level), 255, 0x7FFF);
+ int ret;
+
+ ret = haptics_clear_faults(h);
+ if (ret)
+ return ret;
+
+ /* Enable auto-resonance for DIRECT_PLAY mode */
+ ret = cfg_update_bits(h, HAP_CFG_AUTORES_CFG_REG,
+ AUTORES_EN_BIT, AUTORES_EN_BIT);
+ if (ret)
+ return ret;
+
+ ret = haptics_set_vmax(h, haptics_gain_scaled_vmax(h, h->vmax_mv));
+ if (ret)
+ return ret;
+
+ ret = ptn_write(h, HAP_PTN_DIRECT_PLAY_REG, amplitude);
+ if (ret)
+ return ret;
+
+ return cfg_write(h, HAP_CFG_SPMI_PLAY_REG,
+ PLAY_EN_BIT | FIELD_PREP(PAT_SRC_MASK, PAT_SRC_DIRECT_PLAY));
+}
+
+static int haptics_start_fifo(struct qcom_haptics *h, int effect_id)
+{
+ struct qcom_haptics_effect *eff = &h->effects[effect_id];
+ u32 vmax = eff->vmax_mv ? eff->vmax_mv : h->vmax_mv;
+ u32 init_len;
+ int ret;
+
+ ret = haptics_clear_faults(h);
+ if (ret)
+ return ret;
+
+ /* Disable auto-resonance for FIFO mode */
+ ret = cfg_update_bits(h, HAP_CFG_AUTORES_CFG_REG, AUTORES_EN_BIT, 0);
+ if (ret)
+ return ret;
+
+ ret = haptics_set_vmax(h, haptics_gain_scaled_vmax(h, vmax));
+ if (ret)
+ return ret;
+
+ ret = ptn_update_bits(h, HAP_PTN_FIFO_PLAY_RATE_REG,
+ FIFO_PLAY_RATE_MASK,
+ FIELD_PREP(FIFO_PLAY_RATE_MASK, eff->play_rate));
+ if (ret)
+ return ret;
+
+ /* Flush FIFO before loading new data */
+ ret = ptn_write(h, HAP_PTN_MEM_OP_ACCESS_REG, MEM_FLUSH_RELOAD_BIT);
+ if (ret)
+ return ret;
+ ret = ptn_write(h, HAP_PTN_MEM_OP_ACCESS_REG, 0);
+ if (ret)
+ return ret;
+
+ /* Write the initial chunk and initialise streaming state */
+ init_len = min_t(u32, eff->data_len, FIFO_INIT_FILL);
+ ret = haptics_write_fifo_chunk(h, eff->fifo_data, init_len);
+ if (ret)
+ return ret;
+
+ mutex_lock(&h->fifo_lock);
+ h->fifo_data = eff->fifo_data;
+ h->data_len = eff->data_len;
+ h->data_written = init_len;
+ h->data_done = (init_len >= eff->data_len);
+ mutex_unlock(&h->fifo_lock);
+
+ /*
+ * Set empty threshold. When threshold > 0 the hardware fires the
+ * FIFO-empty interrupt when occupancy drops below the threshold,
+ * allowing the driver to refill. A threshold of 0 disables the IRQ.
+ */
+ ret = ptn_write(h, HAP_PTN_FIFO_EMPTY_CFG_REG, h->data_done ? 0 :
+ FIFO_EMPTY_THRESH / FIFO_THRESH_LSB);
+ if (ret)
+ return ret;
+ if (!h->data_done)
+ haptics_fifo_irq_enable(h, true);
+
+ return cfg_write(h, HAP_CFG_SPMI_PLAY_REG,
+ PLAY_EN_BIT | FIELD_PREP(PAT_SRC_MASK, PAT_SRC_FIFO));
+}
+
+/*
+ * Threaded IRQ handler for the FIFO-empty interrupt.
+ *
+ * While a FIFO play is in progress the hardware fires this interrupt when
+ * the number of samples in the FIFO drops below the programmed threshold.
+ * The handler refills the FIFO from the effect's data buffer. When all
+ * samples have been written the threshold is set to zero, which suppresses
+ * further interrupts; the hardware drains the remaining samples naturally
+ * and the play work handler stops the engine on the next invocation.
+ */
+static irqreturn_t haptics_fifo_empty_irq(int irq, void *dev_id)
+{
+ struct qcom_haptics *h = dev_id;
+ u32 sts, to_write;
+ int ret;
+
+ ret = regmap_read(h->regmap,
+ h->cfg_base + HAP_CFG_INT_RT_STS_REG, &sts);
+ if (ret || !(sts & FIFO_EMPTY_BIT))
+ return IRQ_HANDLED;
+
+ mutex_lock(&h->fifo_lock);
+
+ if (!h->fifo_data) {
+ mutex_unlock(&h->fifo_lock);
+ return IRQ_HANDLED;
+ }
+
+ if (h->data_done) {
+ ptn_write(h, HAP_PTN_FIFO_EMPTY_CFG_REG, 0);
+ h->fifo_data = NULL;
+ h->play_request = false;
+ schedule_work(&h->play_work);
+ mutex_unlock(&h->fifo_lock);
+ return IRQ_HANDLED;
+ }
+
+ /* Refill: write the next chunk, conservatively sized to the threshold */
+ to_write = min_t(u32, h->data_len - h->data_written,
+ h->fifo_len - FIFO_EMPTY_THRESH);
+ haptics_write_fifo_chunk(h, &h->fifo_data[h->data_written], to_write);
+ h->data_written += to_write;
+
+ if (h->data_written >= h->data_len) {
+ /* Last chunk enqueued; disable threshold to stop further IRQs */
+ h->data_done = true;
+ ptn_write(h, HAP_PTN_FIFO_EMPTY_CFG_REG, 0);
+ }
+
+ mutex_unlock(&h->fifo_lock);
+ return IRQ_HANDLED;
+}
+
+static void haptics_play_work(struct work_struct *work)
+{
+ struct qcom_haptics *h = container_of(work, struct qcom_haptics, play_work);
+ int id, ret;
+
+ mutex_lock(&h->play_lock);
+
+ if (!h->play_request) {
+ haptics_stop_locked(h);
+ if (h->pm_ref_held) {
+ pm_runtime_put_autosuspend(h->dev);
+ h->pm_ref_held = false;
+ }
+ goto unlock;
+ }
+
+ if (!h->pm_ref_held) {
+ ret = pm_runtime_resume_and_get(h->dev);
+ if (ret < 0) {
+ dev_err(h->dev, "failed to resume device: %d\n", ret);
+ goto unlock;
+ }
+ h->pm_ref_held = true;
+ }
+
+ id = h->cur_effect_id;
+ switch (h->effects[id].mode) {
+ case HAPTICS_DIRECT_PLAY:
+ ret = haptics_start_direct_play(h, id);
+ break;
+ case HAPTICS_FIFO:
+ ret = haptics_start_fifo(h, id);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ if (ret) {
+ dev_err(h->dev, "failed to start effect %d: %d\n", id, ret);
+ if (h->pm_ref_held) {
+ pm_runtime_put_autosuspend(h->dev);
+ h->pm_ref_held = false;
+ }
+ }
+
+unlock:
+ mutex_unlock(&h->play_lock);
+}
+
+static int haptics_upload_effect(struct input_dev *dev,
+ struct ff_effect *effect,
+ struct ff_effect *old)
+{
+ struct qcom_haptics *h = input_get_drvdata(dev);
+ struct qcom_haptics_effect *priv;
+ int id = effect->id;
+ u32 data_len;
+ s16 *buf;
+ s8 *fifo;
+
+ if (id < 0 || id >= HAPTICS_MAX_EFFECTS)
+ return -EINVAL;
+
+ priv = &h->effects[id];
+
+ switch (effect->type) {
+ case FF_CONSTANT:
+ kfree(priv->fifo_data);
+ priv->fifo_data = NULL;
+ priv->data_len = 0;
+ priv->mode = HAPTICS_DIRECT_PLAY;
+ return 0;
+
+ case FF_PERIODIC:
+ if (effect->u.periodic.waveform != FF_CUSTOM)
+ return -EINVAL;
+ /*
+ * Minimum 3 elements: play-rate code + vmax + at least one sample.
+ * No upper bound: the FIFO is refilled continuously from the IRQ
+ * handler, so any length of PCM data is supported.
+ */
+ if (effect->u.periodic.custom_len < 3)
+ return -EINVAL;
+
+ buf = memdup_array_user(effect->u.periodic.custom_data,
+ effect->u.periodic.custom_len,
+ sizeof(s16));
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ if (buf[CUSTOM_DATA_RATE_IDX] > PLAY_RATE_MAX) {
+ kfree(buf);
+ return -EINVAL;
+ }
+
+ data_len = effect->u.periodic.custom_len - CUSTOM_DATA_SAMPLE_START;
+ fifo = kmalloc(data_len, GFP_KERNEL);
+ if (!fifo) {
+ kfree(buf);
+ return -ENOMEM;
+ }
+
+ /* Pack: one s8 sample per s16 slot (lower byte) */
+ for (int i = 0; i < data_len; i++)
+ fifo[i] = (s8)buf[CUSTOM_DATA_SAMPLE_START + i];
+
+ kfree(priv->fifo_data);
+ priv->fifo_data = fifo;
+ priv->data_len = data_len;
+ priv->play_rate = (u8)buf[CUSTOM_DATA_RATE_IDX];
+ priv->vmax_mv = (u32)clamp_val(buf[CUSTOM_DATA_VMAX_IDX], 0, VMAX_MV_MAX);
+ priv->mode = HAPTICS_FIFO;
+
+ kfree(buf);
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int haptics_playback(struct input_dev *dev, int effect_id, int val)
+{
+ struct qcom_haptics *h = input_get_drvdata(dev);
+
+ h->cur_effect_id = effect_id;
+ h->play_request = (val > 0);
+ schedule_work(&h->play_work);
+
+ return 0;
+}
+
+static int haptics_erase(struct input_dev *dev, int effect_id)
+{
+ struct qcom_haptics *h = input_get_drvdata(dev);
+ struct qcom_haptics_effect *priv = &h->effects[effect_id];
+
+ kfree(priv->fifo_data);
+ priv->fifo_data = NULL;
+ priv->data_len = 0;
+
+ return 0;
+}
+
+static void haptics_set_gain(struct input_dev *dev, u16 gain)
+{
+ struct qcom_haptics *h = input_get_drvdata(dev);
+
+ h->gain = gain;
+}
+
+static int qcom_haptics_probe(struct platform_device *pdev)
+{
+ struct qcom_haptics *h;
+ struct input_dev *input;
+ struct ff_device *ff;
+ u32 regs[2], vmax_uv;
+ int ret, irq;
+
+ h = devm_kzalloc(&pdev->dev, sizeof(*h), GFP_KERNEL);
+ if (!h)
+ return -ENOMEM;
+
+ h->dev = &pdev->dev;
+
+ h->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!h->regmap)
+ return dev_err_probe(h->dev, -ENODEV, "no regmap from parent\n");
+
+ ret = device_property_read_u32_array(h->dev, "reg", regs, ARRAY_SIZE(regs));
+ if (ret)
+ return dev_err_probe(h->dev, ret, "failed to read 'reg' property\n");
+
+ h->cfg_base = regs[0];
+ h->ptn_base = regs[1];
+
+ ret = device_property_read_u32(h->dev, "qcom,lra-period-us", &h->t_lra_us);
+ if (ret)
+ return dev_err_probe(h->dev, ret, "missing qcom,lra-period-us\n");
+
+ ret = device_property_read_u32(h->dev, "qcom,vmax-microvolt", &vmax_uv);
+ if (ret)
+ return dev_err_probe(h->dev, ret, "missing qcom,vmax-microvolt\n");
+
+ h->vmax_mv = clamp(vmax_uv / 1000, (u32)VMAX_STEP_MV, (u32)VMAX_MV_MAX);
+
+ ret = haptics_config_lra_period(h);
+ if (ret)
+ return ret;
+
+ ret = haptics_configure_autores(h);
+ if (ret)
+ return ret;
+
+ ret = haptics_set_vmax(h, h->vmax_mv);
+ if (ret)
+ return ret;
+
+ ret = haptics_configure_fifo_mmap(h);
+ if (ret)
+ return ret;
+
+ mutex_init(&h->play_lock);
+ mutex_init(&h->fifo_lock);
+ INIT_WORK(&h->play_work, haptics_play_work);
+ h->gain = 0xFFFF;
+
+ irq = platform_get_irq_byname(pdev, "fifo-empty");
+ if (irq < 0)
+ return dev_err_probe(h->dev, irq, "failed to get fifo-empty IRQ\n");
+
+ ret = devm_request_threaded_irq(h->dev, irq, NULL,
+ haptics_fifo_empty_irq,
+ IRQF_ONESHOT,
+ "qcom-haptics-fifo-empty", h);
+ if (ret)
+ return dev_err_probe(h->dev, ret, "failed to request fifo-empty IRQ\n");
+
+ h->fifo_empty_irq = irq;
+ disable_irq_nosync(irq);
+
+ input = devm_input_allocate_device(h->dev);
+ if (!input)
+ return -ENOMEM;
+
+ input->name = "qcom-spmi-haptics";
+ input_set_drvdata(input, h);
+ h->input = input;
+
+ input_set_capability(input, EV_FF, FF_CONSTANT);
+ input_set_capability(input, EV_FF, FF_PERIODIC);
+ input_set_capability(input, EV_FF, FF_CUSTOM);
+ input_set_capability(input, EV_FF, FF_GAIN);
+
+ ret = input_ff_create(input, HAPTICS_MAX_EFFECTS);
+ if (ret)
+ return ret;
+
+ ff = input->ff;
+ ff->upload = haptics_upload_effect;
+ ff->playback = haptics_playback;
+ ff->erase = haptics_erase;
+ ff->set_gain = haptics_set_gain;
+
+ pm_runtime_get_noresume(h->dev);
+ pm_runtime_use_autosuspend(h->dev);
+ pm_runtime_set_autosuspend_delay(h->dev, HAPTICS_AUTOSUSPEND_MS);
+ devm_pm_runtime_set_active_enabled(h->dev);
+ pm_runtime_put_autosuspend(h->dev);
+
+ ret = input_register_device(input);
+ if (ret) {
+ input_ff_destroy(input);
+ return dev_err_probe(h->dev, ret, "failed to register input device\n");
+ }
+
+ platform_set_drvdata(pdev, h);
+
+ return 0;
+}
+
+static void qcom_haptics_remove(struct platform_device *pdev)
+{
+ struct qcom_haptics *h = platform_get_drvdata(pdev);
+ int i;
+
+ pm_runtime_disable(&pdev->dev);
+ pm_runtime_set_suspended(&pdev->dev);
+
+ mutex_lock(&h->play_lock);
+ haptics_stop_locked(h);
+ mutex_unlock(&h->play_lock);
+
+ haptics_enable_module(h, false);
+ cancel_work_sync(&h->play_work);
+ for (i = 0; i < HAPTICS_MAX_EFFECTS; i++) {
+ kfree(h->effects[i].fifo_data);
+ h->effects[i].fifo_data = NULL;
+ }
+
+ input_unregister_device(h->input);
+}
+
+static int qcom_haptics_runtime_suspend(struct device *dev)
+{
+ struct qcom_haptics *h = dev_get_drvdata(dev);
+
+ return haptics_enable_module(h, false);
+}
+
+static int qcom_haptics_runtime_resume(struct device *dev)
+{
+ struct qcom_haptics *h = dev_get_drvdata(dev);
+
+ return haptics_enable_module(h, true);
+}
+
+static int qcom_haptics_suspend(struct device *dev)
+{
+ struct qcom_haptics *h = dev_get_drvdata(dev);
+
+ mutex_lock(&h->play_lock);
+ haptics_stop_locked(h);
+ if (h->pm_ref_held) {
+ pm_runtime_put_noidle(dev);
+ h->pm_ref_held = false;
+ }
+ mutex_unlock(&h->play_lock);
+
+ cancel_work_sync(&h->play_work);
+ return pm_runtime_force_suspend(dev);
+}
+
+static int qcom_haptics_resume(struct device *dev)
+{
+ return pm_runtime_force_resume(dev);
+}
+
+static const struct dev_pm_ops qcom_haptics_pm_ops = {
+ SYSTEM_SLEEP_PM_OPS(qcom_haptics_suspend, qcom_haptics_resume)
+ RUNTIME_PM_OPS(qcom_haptics_runtime_suspend, qcom_haptics_runtime_resume,
+ NULL)
+};
+
+static const struct of_device_id qcom_haptics_of_match[] = {
+ { .compatible = "qcom,spmi-haptics" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, qcom_haptics_of_match);
+
+static struct platform_driver qcom_haptics_driver = {
+ .probe = qcom_haptics_probe,
+ .remove = qcom_haptics_remove,
+ .driver = {
+ .name = "qcom-spmi-haptics",
+ .of_match_table = qcom_haptics_of_match,
+ .pm = pm_ptr(&qcom_haptics_pm_ops),
+ },
+};
+module_platform_driver(qcom_haptics_driver);
+
+MODULE_DESCRIPTION("Qualcomm SPMI PMIC Haptics driver");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related
* [PATCH v2 1/3] dt-bindings: input: Add Qualcomm SPMI PMIC haptics
From: Fenglin Wu @ 2026-06-25 2:00 UTC (permalink / raw)
To: linux-arm-msm, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Lee Jones, Stephen Boyd, Bjorn Andersson,
Konrad Dybcio
Cc: David Collins, Subbaraman Narayanamurthy, Kamal Wadhwa, kernel,
linux-input, devicetree, linux-kernel, Fenglin Wu
In-Reply-To: <20260624-qcom-spmi-haptics-v2-0-b9118e60f3e3@oss.qualcomm.com>
Add binding document for the haptics module inside Qualcomm PMIC
PMIH0108.
Assisted-by: Claude:claude-4-6-sonnet
Signed-off-by: Fenglin Wu <fenglin.wu@oss.qualcomm.com>
---
.../bindings/input/qcom,spmi-haptics.yaml | 132 +++++++++++++++++++++
1 file changed, 132 insertions(+)
diff --git a/Documentation/devicetree/bindings/input/qcom,spmi-haptics.yaml b/Documentation/devicetree/bindings/input/qcom,spmi-haptics.yaml
new file mode 100644
index 000000000000..3764c3e113a3
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/qcom,spmi-haptics.yaml
@@ -0,0 +1,132 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/input/qcom,spmi-haptics.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Haptics device inside Qualcomm Technologies, Inc. PMIC
+
+maintainers:
+ - Fenglin Wu <fenglin.wu@oss.qualcomm.com>
+
+description: |
+ Certain Qualcomm PMICs integrate a haptics module, such as the HAP530_HV haptics
+ module in the PMIH0108 PMIC, which drives an LRA (Linear Resonant Actuator) with
+ an output voltage up to 10 V. Several play modes are supported in HAP530_HV:
+
+ DIRECT_PLAY: The hardware outputs sinusoidal waveforms whose period is
+ defined by lra-period-us and whose peak voltage is defined by vmax-microvolt.
+ The driving amplitude can be scaled in the range [0, 255] via a single
+ register byte. Hardware-based LRA auto-resonance tracking is enabled by
+ default in this mode, allowing the haptics engine to follow the actual
+ resonant frequency of the LRA and update the driving period accordingly
+ to achieve stronger vibration magnitude.
+
+ FIFO: The hardware can play an arbitrary waveform composed of a sequence
+ of 8-bit samples at a configurable play rate. Samples are pre-filled
+ into the internal FIFO memory of the haptics module and continuously
+ replenished via the FIFO-empty IRQ until all samples have been played.
+ An 8K-byte FIFO memory bank is available in the HAP530_HV haptics module,
+ shared between the FIFO and PAT_MEM play modes. The memory partition
+ between the two modes is configurable via registers, and FIFO mode always
+ uses the 1st partition starting from offset 0.
+
+ PAT_MEM: This mode is very similar to FIFO streaming mode but without the
+ data refilling capability. It is designed mainly for short, latency-critical
+ vibrations. The memory space for PAT_MEM mode must be reserved for dedicated
+ usage, and the waveform data should be preloaded and remain unchanged
+ thereafter. The haptics module can play the waveform data from the memory
+ region specified by the PAT_MEM play start address and length registers.
+
+ In either FIFO mode or PAT_MEM mode, the following play rates are supported:
+ -- 0(T_LRA): each FIFO byte drives one full sinusoidal cycle with the
+ period defined in lra-period-us.
+ -- 1/2/3(T_LRA_DIV_2/4/8): each FIFO byte drives a half/quarter/eighth
+ sinusoidal cycle with the period defined in lra-period-us.
+ -- 8/9/10/11/12/13(8KHz/16KHz/24KHz/32KHz/44.1KHz/48KHz): the FIFO
+ data is treated as PCM samples and drives the output with an
+ arbitrarily shaped waveform. This mode is typically used to define
+ custom driving waveforms for specific vibration effects such as fast
+ attack, crisp brake, etc.
+
+ The drive voltage in FIFO or PAT_MEM mode can exceed the value defined in
+ vmax-mv to achieve a special vibration effect, but the waveform must be
+ short enough to prevent the LRA from being damaged by operating at an
+ overvoltage.
+
+ Also, hardware-based LRA auto-resonance tracking is normally disabled in
+ FIFO or PAT_MEM mode, as these modes are intended to drive arbitrary
+ waveforms that may not follow the resonant frequency; autonomous hardware
+ resonance correction would interfere with the intended output.
+
+properties:
+ compatible:
+ const: qcom,spmi-haptics
+
+ reg:
+ items:
+ - description: HAP_CFG module base address
+ - description: HAP_PTN module base address
+
+ reg-names:
+ items:
+ - const: cfg
+ - const: ptn
+
+ interrupts:
+ maxItems: 1
+
+ interrupt-names:
+ items:
+ - const: fifo-empty
+
+ qcom,vmax-microvolt:
+ description:
+ Maximum allowed output driving voltage in microvolts, rounded to the
+ nearest 50,000 uV step. This is the peak driving voltage in DIRECT_PLAY
+ mode, which outputs sinusoidal waveforms. The value should be equal to
+ the square root of 2 times the Vrms voltage of the LRA.
+ $ref: /schemas/types.yaml#/definitions/uint32
+ minimum: 50000
+ maximum: 10000000
+ multipleOf: 50000
+
+ qcom,lra-period-us:
+ description:
+ LRA actuator initial resonance period in microseconds
+ (1,000,000 / resonant_freq_hz). Used to configure T_LRA-based play
+ rates and the auto-resonance zero-crossing window. It could be also used
+ as the initial period if the LRA wants to be driven off resonance.
+ minimum: 5
+ maximum: 20475
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - interrupts
+ - interrupt-names
+ - qcom,vmax-microvolt
+ - qcom,lra-period-us
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ pmic {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ haptics@f000 {
+ compatible = "qcom,spmi-haptics";
+ reg = <0xf000>, <0xf100>;
+ reg-names = "cfg", "ptn";
+ interrupts = <0x7 0xf0 0x1 IRQ_TYPE_EDGE_RISING>;
+ interrupt-names = "fifo-empty";
+
+ qcom,vmax-microvolt = <1300000>;
+ qcom,lra-period-us = <5880>;
+ };
+ };
--
2.43.0
^ permalink raw reply related
* [PATCH v2 0/3] input: misc: Add an initial driver for haptics inside Qcom PMIH010x PMIC
From: Fenglin Wu @ 2026-06-25 2:00 UTC (permalink / raw)
To: linux-arm-msm, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Lee Jones, Stephen Boyd, Bjorn Andersson,
Konrad Dybcio
Cc: David Collins, Subbaraman Narayanamurthy, Kamal Wadhwa, kernel,
linux-input, devicetree, linux-kernel, Fenglin Wu
Qualcomm PMIH0108 PMIC has a haptics module inside and it could drive
a LRA actuator with several play modes, including: DIRECT_PLAY, FIFO,
PAT_MEM, SWR, etc. Add an initial driver to support two of the play
modes using the input force-feedback framework:
-- FF_CONSTANT effect for DIRECT_PLAY mode which drives sinusoidual
waveforms with fixed period and amplitude, which would generate
a constant vibration effect on the LRA actuator.
-- FF_PERIODIC effect with FF_CUSTOM for FIFO streaming mode, which
can play an arbitrary waveform composed of a sequence of 8-bit
samples at a configurable play rate.
Also, add the device node in the existing pmih0108 dtsi files, and enble
the haptics device for several boards by updating the vmax and
lra-period sttings according to the LRA components that mounted on each
of them.
Signed-off-by: Fenglin Wu <fenglin.wu@oss.qualcomm.com>
---
Changes in v2:
Dropped dtsi change and I will resend them after the driver and binding changes get accepted.
Updated haptics binding and addressed review comments from Krzysztof and Konrad:
- Extended the description to clarify the 'PAT_MEM' mode (not yet supported in the driver)
by comparing it with the 'FIFO' mode.
- Updated the compatible string to 'qcom,spmi-haptics' to match the file name and removed
the PMIC wildcard.
- Simplified register names to 'cfg' and 'ptn'.
- Corrected the unit naming for the 'qcom,vmax-microvolt' property.
- Added an additional clarification for the 'qcom,lra-period-us' property.
Updated the driver to address review comments from Konrad and Julian:
- In haptics_write_fifo_chunk(), separated variable declaration and assignment, and added
comments explaining the 4-byte and 1-byte FIFO writes.
- Replaced manual 'x * n / d' calculations with mult_frac().
- Switched to disable_irq() to prevent late IRQs after device removal.
- Replaced property reads with device_property_read_u32().
- Remove the 'INPUT' dependency in Kconfig
Updated the driver to address feedback from Sashiko AI:
- Guarded pm_runtime_resume()/suspend() with 'pm_ref_held' to prevent runtime PM reference leaks.
- Replaced spinlock with a mutex to protect FIFO data during playback and avoid calling
sleepable regmap APIs under spinlock.
- Adjusted suspend/remove() sequence to stop playback before canceling work, and freed
FIFO buffers to prevent potential memory leaks.
- In FF_PERIODIC handling, allocated 'fifo_data' before assigning data to ensure its
consistency with 'data_len'.
- Registered the input device after enabling runtime PM.
- Unify to use 'h->dev' pointer in probe()
- Link to v1: https://patch.msgid.link/20260616-qcom-spmi-haptics-v1-0-d24e422de6b4@oss.qualcomm.com
---
Fenglin Wu (3):
dt-bindings: input: Add Qualcomm SPMI PMIC haptics
dt-bindings: mfd: qcom,spmi-pmic: Document haptics device
input: misc: Add Qualcomm SPMI PMIC haptics driver
.../bindings/input/qcom,spmi-haptics.yaml | 132 ++++
.../devicetree/bindings/mfd/qcom,spmi-pmic.yaml | 4 +
drivers/input/misc/Kconfig | 11 +
drivers/input/misc/Makefile | 1 +
drivers/input/misc/qcom-spmi-haptics.c | 838 +++++++++++++++++++++
5 files changed, 986 insertions(+)
---
base-commit: 66725039f7090afe14c31bd259e2059a68f04023
change-id: 20260616-qcom-spmi-haptics-3cc97e7b232e
Best regards,
--
Fenglin Wu <fenglin.wu@oss.qualcomm.com>
^ permalink raw reply
* [PATCH v2 2/3] dt-bindings: mfd: qcom,spmi-pmic: Document haptics device
From: Fenglin Wu @ 2026-06-25 2:00 UTC (permalink / raw)
To: linux-arm-msm, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Lee Jones, Stephen Boyd, Bjorn Andersson,
Konrad Dybcio
Cc: David Collins, Subbaraman Narayanamurthy, Kamal Wadhwa, kernel,
linux-input, devicetree, linux-kernel, Fenglin Wu
In-Reply-To: <20260624-qcom-spmi-haptics-v2-0-b9118e60f3e3@oss.qualcomm.com>
Some of the Qualcomm SPMI PMIC has haptics device in it, add it in the
device list.
Signed-off-by: Fenglin Wu <fenglin.wu@oss.qualcomm.com>
---
Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml
index 644c42b5e2e5..773f4cba5935 100644
--- a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml
+++ b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml
@@ -165,6 +165,10 @@ patternProperties:
type: object
$ref: /schemas/pinctrl/qcom,pmic-gpio.yaml#
+ "^haptics@[0-9a-f]+$":
+ type: object
+ $ref: /schemas/input/qcom,spmi-haptics.yaml#
+
"^led-controller@[0-9a-f]+$":
type: object
$ref: /schemas/leds/qcom,spmi-flash-led.yaml#
--
2.43.0
^ permalink raw reply related
* Re: [PATCH v2 5/8] riscv_cbqri: resctrl: Add cache allocation via capacity block mask
From: sashiko-bot @ 2026-06-25 1:53 UTC (permalink / raw)
To: Drew Fustini; +Cc: conor+dt, devicetree, linux-kernel, robh, linux-rt-devel
In-Reply-To: <20260624-dfustini-atl-sc-cbqri-dt-v2-5-2f8049fd902b@kernel.org>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Traversing the global `cbqri_controllers` list without holding `cbqri_controllers_lock` during CPU hotplug risks list corruption.
--
commit e1c49060a1fc98c61e36689ccda2041d360bb762
Author: Drew Fustini <fustini@kernel.org>
riscv_cbqri: resctrl: Add cache allocation via capacity block mask
This commit wires up the CBQRI capacity controllers into resctrl as
RDT_RESOURCE_L2 and RDT_RESOURCE_L3 schemata. It treats mismatched
capabilities at the same cache level as a fatal error and lazily creates
domains in the cpuhp online callback.
> diff --git a/drivers/resctrl/cbqri_resctrl.c b/drivers/resctrl/cbqri_resctrl.c
> new file mode 100644
> index 0000000000000..2f31baff032b6
> --- /dev/null
> +++ b/drivers/resctrl/cbqri_resctrl.c
[ ... ]
> +static int cbqri_attach_cpu_to_all_ctrls(unsigned int cpu)
> +{
> + struct cbqri_controller *ctrl;
> + int err = 0;
> +
> + lockdep_assert_held(&cbqri_domain_list_lock);
> +
> + list_for_each_entry(ctrl, &cbqri_controllers, list) {
[Severity: High]
Is it safe to iterate the global cbqri_controllers list without holding
cbqri_controllers_lock?
If a capacity controller is dynamically added at runtime (e.g., via a device
tree overlay instantiating the platform device), its probe function can acquire
cbqri_controllers_lock and mutate the cbqri_controllers list.
Concurrently, if a CPU is hotplugged, the cpuhp online callback calls
cbqri_resctrl_online_cpu() which invokes this function. Traversing the list
here while holding only cbqri_domain_list_lock could lead to list corruption,
resulting in an infinite loop or NULL pointer dereference.
> + if (ctrl->type != CBQRI_CONTROLLER_TYPE_CAPACITY)
> + continue;
> + if (!cpumask_test_cpu(cpu, &ctrl->cache.cpu_mask))
> + continue;
> + if (!ctrl->alloc_capable)
> + continue;
> +
> + err = cbqri_attach_cpu_to_cap_ctrl(ctrl, cpu);
> + if (err) {
> + cbqri_detach_cpu_from_all_ctrls(cpu);
> + break;
> + }
> + }
> +
> + return err;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260624-dfustini-atl-sc-cbqri-dt-v2-0-2f8049fd902b@kernel.org?part=5
^ permalink raw reply
* Re: [PATCH v2 3/8] riscv: Add support for srmcfg CSR from Ssqosid extension
From: sashiko-bot @ 2026-06-25 1:50 UTC (permalink / raw)
To: Drew Fustini; +Cc: robh, linux-kernel, conor+dt, devicetree, linux-rt-devel
In-Reply-To: <20260624-dfustini-atl-sc-cbqri-dt-v2-3-2f8049fd902b@kernel.org>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] CPU hotplug cache invalidation is registered at the wrong state (CPUHP_AP_ONLINE_DYN), allowing the srmcfg cache sentinel to be overwritten before the CPU goes offline.
--
commit d51eb8c4a6c5a57f238cc53688229c08ea6418e1
Author: Drew Fustini <fustini@kernel.org>
riscv: Add support for srmcfg CSR from Ssqosid extension
This commit adds support for the srmcfg CSR defined in the Ssqosid ISA
extension. It introduces a per-cpu cache to avoid redundant CSR writes
during context switches and uses CPU hotplug and PM notifiers to manage
cache invalidation.
Link: https://github.com/riscv/riscv-ssqosid/releases/tag/v1.0
> diff --git a/arch/riscv/kernel/qos.c b/arch/riscv/kernel/qos.c
> new file mode 100644
> index 0000000000000..ea33201a43f61
> --- /dev/null
> +++ b/arch/riscv/kernel/qos.c
[ ... ]
> +static int riscv_srmcfg_reset_cache(unsigned int cpu)
> +{
> + per_cpu(cpu_srmcfg, cpu) = U32_MAX;
> + return 0;
> +}
[ ... ]
> +static int __init riscv_srmcfg_init(void)
> +{
> + int err;
> +
> + if (!riscv_has_extension_unlikely(RISCV_ISA_EXT_SSQOSID))
> + return 0;
> +
> + /*
> + * cpuhp_setup_state() invokes the startup callback locally on every
> + * already-online CPU, so no separate seed loop is needed here.
> + */
> + err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "riscv/srmcfg:online",
> + riscv_srmcfg_reset_cache, riscv_srmcfg_reset_cache);
[Severity: High]
Does registering the hotplug callbacks at CPUHP_AP_ONLINE_DYN run too early
during teardown and too late during startup?
Since CPUHP_AP_ONLINE_DYN teardown runs while the CPU is still schedulable,
could the U32_MAX sentinel set by riscv_srmcfg_reset_cache() be overwritten
by subsequent context switches (such as switching to the idle thread) before
the CPU actually goes offline?
If a CPU is offlined and then onlined, the stale cache would persist while
the hardware CSR has been reset. Because the CPU becomes schedulable before
the CPUHP_AP_ONLINE_DYN startup callback runs, a task matching the stale
cache might skip the required CSR write and run with incorrect hardware tags.
> + if (err < 0) {
> + pr_warn("srmcfg cpuhp registration failed (%d), cpus brought online after boot will not invalidate the CSR_SRMCFG cache\n",
> + err);
> + return err;
> + }
> +
> + cpu_pm_register_notifier(&riscv_srmcfg_pm_nb);
> + return 0;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260624-dfustini-atl-sc-cbqri-dt-v2-0-2f8049fd902b@kernel.org?part=3
^ permalink raw reply
* RE: [PATCH v6 2/9] dt-bindings: media: nxp: Add Wave6 video codec device
From: Nas Chung @ 2026-06-25 1:43 UTC (permalink / raw)
To: Conor Dooley
Cc: mchehab@kernel.org, hverkuil@xs4all.nl, robh@kernel.org,
krzk+dt@kernel.org, conor+dt@kernel.org, shawnguo@kernel.org,
s.hauer@pengutronix.de, linux-media@vger.kernel.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-imx@nxp.com, linux-arm-kernel@lists.infradead.org,
jackson.lee, lafley.kim, marek.vasut@mailbox.org
In-Reply-To: <20260624-junkyard-sensuous-fcd43189b593@spud>
Hi, Conor.
>-----Original Message-----
>From: Conor Dooley <conor@kernel.org>
>Sent: Thursday, June 25, 2026 1:42 AM
>To: Nas Chung <nas.chung@chipsnmedia.com>
>Cc: mchehab@kernel.org; hverkuil@xs4all.nl; robh@kernel.org;
>krzk+dt@kernel.org; conor+dt@kernel.org; shawnguo@kernel.org;
>s.hauer@pengutronix.de; linux-media@vger.kernel.org;
>devicetree@vger.kernel.org; linux-kernel@vger.kernel.org; linux-imx@nxp.com;
>linux-arm-kernel@lists.infradead.org; jackson.lee
><jackson.lee@chipsnmedia.com>; lafley.kim <lafley.kim@chipsnmedia.com>;
>marek.vasut@mailbox.org
>Subject: Re: [PATCH v6 2/9] dt-bindings: media: nxp: Add Wave6 video codec
>device
>
>On Wed, Jun 24, 2026 at 04:20:36PM +0900, Nas Chung wrote:
>> Add documentation for the Chips&Media Wave6 video codec on NXP i.MX SoCs.
>>
>> The hardware contains one control register region and four interface
>> register regions for a shared video processing engine. The control region
>> manages shared resources such as firmware memory, while each interface
>> region has its own MMIO range and interrupt.
>>
>> The control region and each interface region are distinct DMA requesters
>> and can be associated with separate IOMMU stream IDs. Represent the
>> control region as the parent node and the interface register regions as
>> child nodes to describe these resources.
>>
>> Signed-off-by: Nas Chung <nas.chung@chipsnmedia.com>
>> ---
>> .../bindings/media/nxp,imx95-vpu.yaml | 163 ++++++++++++++++++
>> MAINTAINERS | 7 +
>> 2 files changed, 170 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/media/nxp,imx95-
>vpu.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/media/nxp,imx95-vpu.yaml
>b/Documentation/devicetree/bindings/media/nxp,imx95-vpu.yaml
>> new file mode 100644
>> index 000000000000..9a5ca53e15a3
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/media/nxp,imx95-vpu.yaml
>> @@ -0,0 +1,163 @@
>> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/media/nxp,imx95-vpu.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Chips&Media Wave6 Series multi-standard codec IP on NXP i.MX SoCs
>> +
>> +maintainers:
>> + - Nas Chung <nas.chung@chipsnmedia.com>
>> + - Jackson Lee <jackson.lee@chipsnmedia.com>
>> +
>> +description:
>> + The Chips&Media Wave6 codec IP is a multi-standard video
>encoder/decoder.
>> + On NXP i.MX SoCs, the Wave6 codec IP exposes one control register
>region and
>> + four interface register regions for a shared video processing engine.
>> + The parent node describes the control region, which has its own MMIO
>range and
>> + manages shared resources such as firmware memory. The child nodes
>describe the
>> + interface register regions. Each interface region has its own MMIO
>range and
>> + interrupt.
>> + The control region and the interface regions are distinct DMA
>requesters.
>> + The control region and each interface region can be associated with
>separate
>> + IOMMU stream IDs, allowing DMA isolation between them.
>> +
>> +properties:
>> + compatible:
>> + enum:
>> + - nxp,imx95-vpu
>> +
>> + reg:
>> + maxItems: 1
>> +
>> + clocks:
>> + items:
>> + - description: VPU core clock
>> + - description: VPU associated block clock
>> +
>> + clock-names:
>> + items:
>> + - const: core
>> + - const: vpublk
>> +
>> + power-domains:
>> + items:
>> + - description: Main VPU power domain
>> + - description: Performance power domain
>> +
>> + power-domain-names:
>> + items:
>> + - const: vpu
>> + - const: perf
>> +
>> + memory-region:
>> + maxItems: 1
>> +
>> + sram:
>> + $ref: /schemas/types.yaml#/definitions/phandle
>> + description:
>> + phandle to the SRAM node used to store reference data, reducing DMA
>> + memory bandwidth.
>> +
>> + iommus:
>> + maxItems: 1
>> +
>> + "#cooling-cells":
>> + const: 2
>> +
>> + "#address-cells":
>> + const: 2
>> +
>> + "#size-cells":
>> + const: 2
>> +
>> + ranges: true
>> +
>> +patternProperties:
>> + "^interface@[0-9a-f]+$":
>
>I have to wonder if this interface business is required at all.
>Why can this not go into the parent, with each region fetchable via
>reg-names, interrupt-names and iommu-names?
Thanks for your feedback.
I did try the flat model, but the blocker is the IOMMU.
The control region and four interface regions are independent DMA requesters
with distinct stream IDs, and each interface can be assigned to a different VM,
driving the video core with its own isolated memory.
If all stream IDs are listed under the parent's iommus, they bind to a
single device and share one domain, so the isolation is lost.
This is the main reason I added the interface nodes.
Thanks.
Nas.
>
>Cheers,
>Conor.
>
>> + type: object
>> + description:
>> + An interface register region within the Chips&Media Wave6 codec IP.
>> + Each region has its own MMIO range and interrupt and can be
>associated
>> + with a separate IOMMU stream ID for DMA isolation.
>> + additionalProperties: false
>> +
>> + properties:
>> + reg:
>> + maxItems: 1
>> +
>> + interrupts:
>> + maxItems: 1
>> +
>> + iommus:
>> + maxItems: 1
>> +
>> + required:
>> + - reg
>> + - interrupts
>> +
>> +required:
>> + - compatible
>> + - reg
>> + - clocks
>> + - clock-names
>> + - power-domains
>> + - power-domain-names
>> + - memory-region
>> + - "#address-cells"
>> + - "#size-cells"
>> + - ranges
>> +
>> +additionalProperties: false
>> +
>> +examples:
>> + - |
>> + #include <dt-bindings/interrupt-controller/arm-gic.h>
>> + #include <dt-bindings/clock/nxp,imx95-clock.h>
>> +
>> + soc {
>> + #address-cells = <2>;
>> + #size-cells = <2>;
>> +
>> + video-codec@4c4c0000 {
>> + compatible = "nxp,imx95-vpu";
>> + reg = <0x0 0x4c4c0000 0x0 0x10000>;
>> + clocks = <&scmi_clk 115>,
>> + <&vpu_blk_ctrl IMX95_CLK_VPUBLK_WAVE>;
>> + clock-names = "core", "vpublk";
>> + power-domains = <&scmi_devpd 21>,
>> + <&scmi_perf 10>;
>> + power-domain-names = "vpu", "perf";
>> + memory-region = <&vpu_boot>;
>> + sram = <&sram1>;
>> + iommus = <&smmu 0x32>;
>> + #cooling-cells = <2>;
>> + #address-cells = <2>;
>> + #size-cells = <2>;
>> + ranges;
>> +
>> + interface@4c480000 {
>> + reg = <0x0 0x4c480000 0x0 0x10000>;
>> + interrupts = <GIC_SPI 299 IRQ_TYPE_LEVEL_HIGH>;
>> + iommus = <&smmu 0x33>;
>> + };
>> +
>> + interface@4c490000 {
>> + reg = <0x0 0x4c490000 0x0 0x10000>;
>> + interrupts = <GIC_SPI 300 IRQ_TYPE_LEVEL_HIGH>;
>> + iommus = <&smmu 0x34>;
>> + };
>> +
>> + interface@4c4a0000 {
>> + reg = <0x0 0x4c4a0000 0x0 0x10000>;
>> + interrupts = <GIC_SPI 301 IRQ_TYPE_LEVEL_HIGH>;
>> + iommus = <&smmu 0x35>;
>> + };
>> +
>> + interface@4c4b0000 {
>> + reg = <0x0 0x4c4b0000 0x0 0x10000>;
>> + interrupts = <GIC_SPI 302 IRQ_TYPE_LEVEL_HIGH>;
>> + iommus = <&smmu 0x36>;
>> + };
>> + };
>> + };
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index efbf808063e5..77ea3a1a966b 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -28688,6 +28688,13 @@ S: Maintained
>> F: Documentation/devicetree/bindings/media/cnm,wave521c.yaml
>> F: drivers/media/platform/chips-media/wave5/
>>
>> +WAVE6 VPU CODEC DRIVER
>> +M: Nas Chung <nas.chung@chipsnmedia.com>
>> +M: Jackson Lee <jackson.lee@chipsnmedia.com>
>> +L: linux-media@vger.kernel.org
>> +S: Maintained
>> +F: Documentation/devicetree/bindings/media/nxp,imx95-vpu.yaml
>> +
>> WHISKEYCOVE PMIC GPIO DRIVER
>> M: Kuppuswamy Sathyanarayanan
><sathyanarayanan.kuppuswamy@linux.intel.com>
>> L: linux-gpio@vger.kernel.org
>> --
>> 2.31.1
>>
^ permalink raw reply
* Re: [PATCH 0/4] input: misc: Add an initial driver for haptics inside Qcom PMIH010x PMIC
From: Fenglin Wu @ 2026-06-25 1:41 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: linux-arm-msm, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Lee Jones, Stephen Boyd, Bjorn Andersson,
Konrad Dybcio, David Collins, Subbaraman Narayanamurthy,
Kamal Wadhwa, kernel, linux-input, devicetree, linux-kernel
In-Reply-To: <4ba2eeed-71f8-4799-b261-e4e2c268043e@kernel.org>
On 6/24/2026 6:05 PM, Krzysztof Kozlowski wrote:
> No. Act as maintainer. Clone Linus tree, apply the patch and see if
> everything works. My claim is that nothing works and maintainer tree is
> broken.
>
> Best regards,
> Krzysztof
Thanks for the explanation. I just did that and I didn't see conflict
when applying the binding and driver changes, but I did see a conflict
when applying the DTS change. I will drop the DTS change 1st and resend
them after the driver and binding changes get accepted.
^ permalink raw reply
* [PATCH v2 8/8] riscv_cbqri: Add CBQRI cache capacity-allocation platform driver
From: Drew Fustini @ 2026-06-25 1:38 UTC (permalink / raw)
To: Adrien Ricciardi, Alexandre Ghiti, Atish Kumar Patra, Atish Patra,
Babu Moger, Ben Horgan, Borislav Petkov, Chen Pei, Conor Dooley,
Conor Dooley, Dave Hansen, Dave Martin, Fenghua Yu, Gong Shuai,
Gong Shuai, guo.wenjia23, James Morse, Kornel Dulęba,
Krzysztof Kozlowski, liu.qingtao2, Liu Zhiwei, Palmer Dabbelt,
Paul Walmsley, Peter Newman, Radim Krčmář,
Reinette Chatre, Rob Herring, Samuel Holland,
Sebastian Andrzej Siewior, Tony Luck, Vasudevan Srinivasan,
Ved Shanbhogue, Weiwei Li, yunhui cui, Drew Fustini
Cc: linux-kernel, linux-riscv, x86, devicetree, linux-rt-devel,
linux-doc
In-Reply-To: <20260624-dfustini-atl-sc-cbqri-dt-v2-0-2f8049fd902b@kernel.org>
Add a device-tree platform driver, bound to the generic
riscv,cbqri-capacity-controller compatible, that registers a CBQRI
capacity controller as the resctrl cache-allocation resource for the
cache it governs.
The driver follows the node's riscv,cbqri-cache phandle to that cache,
reads its level, and matches it against cacheinfo to get the resctrl
domain id and the harts sharing the cache. It then hands the controller
to riscv_cbqri_register_cc_dt() with the riscv,cbqri-rcid count from the
node.
Nothing is vendor-specific, and the DT "reg" is the CBQRI register block
itself, so any SoC that describes a CBQRI capacity controller in device
tree can reuse the driver unchanged.
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Drew Fustini <fustini@kernel.org>
---
MAINTAINERS | 1 +
drivers/resctrl/Kconfig | 12 ++++
drivers/resctrl/Makefile | 1 +
drivers/resctrl/cbqri_capacity.c | 132 +++++++++++++++++++++++++++++++++++++++
4 files changed, 146 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 64a95a4d795a57033d3f36200d98cfb4a013ab94..e0ffccd9ed6ec3c147fb2a4198cbcf6cedd73c9f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23302,6 +23302,7 @@ F: Documentation/devicetree/bindings/riscv/riscv,cbqri.yaml
F: arch/riscv/include/asm/qos.h
F: arch/riscv/include/asm/resctrl.h
F: arch/riscv/kernel/qos.c
+F: drivers/resctrl/cbqri_capacity.c
F: drivers/resctrl/cbqri_devices.c
F: drivers/resctrl/cbqri_internal.h
F: drivers/resctrl/cbqri_resctrl.c
diff --git a/drivers/resctrl/Kconfig b/drivers/resctrl/Kconfig
index f8566c003d49570b844908d57c231d73c3bb0f6e..3645dd643117c49a5379dd33fb5ee955f4f8eb3a 100644
--- a/drivers/resctrl/Kconfig
+++ b/drivers/resctrl/Kconfig
@@ -41,6 +41,18 @@ menuconfig RISCV_CBQRI
if RISCV_CBQRI
+config RISCV_CBQRI_CAPACITY
+ bool "RISC-V CBQRI cache capacity-allocation controller"
+ depends on OF
+ help
+ Enable driver for a RISC-V CBQRI capacity controller that
+ governs a CPU cache, matching the "riscv,cbqri-capacity-controller"
+ compatible. The controller's cache phandle gives the cache level and the
+ harts that share it, which the driver registers as a resctrl
+ cache-allocation resource.
+
+ Say N unless your device tree describes a CBQRI capacity controller.
+
endif
config RISCV_CBQRI_RESCTRL_FS
diff --git a/drivers/resctrl/Makefile b/drivers/resctrl/Makefile
index a7631712dba9e1c9dd2a0b07a089204671f85d1f..c8339113ef1f735cd27c4452ae2f73ab348c3230 100644
--- a/drivers/resctrl/Makefile
+++ b/drivers/resctrl/Makefile
@@ -7,3 +7,4 @@ ccflags-$(CONFIG_ARM64_MPAM_DRIVER_DEBUG) += -DDEBUG
obj-$(CONFIG_RISCV_CBQRI) += cbqri.o
cbqri-y += cbqri_devices.o
cbqri-$(CONFIG_RISCV_CBQRI_RESCTRL_FS) += cbqri_resctrl.o
+cbqri-$(CONFIG_RISCV_CBQRI_CAPACITY) += cbqri_capacity.o
diff --git a/drivers/resctrl/cbqri_capacity.c b/drivers/resctrl/cbqri_capacity.c
new file mode 100644
index 0000000000000000000000000000000000000000..2172432eb3287f5c7db9ab44d0a4dae45c4fa2cc
--- /dev/null
+++ b/drivers/resctrl/cbqri_capacity.c
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Platform driver for a RISC-V CBQRI capacity controller that backs a CPU
+ * cache. The controller is described in device tree by the generic
+ * "riscv,cbqri-capacity-controller" compatible together with a phandle to the
+ * cache node it governs. The driver hands it to the CBQRI core, which probes
+ * the capabilities register and exposes a controller that supports allocation
+ * as the resctrl cache allocation resource for that cache.
+ */
+
+#define pr_fmt(fmt) "cbqri-capacity: " fmt
+
+#include <linux/cacheinfo.h>
+#include <linux/cpu.h>
+#include <linux/cpumask.h>
+#include <linux/ioport.h>
+#include <linux/mod_devicetable.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/printk.h>
+#include <linux/riscv_cbqri.h>
+#include <linux/types.h>
+
+static int cbqri_capacity_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct cbqri_controller_info info = {};
+ struct device_node *cache_np;
+ cpumask_var_t cpu_mask;
+ struct resource *res;
+ u32 rcid_count, cache_level;
+ int cache_id, cpu, ret;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -EINVAL;
+
+ ret = of_property_read_u32(dev->of_node, "riscv,cbqri-rcid", &rcid_count);
+ if (ret) {
+ dev_err(dev, "missing riscv,cbqri-rcid\n");
+ return ret;
+ }
+
+ cache_np = of_parse_phandle(dev->of_node, "riscv,cbqri-cache", 0);
+ if (!cache_np) {
+ dev_err(dev, "missing riscv,cbqri-cache phandle\n");
+ return -EINVAL;
+ }
+
+ ret = of_property_read_u32(cache_np, "cache-level", &cache_level);
+ if (ret) {
+ dev_err(dev, "%pOF: missing cache-level\n", cache_np);
+ goto out_put;
+ }
+
+ if (!zalloc_cpumask_var(&cpu_mask, GFP_KERNEL)) {
+ ret = -ENOMEM;
+ goto out_put;
+ }
+
+ /*
+ * Associate the controller with its cache instance via
+ * cacheinfo. The matching cache provides the cache id and the
+ * set of harts that share the cache.
+ */
+ cache_id = -1;
+ cpus_read_lock();
+ for_each_online_cpu(cpu) {
+ struct cacheinfo *ci = get_cpu_cacheinfo_level(cpu, cache_level);
+
+ if (ci && ci->fw_token == cache_np) {
+ cache_id = ci->id;
+ cpumask_copy(cpu_mask, &ci->shared_cpu_map);
+ break;
+ }
+ }
+ cpus_read_unlock();
+
+ if (cache_id < 0) {
+ dev_err(dev, "%pOF: no online hart reports an L%u cache for this node\n",
+ cache_np, cache_level);
+ ret = -ENODEV;
+ goto out_free;
+ }
+
+ info.type = CBQRI_CONTROLLER_TYPE_CAPACITY;
+ info.addr = res->start;
+ info.size = resource_size(res);
+ info.rcid_count = rcid_count;
+ info.cache_id = cache_id;
+
+ ret = riscv_cbqri_register_cc_dt(&info, cache_level, cpu_mask);
+ if (ret) {
+ dev_err(dev, "failed to register capacity controller: %d\n", ret);
+ goto out_free;
+ }
+
+ dev_info(dev, "registered L%u capacity controller at %pa (cache_id=%d, rcid=%u)\n",
+ cache_level, &info.addr, cache_id, rcid_count);
+
+out_free:
+ free_cpumask_var(cpu_mask);
+out_put:
+ of_node_put(cache_np);
+ return ret;
+}
+
+static const struct of_device_id cbqri_capacity_of_match[] = {
+ { .compatible = "riscv,cbqri-capacity-controller" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, cbqri_capacity_of_match);
+
+static struct platform_driver cbqri_capacity_driver = {
+ .probe = cbqri_capacity_probe,
+ .driver = {
+ .name = "cbqri-capacity",
+ .of_match_table = cbqri_capacity_of_match,
+ /*
+ * The controller is registered permanently into the
+ * CBQRI core for the life of the system. Block unbind
+ * so userspace cannot leave a dangling controller.
+ */
+ .suppress_bind_attrs = true,
+ },
+};
+
+/*
+ * Register at device_initcall so probe runs before the CBQRI core's
+ * late_initcall which walks the cbqri_controllers list.
+ */
+builtin_platform_driver(cbqri_capacity_driver);
--
2.34.1
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox