* [PATCH RFC v5 2/6] Documentation: iio: add Open Sensor Fusion driver overview
From: Jinseob Kim @ 2026-06-16 7:22 UTC (permalink / raw)
To: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: David Lechner, Nuno Sá, Andy Shevchenko, Jonathan Corbet,
Shuah Khan, linux-iio, devicetree, linux-doc, linux-kernel,
Jinseob Kim
In-Reply-To: <20260616072242.3942-1-kimjinseob88@gmail.com>
Document the Linux IIO mapping for Open Sensor Fusion devices, including
capability-driven IIO device registration and the initially supported
receive path.
Call out that OSF0 is a wire magic value, while protocol_major and
protocol_minor carry protocol compatibility inside frames. The Linux
compatible remains the generic Open Sensor Fusion host interface.
Signed-off-by: Jinseob Kim <kimjinseob88@gmail.com>
---
Documentation/iio/index.rst | 1 +
Documentation/iio/open-sensor-fusion.rst | 71 ++++++++++++++++++++++++
2 files changed, 72 insertions(+)
create mode 100644 Documentation/iio/open-sensor-fusion.rst
diff --git a/Documentation/iio/index.rst b/Documentation/iio/index.rst
index ba3e609c6..2713ec5e0 100644
--- a/Documentation/iio/index.rst
+++ b/Documentation/iio/index.rst
@@ -38,4 +38,5 @@ Industrial I/O Kernel Drivers
adxl345
bno055
ep93xx_adc
+ open-sensor-fusion
opt4060
diff --git a/Documentation/iio/open-sensor-fusion.rst b/Documentation/iio/open-sensor-fusion.rst
new file mode 100644
index 000000000..cf3bbd761
--- /dev/null
+++ b/Documentation/iio/open-sensor-fusion.rst
@@ -0,0 +1,71 @@
+.. SPDX-License-Identifier: GPL-2.0-only
+
+Open Sensor Fusion
+==================
+
+Open Sensor Fusion is a sensor aggregation hub interface. The Linux IIO driver
+receives OSF protocol frames from an attached device, discovers supported sensor
+streams through capability reports, and registers matching IIO devices for the
+sensor classes supported by the driver.
+
+This document is a driver-facing overview for the Linux IIO mapping. The full
+wire protocol, firmware behavior, and hardware model details belong in the Open
+Sensor Fusion project documentation.
+
+Device Model
+------------
+
+An OSF device sends binary frames from the device to the host. The host driver
+uses ``CAPABILITY_REPORT`` messages to discover which sensor streams are
+available. Device Tree describes the attached OSF sensor aggregation hub; it does
+not enumerate the individual sensors discovered at runtime.
+
+The currently supported Linux subset exposes:
+
+* accelerometer samples as ``IIO_ACCEL`` X/Y/Z channels,
+* gyroscope samples as ``IIO_ANGL_VEL`` X/Y/Z channels,
+* magnetometer samples as ``IIO_MAGN`` X/Y/Z channels, and
+* temperature samples as ``IIO_TEMP``.
+
+Protocol Scope
+---------------
+
+The driver supports OSF protocol major version 0 for the initial IIO receive
+path. The current wire magic is ``OSF0``; that string is a wire-format detail and
+is not the Linux driver identity. Device Tree keeps the generic
+``opensensorfusion,osf`` compatible rather than naming a product such as OSF
+GREEN or a wire magic value.
+
+Protocol versioning is carried by the ``protocol_major`` and ``protocol_minor``
+fields at fixed offsets in the OSF frame header. The driver currently
+supports ``protocol_major`` 0. ``protocol_minor`` changes within major version
+0 are intended to remain backward-compatible within the fixed header layout.
+Incompatible wire-format changes require a new ``protocol_major``. A future
+device that cannot expose compatible version discovery through that fixed
+header layout would need a different Device Tree compatible.
+
+The initial Linux driver handles device-to-host frames for:
+
+* ``SENSOR_SAMPLE`` buffered and direct-mode sample data,
+* ``CAPABILITY_REPORT`` based IIO device registration, and
+* ``DEVICE_STATUS`` cache updates.
+
+Vendor-private message types are ignored. Command transport, calibration
+control ABI, fusion output ABI, and runtime capability removal are outside the
+initial Linux IIO receive path.
+
+Timestamps
+----------
+
+OSF frames include a device-side ``timestamp_us`` field. Buffered IIO samples use
+an IIO timestamp captured on the host when samples are pushed to IIO buffers.
+The initial driver does not correlate the device timestamp with the host IIO
+clock.
+
+Compatibility Notes
+-------------------
+
+The project protocol documentation should define the compatibility rules for
+reserved fields, optional flags, and trailing extension data. Until those rules
+are finalized, the Linux decoder keeps conservative bounds checks around the
+currently supported message layouts.
--
2.43.0
^ permalink raw reply related
* [PATCH RFC v5 1/6] dt-bindings: iio: add Open Sensor Fusion device
From: Jinseob Kim @ 2026-06-16 7:22 UTC (permalink / raw)
To: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: David Lechner, Nuno Sá, Andy Shevchenko, Jonathan Corbet,
Shuah Khan, linux-iio, devicetree, linux-doc, linux-kernel,
Jinseob Kim
In-Reply-To: <20260616072242.3942-1-kimjinseob88@gmail.com>
Add the generic Open Sensor Fusion device binding for a serdev-attached
IIO sensor aggregation hub, and document the opensensorfusion vendor
prefix.
The opensensorfusion,osf compatible describes the generic Open Sensor
Fusion host interface. OSF GREEN is not the Linux compatible identity.
Likewise, OSF0 is the current wire magic and a wire-format detail, not
the Linux driver identity.
The fixed OSF frame header carries protocol_major and protocol_minor at
fixed offsets. This driver currently supports protocol_major 0.
protocol_minor changes are intended to remain backward-compatible within
that fixed header layout. Incompatible wire-format changes require a new
protocol_major. If a future device cannot expose compatible version
discovery through the fixed header layout, it will need a different
compatible.
Require vcc-supply so the driver can enable device power before starting
communication.
Signed-off-by: Jinseob Kim <kimjinseob88@gmail.com>
---
.../bindings/iio/opensensorfusion,osf.yaml | 59 +++++++++++++++++++
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
MAINTAINERS | 13 ++++
3 files changed, 74 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml
diff --git a/Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml b/Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml
new file mode 100644
index 000000000..012a07fd6
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml
@@ -0,0 +1,59 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/opensensorfusion,osf.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Open Sensor Fusion Sensor Aggregation Hub
+
+maintainers:
+ - Jinseob Kim <kimjinseob88@gmail.com>
+
+description: |
+ Open Sensor Fusion is a sensor aggregation hub. The hub exposes an OSF
+ protocol data stream over its host interface and may report capabilities and
+ samples for multiple sensor classes. The Linux driver discovers the actual
+ sensor channels from OSF capability reports instead of describing those
+ sensors in Device Tree.
+
+ Open Sensor Fusion is not a generic industry standard. Public project
+ documentation is available at:
+
+ https://github.com/opensensorfusion
+
+ The compatible describes the generic Open Sensor Fusion host interface. It
+ is not an OSF GREEN board identity, and it does not encode the OSF0 wire
+ magic. OSF0, protocol_major, and protocol_minor are wire-protocol details
+ exchanged in OSF frames.
+
+allOf:
+ - $ref: /schemas/serial/serial-peripheral-props.yaml#
+
+properties:
+ compatible:
+ const: opensensorfusion,osf
+
+ vcc-supply:
+ description:
+ Regulator supplying power to the Open Sensor Fusion device.
+
+required:
+ - compatible
+ - vcc-supply
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ vcc_sensor: regulator-0 {
+ compatible = "regulator-fixed";
+ regulator-name = "sensor-vcc";
+ };
+
+ serial {
+ sensor {
+ compatible = "opensensorfusion,osf";
+ vcc-supply = <&vcc_sensor>;
+ };
+ };
+...
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index 28784d66a..88172d4a4 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -1237,6 +1237,8 @@ patternProperties:
description: OpenPandora GmbH
"^openrisc,.*":
description: OpenRISC.io
+ "^opensensorfusion,.*":
+ description: Open Sensor Fusion
"^openwrt,.*":
description: OpenWrt
"^option,.*":
diff --git a/MAINTAINERS b/MAINTAINERS
index c2c6d7927..2ddefc42d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20011,6 +20011,19 @@ F: Documentation/devicetree/
F: arch/*/boot/dts/
F: include/dt-bindings/
+OPEN SENSOR FUSION IIO DRIVER
+M: Jinseob Kim <kimjinseob88@gmail.com>
+S: Maintained
+F: Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml
+F: Documentation/iio/open-sensor-fusion.rst
+F: drivers/iio/opensensorfusion/Kconfig
+F: drivers/iio/opensensorfusion/Makefile
+F: drivers/iio/opensensorfusion/osf_core.*
+F: drivers/iio/opensensorfusion/osf_iio.*
+F: drivers/iio/opensensorfusion/osf_protocol.*
+F: drivers/iio/opensensorfusion/osf_serdev.c
+F: drivers/iio/opensensorfusion/osf_stream.*
+
OPENCOMPUTE PTP CLOCK DRIVER
M: Vadim Fedorenko <vadim.fedorenko@linux.dev>
L: netdev@vger.kernel.org
--
2.43.0
^ permalink raw reply related
* [PATCH RFC v5 0/6] iio: add Open Sensor Fusion IIO driver
From: Jinseob Kim @ 2026-06-16 7:22 UTC (permalink / raw)
To: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: David Lechner, Nuno Sá, Andy Shevchenko, Jonathan Corbet,
Shuah Khan, linux-iio, devicetree, linux-doc, linux-kernel,
Jinseob Kim
Open Sensor Fusion is a sensor aggregation hub interface. The Linux IIO
driver receives OSF protocol frames from a serdev-attached device,
discovers supported sensor streams from capability reports, and exposes
the supported raw sensor data through IIO devices.
The initial driver supports protocol major version 0 and the receive path
for accelerometer, gyroscope, magnetometer, and temperature samples. The
current wire magic is OSF0, but OSF0 is a wire-format detail and not the
Linux driver identity. Protocol compatibility is carried by the
protocol_major and protocol_minor fields in the fixed OSF frame header.
This is still RFC because the driver-facing OSF protocol subset, the
compatible binding, and future protocol compatibility rules are being
reviewed.
Runtime testing was done with an OSF GREEN prototype connected to a
Raspberry Pi over UART. The driver registered osf-accel, osf-gyro,
osf-magn, and osf-temp IIO devices. Direct raw reads and software kfifo
buffer reads were tested.
Changes since v4:
- Regenerated the series as a full standalone replacement series from a
clean upstream base.
- Removed previous-version add/delete churn from the generated series.
- Clarified OSF0, protocol_major, and protocol_minor compatibility
handling.
- Added required vcc-supply support to the binding.
- Added probe-time regulator enablement with devm_regulator_get_enable().
- Added the opensensorfusion vendor prefix.
- Fixed checkpatch cleanup issues in commit messages and driver style.
Jinseob Kim (6):
dt-bindings: iio: add Open Sensor Fusion device
Documentation: iio: add Open Sensor Fusion driver overview
iio: osf: add protocol decoding
iio: osf: add stream parser
iio: osf: add UART transport
iio: osf: register IIO devices from capabilities
.../bindings/iio/opensensorfusion,osf.yaml | 59 ++++
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
Documentation/iio/index.rst | 1 +
Documentation/iio/open-sensor-fusion.rst | 71 ++++
MAINTAINERS | 13 +
drivers/iio/Kconfig | 1 +
drivers/iio/Makefile | 1 +
drivers/iio/opensensorfusion/Kconfig | 14 +
drivers/iio/opensensorfusion/Makefile | 6 +
drivers/iio/opensensorfusion/osf_core.c | 306 ++++++++++++++++++
drivers/iio/opensensorfusion/osf_core.h | 70 ++++
drivers/iio/opensensorfusion/osf_iio.c | 275 ++++++++++++++++
drivers/iio/opensensorfusion/osf_iio.h | 22 ++
drivers/iio/opensensorfusion/osf_protocol.c | 249 ++++++++++++++
drivers/iio/opensensorfusion/osf_protocol.h | 97 ++++++
drivers/iio/opensensorfusion/osf_serdev.c | 117 +++++++
drivers/iio/opensensorfusion/osf_stream.c | 187 +++++++++++
drivers/iio/opensensorfusion/osf_stream.h | 31 ++
18 files changed, 1522 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/opensensorfusion,osf.yaml
create mode 100644 Documentation/iio/open-sensor-fusion.rst
create mode 100644 drivers/iio/opensensorfusion/Kconfig
create mode 100644 drivers/iio/opensensorfusion/Makefile
create mode 100644 drivers/iio/opensensorfusion/osf_core.c
create mode 100644 drivers/iio/opensensorfusion/osf_core.h
create mode 100644 drivers/iio/opensensorfusion/osf_iio.c
create mode 100644 drivers/iio/opensensorfusion/osf_iio.h
create mode 100644 drivers/iio/opensensorfusion/osf_protocol.c
create mode 100644 drivers/iio/opensensorfusion/osf_protocol.h
create mode 100644 drivers/iio/opensensorfusion/osf_serdev.c
create mode 100644 drivers/iio/opensensorfusion/osf_stream.c
create mode 100644 drivers/iio/opensensorfusion/osf_stream.h
--
2.43.0
^ permalink raw reply
* Re: [PATCH v3 2/3] perf: marvell: Add CN20K LLC-TAD PMU support
From: sashiko-bot @ 2026-06-16 7:22 UTC (permalink / raw)
To: Geetha sowjanya; +Cc: conor+dt, devicetree, linux-perf-users, robh
In-Reply-To: <20260616071149.12523-3-gakula@marvell.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The patch series reverses the registration order of the PMU and CPU hotplug, creating a race window where the PMU is exposed to userspace before hotplug migration is wired up.
--
commit 71b7ba2b271b1080739c44e37ec7465ecb45ee0e
Author: Geetha sowjanya <gakula@marvell.com>
perf: marvell: Add CN20K LLC-TAD PMU support
Add support for the LLC Tag-and-Data (TAD) PMU present in
Marvell CN20K SoCs.
> diff --git a/drivers/perf/marvell_cn10k_tad_pmu.c b/drivers/perf/marvell_cn10k_tad_pmu.c
> index 69a6648fa6648..cd81bf8ff5690 100644
> --- a/drivers/perf/marvell_cn10k_tad_pmu.c
> +++ b/drivers/perf/marvell_cn10k_tad_pmu.c
[ ... ]
> @@ -478,7 +521,7 @@ static int tad_pmu_probe(struct platform_device *pdev)
> .read = tad_pmu_event_counter_read,
> };
>
> - if (version == TAD_PMU_V1) {
> + if (version == TAD_PMU_V1 || version == TAD_PMU_V3) {
> tad_pmu->pmu.attr_groups = tad_pmu_attr_groups;
> tad_pmu->ops = &tad_pmu_ops;
> } else {
[Severity: Medium]
Looking at the remainder of tad_pmu_probe(), the registration order is:
ret = perf_pmu_register(&tad_pmu->pmu, "tad", -1);
if (ret) {
dev_err(&pdev->dev, "Error %d registering perf PMU\n", ret);
return ret;
}
ret = cpuhp_state_add_instance_nocalls(tad_pmu_cpuhp_state,
&tad_pmu->node);
Does this sequence create a race window where the PMU is exposed to
userspace before hotplug migration is wired up?
If userspace immediately creates events assigned to tad_pmu->cpu, and that
CPU goes offline before the hotplug state instance is added, the hotplug
callback wouldn't be invoked to migrate the context. This could leave the
PMU tied to an offline CPU, preventing its events from being scheduled.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260616071149.12523-1-gakula@marvell.com?part=2
^ permalink raw reply
* [PATCH v4] arm64: dts: imx94: Add Root Port node and PERST property
From: hongxing.zhu @ 2026-06-16 7:23 UTC (permalink / raw)
To: sherry.sun, robh, krzk+dt, conor+dt, frank.li, s.hauer, festevam
Cc: kernel, devicetree, imx, linux-arm-kernel, linux-kernel,
Richard Zhu
From: Richard Zhu <hongxing.zhu@nxp.com>
Since describing the PCIe PERST# property under Host Bridge node is now
deprecated, it is recommended to add it to the Root Port node, so
creating the Root Port node and add the reset-gpios property in Root
Port.
Move the regulator to Root Port nodes as well, because that the PCI
pwrctrl framework had been integrated into pci-imx6 driver.
Signed-off-by: Richard Zhu <hongxing.zhu@nxp.com>
Reviewed-by: Sherry Sun <sherry.sun@nxp.com>
---
arch/arm64/boot/dts/freescale/imx94.dtsi | 11 +++++++++++
arch/arm64/boot/dts/freescale/imx943-evk.dts | 14 ++++++++++----
arch/arm64/boot/dts/freescale/imx943.dtsi | 11 +++++++++++
3 files changed, 32 insertions(+), 4 deletions(-)
---
Changes in v4:
Add the description of regualtor changes into commit message too.
Changes in v3:
- Move the regulator to Root Port node as well, since [2] had been
settled.
- Collect Reviewed-by tag issued by Sherry.
Changes in v2:
- Delete reset-gpio properties in PCIe bridge node.
- Correct the "reset-gpio" property to "reset-gpios".
Since the patch-set [1] issued by Sherry had been landed. Add according
changes on i.MX943 board too.
[1] https://lkml.org/lkml/2026/6/1/1461
[2] https://lore.kernel.org/imx/20260520084904.2424253-1-sherry.sun@oss.nxp.com/
diff --git a/arch/arm64/boot/dts/freescale/imx94.dtsi b/arch/arm64/boot/dts/freescale/imx94.dtsi
index 1f9035e6cf159..dfbb73603cb24 100644
--- a/arch/arm64/boot/dts/freescale/imx94.dtsi
+++ b/arch/arm64/boot/dts/freescale/imx94.dtsi
@@ -1411,6 +1411,17 @@ pcie0: pcie@4c300000 {
power-domains = <&scmi_devpd IMX94_PD_HSIO_TOP>;
fsl,max-link-speed = <3>;
status = "disabled";
+
+ pcie0_port0: pcie@0 {
+ compatible = "pciclass,0604";
+ device_type = "pci";
+ reg = <0x0 0x0 0x0 0x0 0x0>;
+ bus-range = <0x01 0xff>;
+
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges;
+ };
};
pcie0_ep: pcie-ep@4c300000 {
diff --git a/arch/arm64/boot/dts/freescale/imx943-evk.dts b/arch/arm64/boot/dts/freescale/imx943-evk.dts
index 7cfd424689507..674410e541cba 100644
--- a/arch/arm64/boot/dts/freescale/imx943-evk.dts
+++ b/arch/arm64/boot/dts/freescale/imx943-evk.dts
@@ -1034,12 +1034,15 @@ &pcie0 {
<&pcie_ref_clk>;
clock-names = "pcie", "pcie_bus", "pcie_phy", "pcie_aux",
"ref", "extref";
- reset-gpio = <&pcal6416_i2c3_u46 3 GPIO_ACTIVE_LOW>;
- vpcie3v3aux-supply = <®_m2_wlan>;
supports-clkreq;
status = "okay";
};
+&pcie0_port0 {
+ reset-gpios = <&pcal6416_i2c3_u46 3 GPIO_ACTIVE_LOW>;
+ vpcie3v3aux-supply = <®_m2_wlan>;
+};
+
&pcie0_ep {
pinctrl-0 = <&pinctrl_pcie0>;
pinctrl-names = "default";
@@ -1058,12 +1061,15 @@ &pcie1 {
<&pcie_ref_clk>;
clock-names = "pcie", "pcie_bus", "pcie_phy", "pcie_aux",
"ref", "extref";
- reset-gpio = <&pcal6416_i2c3_u46 1 GPIO_ACTIVE_LOW>;
- vpcie3v3aux-supply = <®_slot_pwr>;
supports-clkreq;
status = "okay";
};
+&pcie1_port0 {
+ reset-gpios = <&pcal6416_i2c3_u46 1 GPIO_ACTIVE_LOW>;
+ vpcie3v3aux-supply = <®_slot_pwr>;
+};
+
&pcie1_ep {
pinctrl-0 = <&pinctrl_pcie1>;
pinctrl-names = "default";
diff --git a/arch/arm64/boot/dts/freescale/imx943.dtsi b/arch/arm64/boot/dts/freescale/imx943.dtsi
index cf5b3dbb47ff7..01152fd0efa5e 100644
--- a/arch/arm64/boot/dts/freescale/imx943.dtsi
+++ b/arch/arm64/boot/dts/freescale/imx943.dtsi
@@ -255,6 +255,17 @@ pcie1: pcie@4c380000 {
power-domains = <&scmi_devpd IMX94_PD_HSIO_TOP>;
fsl,max-link-speed = <3>;
status = "disabled";
+
+ pcie1_port0: pcie@0 {
+ compatible = "pciclass,0604";
+ device_type = "pci";
+ reg = <0x0 0x0 0x0 0x0 0x0>;
+ bus-range = <0x01 0xff>;
+
+ #address-cells = <3>;
+ #size-cells = <2>;
+ ranges;
+ };
};
pcie1_ep: pcie-ep@4c380000 {
--
2.34.1
^ permalink raw reply related
* Re: [PATCH 3/4] mfd: mt6397-core: add mt6323 AUXADC support
From: Andy Shevchenko @ 2026-06-16 7:18 UTC (permalink / raw)
To: Roman Vivchar
Cc: Lee Jones, Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Matthias Brugger, AngeloGioacchino Del Regno, linux-iio,
devicetree, linux-kernel, linux-arm-kernel, linux-mediatek,
Ben Grisdale
In-Reply-To: <ycj3k-vV_tQv6u9OfCTP9G9kkeeE-_dbzg_oeHPXn2pb1oZe_Sa1BH6GUGuSd_gD6uPSJiTX7tz6MTlFcqGdWX-f3GMfyW2sLgAOCw6YeNE=@protonmail.com>
On Mon, Jun 15, 2026 at 11:10:20PM +0000, Roman Vivchar wrote:
> On Thursday, June 11th, 2026 at 7:37 PM, Lee Jones <lee@kernel.org> wrote:
> > On Tue, 02 Jun 2026, Roman Vivchar via B4 Relay wrote:
...
> > > + .of_compatible = "mediatek,mt6323-auxadc",
> >
> > [Severity: High]
> > Does adding this compatible string expose an unhandled edge case in the
> > auxadc driver?
> >
> > If this device is instantiated directly from a Device Tree without an MFD
> > parent (e.g., at the DT root), dev->parent could be the platform bus
> > (which has a NULL parent) or NULL itself.
> >
> > Looking at mt6323_auxadc_probe() in drivers/iio/adc/mt6323-auxadc.c:
> >
> > regmap = dev_get_regmap(dev->parent->parent, NULL);
> >
> > Could blindly dereferencing dev->parent->parent here, or passing a NULL
> > device to dev_get_regmap() (which calls devres_find()), result in a
> > kernel oops if probed as a root node?
>
> This is intended behavior, ADC always should be probed as a child of
> the mt6323 MFD because it doesn't have own MMIO space. Otherwise the
> devicetree is malformed. Recent mt6359 ADC driver has same thing with
> dev->parent->parent regmap.
I think Sashiko is overprotective here, but if changing, the whole lot of MFD
drivers should be changed (like Rafael recently has done for driver_override
issue in ACPI related drivers).
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [PATCH 3/3] backlight: lp8864: Convert from LED to backlight class driver
From: Sverdlin, Alexander @ 2026-06-16 7:17 UTC (permalink / raw)
To: linux-leds@vger.kernel.org, afd@ti.com
Cc: jingoohan1@gmail.com, linux-fbdev@vger.kernel.org,
pavel@kernel.org, lee@kernel.org, dri-devel@lists.freedesktop.org,
robh@kernel.org, linux-kernel@vger.kernel.org, krzk+dt@kernel.org,
danielt@kernel.org, conor+dt@kernel.org, deller@gmx.de,
devicetree@vger.kernel.org
In-Reply-To: <0b39450b-559b-43d4-a1e9-bb6684691cb5@ti.com>
Hi Andrew!
On Mon, 2026-06-15 at 14:51 -0500, Andrew Davis wrote:
> > Move the TI LP8864/LP8866 driver from drivers/leds/ to
> > drivers/video/backlight/
>
> Why move it? You can register a backlight device from any directory.
I'm personally fine with the driver residing in drivers/leds, it's
just that currently there are no combined drivers there, the combined
drivers providing both interfaces only live in video/backlight.
But if it's OK from the maintainers' perspective, it will be even
more consistent regarding Kconfig symbol.
> > and convert it to register a backlight class
> > device as its primary interface.
> >
>
> What do you mean by "primary"? You should be able to register with
> both frameworks and have the driver interop between as needed.
Well, I only meant the user's (or my own) perspective, sorry for confusion.
> > The motivation is a use case on a hot-pluggable segment of an I2C bus.
> > The generic led-backlight driver (drivers/video/backlight/led_bl.c) is a
> > platform driver and as such inherently non-hotpluggable.
>
> That isn't strictly true, there is platform_device_{del,unregister}(), so
> whatever your mechanism for removing the I2C device would be, the same
> could be done to the led_bl device before then removing the I2C device.
led_bl is not really designed to act on dynamically instantiated devices,
it's very much device-tree affine (of_count_phandle_with_args(), etc...)
> We don't want to have to move every LED driver that could possibly
> be used as a backlight to the backlight framework, the led_bl.c
> handles adapting LED->backlight as needed. So what you really need
> here is to de-couple led_bl.c from DT so it can better handle dynamic
> add/remove. Then this LED driver simply could register a "led-backlight"
> platform driver to handle the backlight interface, and remove the
> backlight device when it itself (the LED device) is removed.
The mechanism we have regarding hot plugging currently is just I2C bridge,
which de-registers and registers the bridged bus. So no additional drivers
are required, as long as I2C devices are self-contained and not glued with
platform devices.
So bottom line is, I'd prefer to just add the backlight interface to the
existing driver, no matter where it would live in the future.
--
Alexander Sverdlin
Siemens AG
www.siemens.com
^ permalink raw reply
* Re: [PATCH v2 2/4] iio: adc: mt6323-auxadc: add mt6323 PMIC AUXADC driver
From: Andy Shevchenko @ 2026-06-16 7:15 UTC (permalink / raw)
To: Roman Vivchar
Cc: Jonathan Cameron, Roman Vivchar via B4 Relay, David Lechner,
Nuno Sá, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Matthias Brugger, AngeloGioacchino Del Regno,
Lee Jones, linux-iio, devicetree, linux-kernel, linux-arm-kernel,
linux-mediatek, Ben Grisdale
In-Reply-To: <E5H72l1Cg-JQGpaYAFl5TIyITmghqMsTaHQTR8VkuFtlzmfCGTpVlceNp8c2BDW7UoheO0Fx6NLJHmu56WHRNWdPNLsaoIKMCftj_bnyvEI=@protonmail.com>
On Mon, Jun 15, 2026 at 10:52:57PM +0000, Roman Vivchar wrote:
> On Sunday, June 14th, 2026 at 8:22 PM, Jonathan Cameron <jic23@kernel.org> wrote:
> > On Tue, 09 Jun 2026 16:31:59 +0300
> > Roman Vivchar via B4 Relay <devnull+rva333.protonmail.com@kernel.org> wrote:
...
> > > +/*
> > > + * The MediaTek MT6323 (as well as a lot of other PMICs) has the following hierarchy:
> > > + * PMIC AUXADC <- PMIC MFD <- SoC PWRAP (wrapper for PWRAP FSM)
> > > + *
> > > + * Therefore, PWRAP regmap should be obtained using dev->parent->parent.
> > > + */
> > > +struct mt6323_auxadc {
> > > + struct regmap *regmap;
> > > + struct mutex lock;
> > Locks should always have a comment on what data they are protecting.
> > I think this one is about protecting the state of a device during a channel read
> > by serializing those reads.
>
> Nuno said kerneldoc looks unnecessary on v1 [1]. How the comment should
> look?
The comment near to the member:
/* Why this lock? */
struct mutex lock;
...
> After these changes, should I keep or drop Andy's Reviewed-by?
Keep it, the above mentioned changes does not affect on that.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* [PATCH v3 3/3] dt-bindings: perf: marvell: add CN20K TAD PMU support
From: Geetha sowjanya @ 2026-06-16 7:11 UTC (permalink / raw)
To: linux-perf-users, linux-kernel, linux-arm-kernel, devicetree
Cc: mark.rutland, will, krzk+dt, gakula
In-Reply-To: <20260616071149.12523-1-gakula@marvell.com>
Marvell CN20K SoCs integrate a Performance Monitoring Unit (PMU)
associated with the LLC Tag-and-Data (TAD) blocks. The PMU provides
hardware counters to monitor cache traffic and performance events
via a dedicated MMIO region.
The CN20K LLC-TAD PMU is largely similar to CN10K, but differs in the
layout of PFC/PRF register offsets relative to each TAD base. These
offsets are derived from the compatible string in the driver and are
not described through Devicetree properties.
Because of this, using "marvell,cn10k-tad-pmu" as a fallback for CN20K
would result in incorrect register programming. Therefore, add a
separate compatible string:
"marvell,cn20k-tad-pmu"
Update the binding to document CN20K alongside CN10K.
Signed-off-by: Geetha sowjanya <gakula@marvell.com>
Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
---
.../bindings/perf/marvell-cn10k-tad.yaml | 25 +++++++++++++------
1 file changed, 17 insertions(+), 8 deletions(-)
diff --git a/Documentation/devicetree/bindings/perf/marvell-cn10k-tad.yaml b/Documentation/devicetree/bindings/perf/marvell-cn10k-tad.yaml
index 362142252667..d11121a1e2c9 100644
--- a/Documentation/devicetree/bindings/perf/marvell-cn10k-tad.yaml
+++ b/Documentation/devicetree/bindings/perf/marvell-cn10k-tad.yaml
@@ -4,23 +4,32 @@
$id: http://devicetree.org/schemas/perf/marvell-cn10k-tad.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
-title: Marvell CN10K LLC-TAD performance monitor
+title: Marvell CN10K / CN20K LLC-TAD performance monitor
maintainers:
- Bhaskara Budiredla <bbudiredla@marvell.com>
+ - Geetha sowjanya <gakula@marvell.com>
description: |
- The Tag-and-Data units (TADs) maintain coherence and contain CN10K
- shared on-chip last level cache (LLC). The tad pmu measures the
- performance of last-level cache. Each tad pmu supports up to eight
- counters.
+ The Tag-and-Data units (TADs) maintain coherence and contain the
+ shared on-chip last level cache (LLC) on Marvell CN10K and CN20K SoCs.
+ The TAD PMU measures last-level cache performance. Each TAD PMU
+ supports up to eight counters.
- The DT setup comprises of number of tad blocks, the sizes of pmu
- regions, tad blocks and overall base address of the HW.
+ The DT setup describes the number of TAD blocks, the sizes of PMU
+ regions and TAD pages, and the overall MMIO base of the hardware.
+
+ marvell,cn20k-tad-pmu is not a compatible fallback for
+ marvell,cn10k-tad-pmu (and vice versa): the driver selects different
+ PFC/PRF MMIO offsets from the compatible string, and those offsets are
+ not described by separate DT properties today.
properties:
compatible:
- const: marvell,cn10k-tad-pmu
+ items:
+ - enum:
+ - marvell,cn10k-tad-pmu
+ - marvell,cn20k-tad-pmu
reg:
maxItems: 1
--
2.25.1
^ permalink raw reply related
* [PATCH v3 2/3] perf: marvell: Add CN20K LLC-TAD PMU support
From: Geetha sowjanya @ 2026-06-16 7:11 UTC (permalink / raw)
To: linux-perf-users, linux-kernel, linux-arm-kernel, devicetree
Cc: mark.rutland, will, krzk+dt, gakula
In-Reply-To: <20260616071149.12523-1-gakula@marvell.com>
Add support for the LLC Tag-and-Data (TAD) PMU present in
Marvell CN20K SoCs.
The CN20K TAD PMU is based on the CN10K design but differs in the
layout of PFC/PRF register offsets relative to each TAD base, and
introduces additional events. These offsets are selected by the driver
based on the compatible string and are not described via DT properties.
Because of this, "marvell,cn10k-tad-pmu" cannot be used as a fallback
for CN20K, as it would result in incorrect register programming.
Add support for "marvell,cn20k-tad-pmu" by:
- Introducing a TAD_PMU_V3 profile with CN20K-specific register bases
- Extending the event map for new CN20K events
- Matching the PMU via OF and ACPI (MRVL000F)
Signed-off-by: Geetha sowjanya <gakula@marvell.com>
---
Changelog (since v2)
--------------------
- Validate the eventId using an appropriate mask to ensure
it is restricted to 8 bits.
Changelog (since v1)
--------------------
- Hide V3-only events on CN10K via sysfs is_visible and reject them in
event_init.
- Use CN20K-specific MPAM PRF bits (MATCH_MPAMNS, partid << 10) for V3;
software partid is limited to nine bits so this does not collide with
the fixed bit at 25.
- Reset hwc->prev_count when starting counters so reads match cleared HW.
drivers/perf/marvell_cn10k_tad_pmu.c | 54 ++++++++++++++++++++++++++--
1 file changed, 52 insertions(+), 2 deletions(-)
diff --git a/drivers/perf/marvell_cn10k_tad_pmu.c b/drivers/perf/marvell_cn10k_tad_pmu.c
index 69a6648fa664..cd81bf8ff569 100644
--- a/drivers/perf/marvell_cn10k_tad_pmu.c
+++ b/drivers/perf/marvell_cn10k_tad_pmu.c
@@ -17,11 +17,14 @@
#define TAD_PRF_OFFSET 0x900
#define TAD_PFC_OFFSET 0x800
+#define TAD_PRF_NS_OFFSET 0x30900
+#define TAD_PFC_NS_OFFSET 0x30800
#define TAD_PFC(base, counter) ((base) | ((u64)(counter) << 3))
#define TAD_PRF(base, counter) ((base) | ((u64)(counter) << 3))
#define TAD_PRF_CNTSEL_MASK 0xFF
#define TAD_PRF_MATCH_PARTID BIT(8)
#define TAD_PRF_PARTID_NS BIT(10)
+#define TAD_PRF_MATCH_MPAMNS BIT(25)
/*
* config1: bits 0..8 MPAM partition id (including 0); bit 9 requests
* filtering for MPAM-capable events. All-zero config1 means no filter.
@@ -39,6 +42,7 @@ struct tad_region {
enum mrvl_tad_pmu_version {
TAD_PMU_V1 = 1,
TAD_PMU_V2,
+ TAD_PMU_V3,
};
struct tad_pmu_data {
@@ -86,8 +90,15 @@ static void tad_pmu_start_counter(struct tad_pmu *pmu,
if (use_mpam && event_idx > 0x19 && event_idx < 0x21) {
partid_filter = TAD_PRF_MATCH_PARTID | TAD_PRF_PARTID_NS |
((u64)partid << 11);
+
+ if (pdata->id == TAD_PMU_V3)
+ partid_filter = TAD_PRF_MATCH_PARTID | TAD_PRF_MATCH_MPAMNS |
+ ((u64)partid << 10);
}
+ /* CN10K support events 0:24*/
+ if (pdata->id == TAD_PMU_V1 && event_idx >= 0x25)
+ return;
for (i = 0; i < pmu->region_cnt; i++) {
reg_val = event_idx & 0xFF;
@@ -160,6 +171,7 @@ static void tad_pmu_event_counter_start(struct perf_event *event, int flags)
struct hw_perf_event *hwc = &event->hw;
hwc->state = 0;
+ local64_set(&hwc->prev_count, 0);
tad_pmu->ops->start_counter(tad_pmu, event);
}
@@ -220,6 +232,8 @@ static int tad_pmu_event_init(struct perf_event *event)
if (cfg1)
return -EINVAL;
} else {
+ if (pdata->id == TAD_PMU_V1 && event_idx >= 0x25)
+ return -EINVAL;
if ((cfg1 & GENMASK(8, 0)) && !(cfg1 & TAD_PARTID_FILTER_EN))
return -EINVAL;
if (cfg1 & TAD_PARTID_FILTER_EN) {
@@ -246,6 +260,22 @@ static ssize_t tad_pmu_event_show(struct device *dev,
return sysfs_emit(page, "event=0x%02llx\n", pmu_attr->id);
}
+static umode_t tad_pmu_event_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int unused)
+{
+ struct pmu *pmu = dev_get_drvdata(kobj_to_dev(kobj));
+ struct tad_pmu *t = to_tad_pmu(pmu);
+ struct device_attribute *da = container_of(attr, struct device_attribute,
+ attr);
+ struct perf_pmu_events_attr *e = container_of(da, struct perf_pmu_events_attr,
+ attr);
+ u64 id = e->id;
+
+ if (t->pdata->id != TAD_PMU_V3 && id >= 0x25)
+ return 0;
+ return attr->mode;
+}
+
#define TAD_PMU_EVENT_ATTR(name, config) \
PMU_EVENT_ATTR_ID(name, tad_pmu_event_show, config)
@@ -287,12 +317,25 @@ static struct attribute *tad_pmu_event_attrs[] = {
TAD_PMU_EVENT_ATTR(tad_dat_rd_byp, 0x22),
TAD_PMU_EVENT_ATTR(tad_ifb_occ, 0x23),
TAD_PMU_EVENT_ATTR(tad_req_occ, 0x24),
+ TAD_PMU_EVENT_ATTR(tad_req_msh_out_dtg_evict, 0x25),
+ TAD_PMU_EVENT_ATTR(tad_req_msh_out_ltg_evict, 0x26),
+ TAD_PMU_EVENT_ATTR(tad_rsp_msh_out_mpam, 0x28),
+ TAD_PMU_EVENT_ATTR(tad_replays, 0x29),
+ TAD_PMU_EVENT_ATTR(tad_req_byp0, 0x2a),
+ TAD_PMU_EVENT_ATTR(tad_req_byp1, 0x2b),
+ TAD_PMU_EVENT_ATTR(tad_txreq_byp, 0x2c),
+ TAD_PMU_EVENT_ATTR(tad_time_in_dslp, 0x2d),
+ TAD_PMU_EVENT_ATTR(tad_time_elapsed, 0x2e),
+ TAD_PMU_EVENT_ATTR(tad_req_msh_out_dss_rd_128mrg, 0x2f),
+ TAD_PMU_EVENT_ATTR(tad_req_msh_out_dss_wr_128mrg, 0x30),
+ TAD_PMU_EVENT_ATTR(tad_tot_cycle, 0xff),
NULL
};
static const struct attribute_group tad_pmu_events_attr_group = {
.name = "events",
.attrs = tad_pmu_event_attrs,
+ .is_visible = tad_pmu_event_attr_is_visible,
};
static struct attribute *ody_tad_pmu_event_attrs[] = {
@@ -478,7 +521,7 @@ static int tad_pmu_probe(struct platform_device *pdev)
.read = tad_pmu_event_counter_read,
};
- if (version == TAD_PMU_V1) {
+ if (version == TAD_PMU_V1 || version == TAD_PMU_V3) {
tad_pmu->pmu.attr_groups = tad_pmu_attr_groups;
tad_pmu->ops = &tad_pmu_ops;
} else {
@@ -521,6 +564,11 @@ static const struct tad_pmu_data tad_pmu_data = {
.tad_pfc_offset = TAD_PFC_OFFSET,
};
+static const struct tad_pmu_data tad_pmu_cn20k_data = {
+ .id = TAD_PMU_V3,
+ .tad_prf_offset = TAD_PRF_NS_OFFSET,
+ .tad_pfc_offset = TAD_PFC_NS_OFFSET,
+};
#endif
#ifdef CONFIG_ACPI
@@ -534,6 +582,7 @@ static const struct tad_pmu_data tad_pmu_v2_data = {
#ifdef CONFIG_OF
static const struct of_device_id tad_pmu_of_match[] = {
{ .compatible = "marvell,cn10k-tad-pmu", .data = &tad_pmu_data },
+ { .compatible = "marvell,cn20k-tad-pmu", .data = &tad_pmu_cn20k_data },
{},
};
#endif
@@ -542,6 +591,7 @@ static const struct of_device_id tad_pmu_of_match[] = {
static const struct acpi_device_id tad_pmu_acpi_match[] = {
{"MRVL000B", (kernel_ulong_t)&tad_pmu_data},
{"MRVL000D", (kernel_ulong_t)&tad_pmu_v2_data},
+ {"MRVL000F", (kernel_ulong_t)&tad_pmu_cn20k_data},
{},
};
MODULE_DEVICE_TABLE(acpi, tad_pmu_acpi_match);
@@ -603,6 +653,6 @@ static void __exit tad_pmu_exit(void)
module_init(tad_pmu_init);
module_exit(tad_pmu_exit);
-MODULE_DESCRIPTION("Marvell CN10K LLC-TAD perf driver");
+MODULE_DESCRIPTION("Marvell CN10K/CN20K LLC-TAD perf driver");
MODULE_AUTHOR("Bhaskara Budiredla <bbudiredla@marvell.com>");
MODULE_LICENSE("GPL v2");
--
2.25.1
^ permalink raw reply related
* [PATCH v3 1/3] perf: marvell: Add MPAM partid filtering to CN10K TAD PMU
From: Geetha sowjanya @ 2026-06-16 7:11 UTC (permalink / raw)
To: linux-perf-users, linux-kernel, linux-arm-kernel, devicetree
Cc: mark.rutland, will, krzk+dt, gakula
In-Reply-To: <20260616071149.12523-1-gakula@marvell.com>
From: Tanmay Jagdale <tanmay@marvell.com>
The TAD PMU exposes counters that can be filtered by MPAM partition id
for a subset of allocation and hit events.
Add a 9-bit partid format attribute (config1) and route counter programming
through variant-specific ops so CN10K keeps MPAM-capable programming while
Odyssey keeps the reduced event set without advertising partid in sysfs.
Probe no longer mutates the platform_device MMIO resource (walk a local
map_start), rejects tad-cnt / page sizes of zero, validates the memory
window against tad-cnt, and registers the perf PMU before hotplug with
correct unwind.
Example:
perf stat -e tad/tad_alloc_any,partid=0x12,partid_en=1/ -- <program>
Signed-off-by: Tanmay Jagdale <tanmay@marvell.com>
---
Changelog (since v2)
--------------------
- Validate the eventId using an appropriate mask to ensure
it is restricted to 8 bits
Changelog (since v1)
--------------------
- Fix config1 filter enable to use bit 9 consistently with the PMU format
string (partid_en) and reject reserved bits with GENMASK(9, 0).
- Register perf_pmu_register before cpuhp_state_add_instance_nocalls and
unregister on hotplug failure.
drivers/perf/marvell_cn10k_tad_pmu.c | 216 ++++++++++++++++++++-------
1 file changed, 164 insertions(+), 52 deletions(-)
diff --git a/drivers/perf/marvell_cn10k_tad_pmu.c b/drivers/perf/marvell_cn10k_tad_pmu.c
index 51ccb0befa05..69a6648fa664 100644
--- a/drivers/perf/marvell_cn10k_tad_pmu.c
+++ b/drivers/perf/marvell_cn10k_tad_pmu.c
@@ -7,6 +7,7 @@
#define pr_fmt(fmt) "tad_pmu: " fmt
#include <linux/io.h>
+#include <linux/bits.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/cpuhotplug.h>
@@ -14,12 +15,20 @@
#include <linux/platform_device.h>
#include <linux/acpi.h>
-#define TAD_PFC_OFFSET 0x800
-#define TAD_PFC(counter) (TAD_PFC_OFFSET | (counter << 3))
#define TAD_PRF_OFFSET 0x900
-#define TAD_PRF(counter) (TAD_PRF_OFFSET | (counter << 3))
+#define TAD_PFC_OFFSET 0x800
+#define TAD_PFC(base, counter) ((base) | ((u64)(counter) << 3))
+#define TAD_PRF(base, counter) ((base) | ((u64)(counter) << 3))
#define TAD_PRF_CNTSEL_MASK 0xFF
+#define TAD_PRF_MATCH_PARTID BIT(8)
+#define TAD_PRF_PARTID_NS BIT(10)
+/*
+ * config1: bits 0..8 MPAM partition id (including 0); bit 9 requests
+ * filtering for MPAM-capable events. All-zero config1 means no filter.
+ */
+#define TAD_PARTID_FILTER_EN BIT(9)
#define TAD_MAX_COUNTERS 8
+#define TAD_EVENT_SEL_MASK GENMASK(7, 0)
#define to_tad_pmu(p) (container_of(p, struct tad_pmu, pmu))
@@ -27,30 +36,92 @@ struct tad_region {
void __iomem *base;
};
+enum mrvl_tad_pmu_version {
+ TAD_PMU_V1 = 1,
+ TAD_PMU_V2,
+};
+
+struct tad_pmu_data {
+ int id;
+ u64 tad_prf_offset;
+ u64 tad_pfc_offset;
+};
+
struct tad_pmu {
struct pmu pmu;
struct tad_region *regions;
u32 region_cnt;
unsigned int cpu;
+ const struct tad_pmu_ops *ops;
+ const struct tad_pmu_data *pdata;
struct hlist_node node;
struct perf_event *events[TAD_MAX_COUNTERS];
DECLARE_BITMAP(counters_map, TAD_MAX_COUNTERS);
};
-enum mrvl_tad_pmu_version {
- TAD_PMU_V1 = 1,
- TAD_PMU_V2,
-};
-
-struct tad_pmu_data {
- int id;
+struct tad_pmu_ops {
+ void (*start_counter)(struct tad_pmu *pmu, struct perf_event *event);
};
static int tad_pmu_cpuhp_state;
+static void tad_pmu_start_counter(struct tad_pmu *pmu,
+ struct perf_event *event)
+{
+ const struct tad_pmu_data *pdata = pmu->pdata;
+ struct hw_perf_event *hwc = &event->hw;
+ u32 event_idx = (u32)(event->attr.config & TAD_EVENT_SEL_MASK);
+ u32 counter_idx = hwc->idx;
+ u64 partid_filter = 0;
+ u64 reg_val;
+ u64 cfg1 = event->attr.config1;
+ bool use_mpam = cfg1 & TAD_PARTID_FILTER_EN;
+ u32 partid = (u32)(cfg1 & GENMASK(8, 0));
+ int i;
+
+ for (i = 0; i < pmu->region_cnt; i++)
+ writeq_relaxed(0, pmu->regions[i].base +
+ TAD_PFC(pdata->tad_pfc_offset, counter_idx));
+
+ if (use_mpam && event_idx > 0x19 && event_idx < 0x21) {
+ partid_filter = TAD_PRF_MATCH_PARTID | TAD_PRF_PARTID_NS |
+ ((u64)partid << 11);
+ }
+
+
+ for (i = 0; i < pmu->region_cnt; i++) {
+ reg_val = event_idx & 0xFF;
+ reg_val |= partid_filter;
+ writeq_relaxed(reg_val, pmu->regions[i].base +
+ TAD_PRF(pdata->tad_prf_offset, counter_idx));
+ }
+}
+
+static void tad_pmu_v2_start_counter(struct tad_pmu *pmu,
+ struct perf_event *event)
+{
+ const struct tad_pmu_data *pdata = pmu->pdata;
+ struct hw_perf_event *hwc = &event->hw;
+ u32 event_idx = (u32)(event->attr.config & TAD_EVENT_SEL_MASK);
+ u32 counter_idx = hwc->idx;
+ u64 reg_val;
+ int i;
+
+ for (i = 0; i < pmu->region_cnt; i++)
+ writeq_relaxed(0, pmu->regions[i].base +
+ TAD_PFC(pdata->tad_pfc_offset, counter_idx));
+
+ for (i = 0; i < pmu->region_cnt; i++) {
+ reg_val = event_idx & 0xFF;
+ writeq_relaxed(reg_val, pmu->regions[i].base +
+ TAD_PRF(pdata->tad_prf_offset, counter_idx));
+ }
+}
+
static void tad_pmu_event_counter_read(struct perf_event *event)
{
struct tad_pmu *tad_pmu = to_tad_pmu(event->pmu);
+ const struct tad_pmu_data *pdata = tad_pmu->pdata;
struct hw_perf_event *hwc = &event->hw;
u32 counter_idx = hwc->idx;
u64 prev, new;
@@ -60,7 +131,7 @@ static void tad_pmu_event_counter_read(struct perf_event *event)
prev = local64_read(&hwc->prev_count);
for (i = 0, new = 0; i < tad_pmu->region_cnt; i++)
new += readq(tad_pmu->regions[i].base +
- TAD_PFC(counter_idx));
+ TAD_PFC(pdata->tad_pfc_offset, counter_idx));
} while (local64_cmpxchg(&hwc->prev_count, prev, new) != prev);
local64_add(new - prev, &event->count);
@@ -69,16 +140,14 @@ static void tad_pmu_event_counter_read(struct perf_event *event)
static void tad_pmu_event_counter_stop(struct perf_event *event, int flags)
{
struct tad_pmu *tad_pmu = to_tad_pmu(event->pmu);
+ const struct tad_pmu_data *pdata = tad_pmu->pdata;
struct hw_perf_event *hwc = &event->hw;
u32 counter_idx = hwc->idx;
int i;
- /* TAD()_PFC() stop counting on the write
- * which sets TAD()_PRF()[CNTSEL] == 0
- */
for (i = 0; i < tad_pmu->region_cnt; i++) {
writeq_relaxed(0, tad_pmu->regions[i].base +
- TAD_PRF(counter_idx));
+ TAD_PRF(pdata->tad_prf_offset, counter_idx));
}
tad_pmu_event_counter_read(event);
@@ -89,26 +158,10 @@ static void tad_pmu_event_counter_start(struct perf_event *event, int flags)
{
struct tad_pmu *tad_pmu = to_tad_pmu(event->pmu);
struct hw_perf_event *hwc = &event->hw;
- u32 event_idx = event->attr.config;
- u32 counter_idx = hwc->idx;
- u64 reg_val;
- int i;
hwc->state = 0;
- /* Typically TAD_PFC() are zeroed to start counting */
- for (i = 0; i < tad_pmu->region_cnt; i++)
- writeq_relaxed(0, tad_pmu->regions[i].base +
- TAD_PFC(counter_idx));
-
- /* TAD()_PFC() start counting on the write
- * which sets TAD()_PRF()[CNTSEL] != 0
- */
- for (i = 0; i < tad_pmu->region_cnt; i++) {
- reg_val = event_idx & 0xFF;
- writeq_relaxed(reg_val, tad_pmu->regions[i].base +
- TAD_PRF(counter_idx));
- }
+ tad_pmu->ops->start_counter(tad_pmu, event);
}
static void tad_pmu_event_counter_del(struct perf_event *event, int flags)
@@ -128,7 +181,6 @@ static int tad_pmu_event_counter_add(struct perf_event *event, int flags)
struct hw_perf_event *hwc = &event->hw;
int idx;
- /* Get a free counter for this event */
idx = find_first_zero_bit(tad_pmu->counters_map, TAD_MAX_COUNTERS);
if (idx == TAD_MAX_COUNTERS)
return -EAGAIN;
@@ -148,6 +200,9 @@ static int tad_pmu_event_counter_add(struct perf_event *event, int flags)
static int tad_pmu_event_init(struct perf_event *event)
{
struct tad_pmu *tad_pmu = to_tad_pmu(event->pmu);
+ const struct tad_pmu_data *pdata = tad_pmu->pdata;
+ u32 event_idx = (u32)(event->attr.config & TAD_EVENT_SEL_MASK);
+ u64 cfg1 = event->attr.config1;
if (event->attr.type != event->pmu->type)
return -ENOENT;
@@ -158,6 +213,23 @@ static int tad_pmu_event_init(struct perf_event *event)
if (event->state != PERF_EVENT_STATE_OFF)
return -EINVAL;
+ if (event->attr.config & ~TAD_EVENT_SEL_MASK)
+ return -EINVAL;
+
+ if (pdata->id == TAD_PMU_V2) {
+ if (cfg1)
+ return -EINVAL;
+ } else {
+ if ((cfg1 & GENMASK(8, 0)) && !(cfg1 & TAD_PARTID_FILTER_EN))
+ return -EINVAL;
+ if (cfg1 & TAD_PARTID_FILTER_EN) {
+ if (event_idx <= 0x19 || event_idx >= 0x21)
+ return -EINVAL;
+ }
+ if (cfg1 & ~GENMASK(9, 0))
+ return -EINVAL;
+ }
+
event->cpu = tad_pmu->cpu;
event->hw.idx = -1;
event->hw.config_base = event->attr.config;
@@ -232,7 +304,7 @@ static struct attribute *ody_tad_pmu_event_attrs[] = {
TAD_PMU_EVENT_ATTR(tad_hit_ltg, 0x1e),
TAD_PMU_EVENT_ATTR(tad_hit_any, 0x1f),
TAD_PMU_EVENT_ATTR(tad_tag_rd, 0x20),
- TAD_PMU_EVENT_ATTR(tad_tot_cycle, 0xFF),
+ TAD_PMU_EVENT_ATTR(tad_tot_cycle, 0xff),
NULL
};
@@ -242,9 +314,13 @@ static const struct attribute_group ody_tad_pmu_events_attr_group = {
};
PMU_FORMAT_ATTR(event, "config:0-7");
+PMU_FORMAT_ATTR(partid, "config1:0-8");
+PMU_FORMAT_ATTR(partid_en, "config1:9-9");
static struct attribute *tad_pmu_format_attrs[] = {
&format_attr_event.attr,
+ &format_attr_partid.attr,
+ &format_attr_partid_en.attr,
NULL
};
@@ -253,6 +329,16 @@ static struct attribute_group tad_pmu_format_attr_group = {
.attrs = tad_pmu_format_attrs,
};
+static struct attribute *ody_tad_pmu_format_attrs[] = {
+ &format_attr_event.attr,
+ NULL
+};
+
+static struct attribute_group ody_tad_pmu_format_attr_group = {
+ .name = "format",
+ .attrs = ody_tad_pmu_format_attrs,
+};
+
static ssize_t tad_pmu_cpumask_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -281,16 +367,25 @@ static const struct attribute_group *tad_pmu_attr_groups[] = {
static const struct attribute_group *ody_tad_pmu_attr_groups[] = {
&ody_tad_pmu_events_attr_group,
- &tad_pmu_format_attr_group,
+ &ody_tad_pmu_format_attr_group,
&tad_pmu_cpumask_attr_group,
NULL
};
+static const struct tad_pmu_ops tad_pmu_ops = {
+ .start_counter = tad_pmu_start_counter,
+};
+
+static const struct tad_pmu_ops tad_pmu_v2_ops = {
+ .start_counter = tad_pmu_v2_start_counter,
+};
+
static int tad_pmu_probe(struct platform_device *pdev)
{
const struct tad_pmu_data *dev_data;
struct device *dev = &pdev->dev;
struct tad_region *regions;
+ resource_size_t map_start;
struct tad_pmu *tad_pmu;
struct resource *res;
u32 tad_pmu_page_size;
@@ -298,7 +393,6 @@ static int tad_pmu_probe(struct platform_device *pdev)
u32 tad_cnt;
int version;
int i, ret;
- char *name;
tad_pmu = devm_kzalloc(&pdev->dev, sizeof(*tad_pmu), GFP_KERNEL);
if (!tad_pmu)
@@ -312,6 +406,7 @@ static int tad_pmu_probe(struct platform_device *pdev)
return -ENODEV;
}
version = dev_data->id;
+ tad_pmu->pdata = dev_data;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
@@ -338,22 +433,31 @@ static int tad_pmu_probe(struct platform_device *pdev)
dev_err(&pdev->dev, "Can't find tad-cnt property\n");
return ret;
}
+ if (!tad_cnt || !tad_page_size || !tad_pmu_page_size) {
+ dev_err(&pdev->dev, "Invalid tad-cnt or page size\n");
+ return -EINVAL;
+ }
regions = devm_kcalloc(&pdev->dev, tad_cnt,
sizeof(*regions), GFP_KERNEL);
if (!regions)
return -ENOMEM;
- /* ioremap the distributed TAD pmu regions */
- for (i = 0; i < tad_cnt && res->start < res->end; i++) {
- regions[i].base = devm_ioremap(&pdev->dev,
- res->start,
+ map_start = res->start;
+ for (i = 0; i < tad_cnt; i++) {
+ if (map_start > res->end ||
+ tad_pmu_page_size > (resource_size_t)(res->end - map_start + 1)) {
+ dev_err(&pdev->dev, "TAD PMU mem window too small for tad-cnt=%u\n",
+ tad_cnt);
+ return -EINVAL;
+ }
+ regions[i].base = devm_ioremap(&pdev->dev, map_start,
tad_pmu_page_size);
if (!regions[i].base) {
dev_err(&pdev->dev, "TAD%d ioremap fail\n", i);
return -ENOMEM;
}
- res->start += tad_page_size;
+ map_start += tad_page_size;
}
tad_pmu->regions = regions;
@@ -374,28 +478,31 @@ static int tad_pmu_probe(struct platform_device *pdev)
.read = tad_pmu_event_counter_read,
};
- if (version == TAD_PMU_V1)
+ if (version == TAD_PMU_V1) {
tad_pmu->pmu.attr_groups = tad_pmu_attr_groups;
- else
+ tad_pmu->ops = &tad_pmu_ops;
+ } else {
tad_pmu->pmu.attr_groups = ody_tad_pmu_attr_groups;
+ tad_pmu->ops = &tad_pmu_v2_ops;
+ }
tad_pmu->cpu = raw_smp_processor_id();
- /* Register pmu instance for cpu hotplug */
+ ret = perf_pmu_register(&tad_pmu->pmu, "tad", -1);
+ if (ret) {
+ dev_err(&pdev->dev, "Error %d registering perf PMU\n", ret);
+ return ret;
+ }
+
ret = cpuhp_state_add_instance_nocalls(tad_pmu_cpuhp_state,
&tad_pmu->node);
if (ret) {
dev_err(&pdev->dev, "Error %d registering hotplug\n", ret);
+ perf_pmu_unregister(&tad_pmu->pmu);
return ret;
}
- name = "tad";
- ret = perf_pmu_register(&tad_pmu->pmu, name, -1);
- if (ret)
- cpuhp_state_remove_instance_nocalls(tad_pmu_cpuhp_state,
- &tad_pmu->node);
-
- return ret;
+ return 0;
}
static void tad_pmu_remove(struct platform_device *pdev)
@@ -410,12 +517,17 @@ static void tad_pmu_remove(struct platform_device *pdev)
#if defined(CONFIG_OF) || defined(CONFIG_ACPI)
static const struct tad_pmu_data tad_pmu_data = {
.id = TAD_PMU_V1,
+ .tad_prf_offset = TAD_PRF_OFFSET,
+ .tad_pfc_offset = TAD_PFC_OFFSET,
};
+
#endif
#ifdef CONFIG_ACPI
static const struct tad_pmu_data tad_pmu_v2_data = {
.id = TAD_PMU_V2,
+ .tad_prf_offset = TAD_PRF_OFFSET,
+ .tad_pfc_offset = TAD_PFC_OFFSET,
};
#endif
@@ -491,6 +603,6 @@ static void __exit tad_pmu_exit(void)
module_init(tad_pmu_init);
module_exit(tad_pmu_exit);
-MODULE_DESCRIPTION("Marvell CN10K LLC-TAD Perf driver");
+MODULE_DESCRIPTION("Marvell CN10K LLC-TAD perf driver");
MODULE_AUTHOR("Bhaskara Budiredla <bbudiredla@marvell.com>");
MODULE_LICENSE("GPL v2");
--
2.25.1
^ permalink raw reply related
* [PATCH v3 0/3] perf: marvell: LLC-TAD PMU MPAM filtering support
From: Geetha sowjanya @ 2026-06-16 7:11 UTC (permalink / raw)
To: linux-perf-users, linux-kernel, linux-arm-kernel, devicetree
Cc: mark.rutland, will, krzk+dt, gakula
This series extends the Marvell LLC-TAD performance driver used on CN10K
and CN20K systems.
Patch 1 adds optional MPAM partition-id filtering for the subset of TAD
events that support it, exposes partid / partid_en in the PMU format string,
and keeps the reduced Odyssey event surface without advertising partid where
it does not apply. It also fixes probe resource handling (no in-place
mutation of platform_get_resource() bounds, validate MMIO window vs
tad-cnt), orders perf registration vs hotplug with unwind, and aligns the
filter-enable bit in config1 with the sysfs format (bit 9).
Patch 2 introduces CN20K LLC-TAD support: non-standard PFC/PRF offsets,
additional programmable events with visibility checks so CN10K does not
advertise V3-only events, CN20K-specific MPAM encoding for the V3 profile,
local64_set(prev_count) on counter start, and device discovery via OF and
ACPI.
Patch 3 extends the DeviceTree binding for marvell,cn20k-tad-pmu.
Changes since v2
----------------
- Validate the eventId using an appropriate mask to ensure it is restricted to 8 bits.
Changes since v1
----------------
- config1: use bit 9 for MPAM filter enable consistently with partid_en in
the PMU format; allow only bits 0..9 in event_init on CN10K/CN20K paths.
- Reject reserved bits in attr.config and use the same 8-bit event index in
start_counter as in event_init so MPAM validation cannot be bypassed.
- Hide V3-only sysfs events on V1.
- Reset prev_count when starting counters after clearing hardware.
- DT binding: explain non-fallback compatibles for CN10K vs CN20K.
Tanmay Jagdale (1):
perf: marvell: Add MPAM partid filtering to CN10K TAD PMU
Geetha sowjanya (2):
perf: marvell: Add CN20K LLC-TAD PMU support
dt-bindings: perf: marvell: Extend CN10K TAD PMU binding for CN20K
Signed-off-by: Geetha sowjanya <gakula@marvell.com>
--
2.25.1
^ permalink raw reply
* Re: [PATCH v3 2/2] drm/tiny: add support for PIXPAPER 4.26 monochrome e-ink panel
From: Devarsh Thakkar @ 2026-06-16 7:08 UTC (permalink / raw)
To: LiangCheng Wang, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Wig Cheng
Cc: dri-devel, devicetree, linux-kernel, Tomi Valkeinen
In-Reply-To: <20260529-bar-v3-2-5c2ac1c751ee@gmail.com>
Hi LiangCheng,
Thanks for the patch.
On 29/05/26 16:01, LiangCheng Wang wrote:
> Introduce a DRM driver for the Mayqueen Pixpaper 4.26
> monochrome e-ink display panel, which is controlled via SPI.
> The driver supports an 800x480 display with XRGB8888
> framebuffer input.
>
1) Could you please share the datasheet for the display controller used
inside this pixpaper version ?
The PIXPAPER 4.26 command set matches the Solomon SSD1683 almost exactly
as documented in the SSD1683 datasheet [0]. It can also be seen that the
macro values match exactly as well with the posted driver [1].
The only material difference is the ctrl2 update byte 0xF4 vs the
SSD1683 documented sequences (0xFF for BW full refresh, 0xF7 for
3-color). 0xF4 omits the final Disable Analog + Disable OSC bits that
SSD1683 normally expects.
2) Does 0xFF or 0xF7 mode work as well for your display or is it
strictly 0xF4 which seems to mean that analog and osc bits are disabled
?
3) Also could you confirm which display controller IC does the PIXPAPER
4.26 use ? If it is SSD1683 (or any other ssd16xx variant), it would be
appropriate to add this panel as a new display panel entry in
panel-ssd16xx.c rather than a separate driver to avoid code duplication.
You can refer how "Good Display GDEY042T81" was added for e.g. it should
be something like :
[PIXPAPER426M] = {
.data_entry_mode = SSD16XX_DATA_ENTRY_XINC_YINC,
.driver_output_ctrl_byte3 = 0x02, /* SM=1 interlaced scan */
.default_refresh_mode = SSD16XX_REFRESH_FULL, /* single
mode: 0xF4 */
.default_border_waveform_init = SSD16XX_BORDER_LUT1,
.default_border_waveform_update = SSD16XX_BORDER_LUT1,
.default_clear_on_init = -1,
.default_clear_on_disable = -1,
.red_supported = false,
.mode = &pixpaper426m_mode,
},
and a new compatible entry :
static const struct of_device_id ssd16xx_of_match[] = {
{ .compatible = "gooddisplay,gdey042t81", .data = (void
*)GDEY042T81 },
{ .compatible = "mayqueen,pixpaper-426m", .data = (void
*)PIXPAPER426M },
{ }
};
[0] : https://www.crystalfontz.com/controllers/SolomonSystech/SSD1683
[1] : https://lore.kernel.org/all/20260430183311.2978142-4-devarsht@ti.com/
Kindly let me know if any queries.
Regards
Devarsh
> Also, add Kconfig and Makefile entries for the driver and
> update MAINTAINERS for the Pixpaper DRM drivers and binding.
>
> Signed-off-by: LiangCheng Wang <zaq14760@gmail.com>
> ---
> MAINTAINERS | 3 +-
> drivers/gpu/drm/tiny/Kconfig | 16 +
> drivers/gpu/drm/tiny/Makefile | 1 +
> drivers/gpu/drm/tiny/pixpaper-426m.c | 817 +++++++++++++++++++++++++++++++++++
> 4 files changed, 836 insertions(+), 1 deletion(-)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 882214b0e7db53bb8cc8e75b5d2269ee0591ea20..eebd73ee1f531d3785ec963da03fbab265c2d188 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -8234,11 +8234,12 @@ T: git https://gitlab.freedesktop.org/drm/misc/kernel.git
> F: Documentation/devicetree/bindings/display/repaper.txt
> F: drivers/gpu/drm/tiny/repaper.c
>
> -DRM DRIVER FOR PIXPAPER E-INK PANEL
> +DRM DRIVER FOR PIXPAPER E-INK PANELS
> M: LiangCheng Wang <zaq14760@gmail.com>
> L: dri-devel@lists.freedesktop.org
> S: Maintained
> F: Documentation/devicetree/bindings/display/mayqueen,pixpaper.yaml
> +F: drivers/gpu/drm/tiny/pixpaper-426m.c
> F: drivers/gpu/drm/tiny/pixpaper.c
>
> DRM DRIVER FOR QEMU'S CIRRUS DEVICE
> diff --git a/drivers/gpu/drm/tiny/Kconfig b/drivers/gpu/drm/tiny/Kconfig
> index f0e72d4b6a4709564e63c758e857bdb4a320dbe7..028c4314106ac31dfa717f6433c28e58b34c21e8 100644
> --- a/drivers/gpu/drm/tiny/Kconfig
> +++ b/drivers/gpu/drm/tiny/Kconfig
> @@ -98,6 +98,22 @@ config DRM_PIXPAPER
>
> If M is selected, the module will be built as pixpaper.ko.
>
> +config DRM_PIXPAPER_426M
> + tristate "DRM support for PIXPAPER 4.26 monochrome display panel"
> + depends on DRM && SPI
> + depends on MMU
> + select DRM_CLIENT_SELECTION
> + select DRM_GEM_SHMEM_HELPER
> + select DRM_KMS_HELPER
> + help
> + DRM driver for the Mayqueen Pixpaper 4.26 monochrome e-ink
> + display panel.
> +
> + This driver supports SPI-connected 800x480 monochrome panels
> + with an XRGB8888 framebuffer input format.
> +
> + If M is selected, the module will be built as pixpaper-426m.ko.
> +
> config TINYDRM_HX8357D
> tristate "DRM support for HX8357D display panels"
> depends on DRM && SPI
> diff --git a/drivers/gpu/drm/tiny/Makefile b/drivers/gpu/drm/tiny/Makefile
> index 48d30bf6152f979404ac1004174587823a30109e..037b751a1a851cc2f86f701ff71008bcb9c59f29 100644
> --- a/drivers/gpu/drm/tiny/Makefile
> +++ b/drivers/gpu/drm/tiny/Makefile
> @@ -7,6 +7,7 @@ obj-$(CONFIG_DRM_CIRRUS_QEMU) += cirrus-qemu.o
> obj-$(CONFIG_DRM_GM12U320) += gm12u320.o
> obj-$(CONFIG_DRM_PANEL_MIPI_DBI) += panel-mipi-dbi.o
> obj-$(CONFIG_DRM_PIXPAPER) += pixpaper.o
> +obj-$(CONFIG_DRM_PIXPAPER_426M) += pixpaper-426m.o
> obj-$(CONFIG_TINYDRM_HX8357D) += hx8357d.o
> obj-$(CONFIG_TINYDRM_ILI9163) += ili9163.o
> obj-$(CONFIG_TINYDRM_ILI9225) += ili9225.o
> diff --git a/drivers/gpu/drm/tiny/pixpaper-426m.c b/drivers/gpu/drm/tiny/pixpaper-426m.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..99464d564f315543037a3621ff85f98f1bd8f34c
> --- /dev/null
> +++ b/drivers/gpu/drm/tiny/pixpaper-426m.c
> @@ -0,0 +1,817 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * DRM driver for PIXPAPER 4.26 monochrome e-ink panel
> + *
> + * Author: LiangCheng Wang <zaq14760@gmail.com>,
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/module.h>
> +#include <linux/spi/spi.h>
> +#include <linux/string.h>
> +
> +#include <drm/clients/drm_client_setup.h>
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_drv.h>
> +#include <drm/drm_fbdev_shmem.h>
> +#include <drm/drm_framebuffer.h>
> +#include <drm/drm_gem_atomic_helper.h>
> +#include <drm/drm_gem_shmem_helper.h>
> +#include <drm/drm_gem_framebuffer_helper.h>
> +#include <drm/drm_print.h>
> +#include <drm/drm_probe_helper.h>
> +
> +MODULE_IMPORT_NS("DMA_BUF");
> +
> +/* Panel visible resolution */
> +#define PIXPAPER_WIDTH 800
> +#define PIXPAPER_HEIGHT 480
> +
> +/*
> + * The panel datasheet specifies an active area of 92.8 mm x 55.68 mm.
> + * Round to whole millimeters for drm_display_info.
> + */
> +#define PIXPAPER_WIDTH_MM 93
> +#define PIXPAPER_HEIGHT_MM 56
> +
> +/*
> + * According to the panel datasheet, no RGB-style timing parameters
> + * (porches, sync widths, or a dot clock) are provided. Define a minimal
> + * fixed mode only to satisfy the DRM mode API for this SPI-driven
> + * e-paper panel.
> + */
> +#define PIXPAPER_HSYNC_LEN 1
> +#define PIXPAPER_HFRONT_PORCH 1
> +#define PIXPAPER_HBACK_PORCH 1
> +#define PIXPAPER_VSYNC_LEN 1
> +#define PIXPAPER_VFRONT_PORCH 1
> +#define PIXPAPER_VBACK_PORCH 1
> +#define PIXPAPER_MODE_REFRESH_HZ 1
> +#define PIXPAPER_MODE_CLOCK_KHZ \
> + (((PIXPAPER_WIDTH + PIXPAPER_HFRONT_PORCH + PIXPAPER_HSYNC_LEN + \
> + PIXPAPER_HBACK_PORCH) * \
> + (PIXPAPER_HEIGHT + PIXPAPER_VFRONT_PORCH + PIXPAPER_VSYNC_LEN + \
> + PIXPAPER_VBACK_PORCH) * \
> + PIXPAPER_MODE_REFRESH_HZ) / 1000)
> +
> +#define PIXPAPER_SPI_BITS_PER_WORD 8
> +#define PIXPAPER_SPI_SPEED_DEFAULT 1000000
> +
> +#define PIXPAPER_TX_BUF_SIZE 8
> +
> +#define PIXPAPER_PIXEL_THRESHOLD 128
> +
> +#define PIXPAPER_BUSY_TIMEOUT_MS 10000
> +#define PIXPAPER_BUSY_POLL_INITIAL_US_MIN 1000
> +#define PIXPAPER_BUSY_POLL_INITIAL_US_MAX 1500
> +#define PIXPAPER_BUSY_POLL_US_MIN 100
> +#define PIXPAPER_BUSY_POLL_US_MAX 200
> +
> +#define PIXPAPER_RAM_START_ADDR 0x00
> +
> +#define PIXPAPER_LUMA_R_WEIGHT 299
> +#define PIXPAPER_LUMA_G_WEIGHT 587
> +#define PIXPAPER_LUMA_B_WEIGHT 114
> +#define PIXPAPER_LUMA_DIVISOR 1000
> +#define PIXPAPER_LUMA_ROUNDING_BIAS 500
> +
> +#define PIXPAPER_CMD_DRIVER_OUTPUT_CTRL 0x01
> +#define PIXPAPER_CMD_BOOSTER_SOFT_START_CTRL 0x0C
> +#define PIXPAPER_CMD_TEMP_SENSOR_CONTROL 0x18
> +#define PIXPAPER_CMD_MASTER_ACTIVATION 0x20
> +#define PIXPAPER_CMD_DISPLAY_UPDATE_CTRL2 0x22
> +#define PIXPAPER_CMD_WRITE_RAM_BW 0x24
> +#define PIXPAPER_CMD_BORDER_WAVEFORM_CONTROL 0x3C
> +#define PIXPAPER_CMD_SET_RAM_X_START_END 0x44
> +#define PIXPAPER_CMD_SET_RAM_Y_START_END 0x45
> +#define PIXPAPER_CMD_SET_RAM_X_ADDR_COUNTER 0x4E
> +#define PIXPAPER_CMD_SET_RAM_Y_ADDR_COUNTER 0x4F
> +
> +#define PIXPAPER_DRIVER_OUTPUT_SM BIT(1)
> +
> +#define PIXPAPER_BORDER_WAVEFORM_GS_TRANSITION (0x0 << 6)
> +#define PIXPAPER_BORDER_WAVEFORM_LUT1_SEL 0x1
> +
> +#define PIXPAPER_UPDATE_CTRL2_ENABLE_CLK BIT(7)
> +#define PIXPAPER_UPDATE_CTRL2_ENABLE_ANALOG BIT(6)
> +#define PIXPAPER_UPDATE_CTRL2_LOAD_TEMP BIT(5)
> +#define PIXPAPER_UPDATE_CTRL2_LOAD_LUT BIT(4)
> +#define PIXPAPER_UPDATE_CTRL2_PATTERN_DISPLAY BIT(2)
> +
> +#define PIXPAPER_TEMP_SENSOR_INTERNAL 0x80
> +#define PIXPAPER_SOFTSTART_A 0xAE
> +#define PIXPAPER_SOFTSTART_B 0xC7
> +#define PIXPAPER_SOFTSTART_C 0xC3
> +#define PIXPAPER_SOFTSTART_D 0xC0
> +#define PIXPAPER_SOFTSTART_E 0x80
> +#define PIXPAPER_DRIVER_OUTPUT_GD_SM_TB PIXPAPER_DRIVER_OUTPUT_SM
> +#define PIXPAPER_BORDER_LUT1 \
> + (PIXPAPER_BORDER_WAVEFORM_GS_TRANSITION | \
> + PIXPAPER_BORDER_WAVEFORM_LUT1_SEL)
> +#define PIXPAPER_UPDATE_INITIAL \
> + (PIXPAPER_UPDATE_CTRL2_ENABLE_CLK | \
> + PIXPAPER_UPDATE_CTRL2_ENABLE_ANALOG | \
> + PIXPAPER_UPDATE_CTRL2_LOAD_TEMP | \
> + PIXPAPER_UPDATE_CTRL2_LOAD_LUT | \
> + PIXPAPER_UPDATE_CTRL2_PATTERN_DISPLAY)
> +struct pixpaper_error_ctx {
> + int errno_code;
> +};
> +
> +struct pixpaper_init_seq {
> + u8 cmd;
> + const u8 *data;
> + u8 len;
> +};
> +
> +struct pixpaper_panel {
> + struct drm_device drm;
> + struct drm_plane plane;
> + struct drm_crtc crtc;
> + struct drm_encoder encoder;
> + struct drm_connector connector;
> +
> + struct spi_device *spi;
> + struct gpio_desc *reset;
> + struct gpio_desc *busy;
> + struct gpio_desc *dc;
> +
> + u8 *tx_buf;
> +};
> +
> +static const uint32_t pixpaper_formats[] = {
> + DRM_FORMAT_XRGB8888,
> +};
> +
> +static const u8 pixpaper_init_temp_sensor[] = {
> + PIXPAPER_TEMP_SENSOR_INTERNAL,
> +};
> +
> +static const u8 pixpaper_init_softstart[] = {
> + PIXPAPER_SOFTSTART_A,
> + PIXPAPER_SOFTSTART_B,
> + PIXPAPER_SOFTSTART_C,
> + PIXPAPER_SOFTSTART_D,
> + PIXPAPER_SOFTSTART_E,
> +};
> +
> +static const u8 pixpaper_init_driver_output[] = {
> + (PIXPAPER_HEIGHT - 1) & 0xff,
> + (PIXPAPER_HEIGHT - 1) >> 8,
> + PIXPAPER_DRIVER_OUTPUT_GD_SM_TB,
> +};
> +
> +static const u8 pixpaper_init_border[] = {
> + PIXPAPER_BORDER_LUT1,
> +};
> +
> +static const u8 pixpaper_init_ram_x_window[] = {
> + PIXPAPER_RAM_START_ADDR,
> + PIXPAPER_RAM_START_ADDR,
> + (PIXPAPER_WIDTH - 1) & 0xff,
> + (PIXPAPER_WIDTH - 1) >> 8,
> +};
> +
> +static const u8 pixpaper_init_ram_y_window[] = {
> + PIXPAPER_RAM_START_ADDR,
> + PIXPAPER_RAM_START_ADDR,
> + (PIXPAPER_HEIGHT - 1) & 0xff,
> + (PIXPAPER_HEIGHT - 1) >> 8,
> +};
> +
> +static const u8 pixpaper_init_ram_x_counter[] = {
> + PIXPAPER_RAM_START_ADDR,
> + PIXPAPER_RAM_START_ADDR,
> +};
> +
> +static const u8 pixpaper_init_ram_y_counter[] = {
> + PIXPAPER_RAM_START_ADDR,
> + PIXPAPER_RAM_START_ADDR,
> +};
> +
> +static const struct pixpaper_init_seq pixpaper_init_seqs[] = {
> + {
> + .cmd = PIXPAPER_CMD_TEMP_SENSOR_CONTROL,
> + .data = pixpaper_init_temp_sensor,
> + .len = ARRAY_SIZE(pixpaper_init_temp_sensor),
> + },
> + {
> + .cmd = PIXPAPER_CMD_BOOSTER_SOFT_START_CTRL,
> + .data = pixpaper_init_softstart,
> + .len = ARRAY_SIZE(pixpaper_init_softstart),
> + },
> + {
> + .cmd = PIXPAPER_CMD_DRIVER_OUTPUT_CTRL,
> + .data = pixpaper_init_driver_output,
> + .len = ARRAY_SIZE(pixpaper_init_driver_output),
> + },
> + {
> + .cmd = PIXPAPER_CMD_BORDER_WAVEFORM_CONTROL,
> + .data = pixpaper_init_border,
> + .len = ARRAY_SIZE(pixpaper_init_border),
> + },
> + {
> + .cmd = PIXPAPER_CMD_SET_RAM_X_START_END,
> + .data = pixpaper_init_ram_x_window,
> + .len = ARRAY_SIZE(pixpaper_init_ram_x_window),
> + },
> + {
> + .cmd = PIXPAPER_CMD_SET_RAM_Y_START_END,
> + .data = pixpaper_init_ram_y_window,
> + .len = ARRAY_SIZE(pixpaper_init_ram_y_window),
> + },
> + {
> + .cmd = PIXPAPER_CMD_SET_RAM_X_ADDR_COUNTER,
> + .data = pixpaper_init_ram_x_counter,
> + .len = ARRAY_SIZE(pixpaper_init_ram_x_counter),
> + },
> + {
> + .cmd = PIXPAPER_CMD_SET_RAM_Y_ADDR_COUNTER,
> + .data = pixpaper_init_ram_y_counter,
> + .len = ARRAY_SIZE(pixpaper_init_ram_y_counter),
> + },
> +};
> +
> +static inline struct pixpaper_panel *to_pixpaper_panel(struct drm_device *drm)
> +{
> + return container_of(drm, struct pixpaper_panel, drm);
> +}
> +
> +static void pixpaper_wait_for_panel(struct pixpaper_panel *panel)
> +{
> + unsigned int timeout_ms = PIXPAPER_BUSY_TIMEOUT_MS;
> + unsigned long timeout_jiffies = jiffies + msecs_to_jiffies(timeout_ms);
> +
> + usleep_range(PIXPAPER_BUSY_POLL_INITIAL_US_MIN,
> + PIXPAPER_BUSY_POLL_INITIAL_US_MAX);
> + while (gpiod_get_value_cansleep(panel->busy) != 0) {
> + if (time_after(jiffies, timeout_jiffies)) {
> + /*
> + * Treat a busy timeout as warning-only. Some panels may
> + * keep BUSY asserted longer than expected during
> + * initialization or refresh.
> + */
> + drm_warn(&panel->drm, "Busy wait timed out\n");
> + return;
> + }
> + usleep_range(PIXPAPER_BUSY_POLL_US_MIN,
> + PIXPAPER_BUSY_POLL_US_MAX);
> + }
> +}
> +
> +static void pixpaper_spi_write(struct pixpaper_panel *panel, int dc,
> + const void *buf, size_t len,
> + struct pixpaper_error_ctx *err)
> +{
> + int ret;
> +
> + if (err->errno_code || !len)
> + return;
> +
> + gpiod_set_value_cansleep(panel->dc, dc);
> + usleep_range(1, 5);
> +
> + ret = spi_write(panel->spi, buf, len);
> + if (ret < 0)
> + err->errno_code = ret;
> +}
> +
> +static void pixpaper_send_cmd(struct pixpaper_panel *panel, u8 cmd,
> + struct pixpaper_error_ctx *err)
> +{
> + panel->tx_buf[0] = cmd;
> + pixpaper_spi_write(panel, 0, panel->tx_buf, sizeof(cmd), err);
> +}
> +
> +static void pixpaper_send_data(struct pixpaper_panel *panel, u8 data,
> + struct pixpaper_error_ctx *err)
> +{
> + panel->tx_buf[0] = data;
> + pixpaper_spi_write(panel, 1, panel->tx_buf, sizeof(data), err);
> +}
> +
> +static void pixpaper_reset_ram_counters(struct pixpaper_panel *panel,
> + struct pixpaper_error_ctx *err)
> +{
> + if (err->errno_code)
> + return;
> +
> + pixpaper_send_cmd(panel, PIXPAPER_CMD_SET_RAM_X_ADDR_COUNTER, err);
> + pixpaper_send_data(panel, PIXPAPER_RAM_START_ADDR, err);
> + pixpaper_send_data(panel, PIXPAPER_RAM_START_ADDR, err);
> +
> + pixpaper_send_cmd(panel, PIXPAPER_CMD_SET_RAM_Y_ADDR_COUNTER, err);
> + pixpaper_send_data(panel, PIXPAPER_RAM_START_ADDR, err);
> + pixpaper_send_data(panel, PIXPAPER_RAM_START_ADDR, err);
> +}
> +
> +static void pixpaper_write_ram(struct pixpaper_panel *panel, u8 cmd,
> + const u8 *buf, u32 len,
> + struct pixpaper_error_ctx *err)
> +{
> + if (err->errno_code || !buf || !len)
> + return;
> +
> + pixpaper_reset_ram_counters(panel, err);
> +
> + pixpaper_send_cmd(panel, cmd, err);
> + pixpaper_spi_write(panel, 1, buf, len, err);
> +}
> +
> +static void pixpaper_send_init_seq(struct pixpaper_panel *panel,
> + const struct pixpaper_init_seq *seq,
> + struct pixpaper_error_ctx *err)
> +{
> + if (err->errno_code || !seq->data || !seq->len)
> + return;
> +
> + if (seq->len > PIXPAPER_TX_BUF_SIZE) {
> + err->errno_code = -EINVAL;
> + return;
> + }
> +
> + pixpaper_send_cmd(panel, seq->cmd, err);
> + memcpy(panel->tx_buf, seq->data, seq->len);
> + pixpaper_spi_write(panel, 1, panel->tx_buf, seq->len, err);
> +}
> +
> +static void pixpaper_trigger_update(struct pixpaper_panel *panel,
> + struct pixpaper_error_ctx *err)
> +{
> + if (err->errno_code)
> + return;
> +
> + pixpaper_send_cmd(panel, PIXPAPER_CMD_DISPLAY_UPDATE_CTRL2, err);
> + pixpaper_send_data(panel, PIXPAPER_UPDATE_INITIAL, err);
> + pixpaper_send_cmd(panel, PIXPAPER_CMD_MASTER_ACTIVATION, err);
> + pixpaper_wait_for_panel(panel);
> +}
> +
> +static void pixpaper_xrgb8888_to_bw(const void *src, void *dst, u32 height,
> + u32 width, u32 src_pitch, u32 dst_pitch)
> +{
> + const uint8_t *src_base = src;
> + uint8_t *dst_pixels = dst;
> +
> + if (dst == NULL || src == NULL)
> + return;
> +
> + for (u32 y = 0; y < height; y++) {
> + uint8_t *dst_row = dst_pixels + y * dst_pitch;
> + const __le32 *src_pixels =
> + (const __le32 *)(src_base + y * src_pitch);
> +
> + for (u32 x = 0; x < width; x++) {
> + /*
> + * The panel RAM X direction is reversed relative to DRM
> + * coordinates. Read pixels from right to left so the
> + * displayed image matches the expected orientation on
> + * the panel.
> + */
> + u32 src_x = width - 1 - x;
> + uint8_t r, g, b;
> + u8 bit;
> + u32 bit_pos = x % 8;
> + u32 byte_pos = x / 8;
> + uint32_t gray_val;
> + uint32_t pixel;
> +
> + pixel = le32_to_cpu(src_pixels[src_x]);
> + r = (pixel >> 16) & 0xFF;
> + g = (pixel >> 8) & 0xFF;
> + b = pixel & 0xFF;
> +
> + gray_val = (r * PIXPAPER_LUMA_R_WEIGHT +
> + g * PIXPAPER_LUMA_G_WEIGHT +
> + b * PIXPAPER_LUMA_B_WEIGHT +
> + PIXPAPER_LUMA_ROUNDING_BIAS) /
> + PIXPAPER_LUMA_DIVISOR;
> + bit = gray_val >= PIXPAPER_PIXEL_THRESHOLD;
> +
> + if (bit)
> + dst_row[byte_pos] |= BIT(7 - bit_pos);
> + else
> + dst_row[byte_pos] &= ~BIT(7 - bit_pos);
> + }
> + }
> +}
> +
> +static void *pixpaper_prepare_buffer(const void *vaddr,
> + const struct drm_framebuffer *fb,
> + u32 *dst_pitch,
> + struct pixpaper_error_ctx *err)
> +{
> + void *dst;
> +
> + if (err->errno_code)
> + return NULL;
> +
> + *dst_pitch = DIV_ROUND_UP(fb->width, 8);
> + dst = kzalloc(*dst_pitch * fb->height, GFP_KERNEL);
> + if (!dst) {
> + err->errno_code = -ENOMEM;
> + return NULL;
> + }
> +
> + pixpaper_xrgb8888_to_bw(vaddr, dst, fb->height, fb->width,
> + fb->pitches[0], *dst_pitch);
> +
> + return dst;
> +}
> +
> +static void pixpaper_write_image(struct pixpaper_panel *panel,
> + const u8 *buf, u32 len,
> + struct pixpaper_error_ctx *err)
> +{
> + if (err->errno_code)
> + return;
> +
> + pixpaper_write_ram(panel, PIXPAPER_CMD_WRITE_RAM_BW, buf, len, err);
> +}
> +
> +static int pixpaper_panel_hw_init(struct pixpaper_panel *panel)
> +{
> + struct pixpaper_error_ctx err = { .errno_code = 0 };
> + u8 i;
> +
> + gpiod_set_value_cansleep(panel->reset, 0);
> + msleep(50);
> + gpiod_set_value_cansleep(panel->reset, 1);
> + msleep(50);
> +
> + pixpaper_wait_for_panel(panel);
> +
> + for (i = 0; i < ARRAY_SIZE(pixpaper_init_seqs); i++) {
> + pixpaper_send_init_seq(panel, &pixpaper_init_seqs[i], &err);
> + if (err.errno_code)
> + goto init_fail;
> + }
> +
> + return 0;
> +
> +init_fail:
> + drm_err(&panel->drm, "Hardware initialization failed (err=%d)\n",
> + err.errno_code);
> + return err.errno_code;
> +}
> +
> +static int pixpaper_plane_helper_atomic_check(struct drm_plane *plane,
> + struct drm_atomic_state *state)
> +{
> + struct drm_plane_state *new_plane_state =
> + drm_atomic_get_new_plane_state(state, plane);
> + struct drm_crtc *new_crtc = new_plane_state->crtc;
> + struct drm_crtc_state *new_crtc_state = NULL;
> + int ret;
> +
> + if (new_crtc)
> + new_crtc_state = drm_atomic_get_new_crtc_state(state, new_crtc);
> +
> + ret = drm_atomic_helper_check_plane_state(new_plane_state,
> + new_crtc_state, DRM_PLANE_NO_SCALING,
> + DRM_PLANE_NO_SCALING, false, false);
> + if (ret)
> + return ret;
> +
> + return 0;
> +}
> +
> +static int pixpaper_crtc_helper_atomic_check(struct drm_crtc *crtc,
> + struct drm_atomic_state *state)
> +{
> + struct drm_crtc_state *crtc_state =
> + drm_atomic_get_new_crtc_state(state, crtc);
> +
> + if (!crtc_state->enable)
> + return 0;
> +
> + return drm_atomic_helper_check_crtc_primary_plane(crtc_state);
> +}
> +
> +static void pixpaper_crtc_atomic_enable(struct drm_crtc *crtc,
> + struct drm_atomic_state *state)
> +{
> + struct pixpaper_panel *panel = to_pixpaper_panel(crtc->dev);
> + struct drm_device *drm = &panel->drm;
> + int idx;
> +
> + if (!drm_dev_enter(drm, &idx))
> + return;
> +
> + drm_dev_exit(idx);
> +}
> +
> +static void pixpaper_crtc_atomic_disable(struct drm_crtc *crtc,
> + struct drm_atomic_state *state)
> +{
> + struct pixpaper_panel *panel = to_pixpaper_panel(crtc->dev);
> + struct drm_device *drm = &panel->drm;
> + int idx;
> +
> + if (!drm_dev_enter(drm, &idx))
> + return;
> +
> + drm_dev_exit(idx);
> +}
> +
> +static void pixpaper_plane_atomic_update(struct drm_plane *plane,
> + struct drm_atomic_state *state)
> +{
> + struct drm_plane_state *plane_state =
> + drm_atomic_get_new_plane_state(state, plane);
> + struct drm_shadow_plane_state *shadow_plane_state =
> + to_drm_shadow_plane_state(plane_state);
> + struct pixpaper_panel *panel = to_pixpaper_panel(plane->dev);
> +
> + if (!plane_state->crtc || !plane_state->fb || !plane_state->visible)
> + return;
> +
> + {
> + struct drm_device *drm = &panel->drm;
> + struct drm_framebuffer *fb = plane_state->fb;
> + struct iosys_map map = shadow_plane_state->data[0];
> + const void *vaddr = map.vaddr;
> + int idx;
> + struct pixpaper_error_ctx err = { .errno_code = 0 };
> + uint32_t dst_pitch;
> + void *dst = NULL;
> + u32 dst_len;
> +
> + if (!drm_dev_enter(drm, &idx))
> + return;
> +
> + if (fb->format->format != DRM_FORMAT_XRGB8888) {
> + err.errno_code = -EINVAL;
> + drm_err_once(drm, "Unsupported framebuffer format: 0x%08x\n",
> + fb->format->format);
> + goto update_cleanup;
> + }
> +
> + dst = pixpaper_prepare_buffer(vaddr, fb, &dst_pitch, &err);
> + if (err.errno_code) {
> + drm_err_once(drm, "Failed to allocate temporary buffer\n");
> + goto update_cleanup;
> + }
> +
> + dst_len = dst_pitch * fb->height;
> + pixpaper_write_image(panel, dst, dst_len, &err);
> + if (err.errno_code)
> + goto update_cleanup;
> +
> + pixpaper_trigger_update(panel, &err);
> + if (err.errno_code)
> + goto update_cleanup;
> +update_cleanup:
> + if (err.errno_code && err.errno_code != -ETIMEDOUT)
> + drm_err_once(drm, "Frame update failed: %d\n",
> + err.errno_code);
> +
> + kfree(dst);
> + drm_dev_exit(idx);
> + }
> +}
> +
> +static const struct drm_display_mode pixpaper_mode = {
> + .clock = PIXPAPER_MODE_CLOCK_KHZ,
> + .hdisplay = PIXPAPER_WIDTH,
> + .hsync_start = PIXPAPER_WIDTH + PIXPAPER_HFRONT_PORCH,
> + .hsync_end = PIXPAPER_WIDTH + PIXPAPER_HFRONT_PORCH + PIXPAPER_HSYNC_LEN,
> + .htotal = PIXPAPER_WIDTH + PIXPAPER_HFRONT_PORCH + PIXPAPER_HSYNC_LEN +
> + PIXPAPER_HBACK_PORCH,
> + .vdisplay = PIXPAPER_HEIGHT,
> + .vsync_start = PIXPAPER_HEIGHT + PIXPAPER_VFRONT_PORCH,
> + .vsync_end = PIXPAPER_HEIGHT + PIXPAPER_VFRONT_PORCH + PIXPAPER_VSYNC_LEN,
> + .vtotal = PIXPAPER_HEIGHT + PIXPAPER_VFRONT_PORCH + PIXPAPER_VSYNC_LEN +
> + PIXPAPER_VBACK_PORCH,
> + .width_mm = PIXPAPER_WIDTH_MM,
> + .height_mm = PIXPAPER_HEIGHT_MM,
> + .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
> +};
> +
> +static int pixpaper_connector_get_modes(struct drm_connector *connector)
> +{
> + return drm_connector_helper_get_modes_fixed(connector, &pixpaper_mode);
> +}
> +
> +static enum drm_mode_status
> +pixpaper_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode)
> +{
> + if (mode->hdisplay == PIXPAPER_WIDTH &&
> + mode->vdisplay == PIXPAPER_HEIGHT)
> + return MODE_OK;
> +
> + return MODE_BAD;
> +}
> +
> +static const struct drm_plane_funcs pixpaper_plane_funcs = {
> + .update_plane = drm_atomic_helper_update_plane,
> + .disable_plane = drm_atomic_helper_disable_plane,
> + .destroy = drm_plane_cleanup,
> + DRM_GEM_SHADOW_PLANE_FUNCS,
> +};
> +
> +static const struct drm_plane_helper_funcs pixpaper_plane_helper_funcs = {
> + DRM_GEM_SHADOW_PLANE_HELPER_FUNCS,
> + .atomic_check = pixpaper_plane_helper_atomic_check,
> + .atomic_update = pixpaper_plane_atomic_update,
> +};
> +
> +static const struct drm_crtc_funcs pixpaper_crtc_funcs = {
> + .set_config = drm_atomic_helper_set_config,
> + .page_flip = drm_atomic_helper_page_flip,
> + .reset = drm_atomic_helper_crtc_reset,
> + .destroy = drm_crtc_cleanup,
> + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
> + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
> +};
> +
> +static const struct drm_crtc_helper_funcs pixpaper_crtc_helper_funcs = {
> + .mode_valid = pixpaper_mode_valid,
> + .atomic_check = pixpaper_crtc_helper_atomic_check,
> + .atomic_enable = pixpaper_crtc_atomic_enable,
> + .atomic_disable = pixpaper_crtc_atomic_disable,
> +};
> +
> +static const struct drm_encoder_funcs pixpaper_encoder_funcs = {
> + .destroy = drm_encoder_cleanup,
> +};
> +
> +static const struct drm_connector_funcs pixpaper_connector_funcs = {
> + .reset = drm_atomic_helper_connector_reset,
> + .fill_modes = drm_helper_probe_single_connector_modes,
> + .destroy = drm_connector_cleanup,
> + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};
> +
> +static const struct drm_connector_helper_funcs pixpaper_connector_helper_funcs = {
> + .get_modes = pixpaper_connector_get_modes,
> +};
> +
> +DEFINE_DRM_GEM_FOPS(pixpaper_fops);
> +
> +static struct drm_driver pixpaper_drm_driver = {
> + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
> + .fops = &pixpaper_fops,
> + .name = "pixpaper-426m",
> + .desc = "DRM driver for PIXPAPER 4.26 monochrome e-ink panel",
> + .major = 1,
> + .minor = 0,
> + DRM_GEM_SHMEM_DRIVER_OPS,
> + DRM_FBDEV_SHMEM_DRIVER_OPS,
> +};
> +
> +static const struct drm_mode_config_funcs pixpaper_mode_config_funcs = {
> + .fb_create = drm_gem_fb_create_with_dirty,
> + .atomic_check = drm_atomic_helper_check,
> + .atomic_commit = drm_atomic_helper_commit,
> +};
> +
> +static int pixpaper_probe(struct spi_device *spi)
> +{
> + struct device *dev = &spi->dev;
> + struct pixpaper_panel *panel;
> + struct drm_device *drm;
> + int ret;
> +
> + panel = devm_drm_dev_alloc(dev, &pixpaper_drm_driver,
> + struct pixpaper_panel, drm);
> + if (IS_ERR(panel))
> + return PTR_ERR(panel);
> +
> + drm = &panel->drm;
> + panel->spi = spi;
> + spi_set_drvdata(spi, panel);
> +
> + panel->tx_buf = devm_kzalloc(dev, PIXPAPER_TX_BUF_SIZE, GFP_KERNEL);
> + if (!panel->tx_buf)
> + return -ENOMEM;
> +
> + ret = drmm_mode_config_init(drm);
> + if (ret)
> + return ret;
> +
> + spi->mode = SPI_MODE_0;
> + spi->bits_per_word = PIXPAPER_SPI_BITS_PER_WORD;
> +
> + if (!spi->max_speed_hz) {
> + drm_warn(drm,
> + "spi-max-frequency not specified in DT, using default %u Hz\n",
> + PIXPAPER_SPI_SPEED_DEFAULT);
> + spi->max_speed_hz = PIXPAPER_SPI_SPEED_DEFAULT;
> + }
> +
> + ret = spi_setup(spi);
> + if (ret < 0) {
> + drm_err(drm, "SPI setup failed: %d\n", ret);
> + return ret;
> + }
> +
> + if (!dev->dma_mask)
> + dev->dma_mask = &dev->coherent_dma_mask;
> + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
> + if (ret) {
> + drm_err(drm, "Failed to set DMA mask: %d\n", ret);
> + return ret;
> + }
> +
> + panel->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
> + if (IS_ERR(panel->reset))
> + return PTR_ERR(panel->reset);
> +
> + panel->busy = devm_gpiod_get(dev, "busy", GPIOD_IN);
> + if (IS_ERR(panel->busy))
> + return PTR_ERR(panel->busy);
> +
> + panel->dc = devm_gpiod_get(dev, "dc", GPIOD_OUT_HIGH);
> + if (IS_ERR(panel->dc))
> + return PTR_ERR(panel->dc);
> +
> + ret = pixpaper_panel_hw_init(panel);
> + if (ret) {
> + drm_err(drm, "Panel hardware initialization failed: %d\n", ret);
> + return ret;
> + }
> +
> + drm->mode_config.funcs = &pixpaper_mode_config_funcs;
> + drm->mode_config.min_width = PIXPAPER_WIDTH;
> + drm->mode_config.max_width = PIXPAPER_WIDTH;
> + drm->mode_config.min_height = PIXPAPER_HEIGHT;
> + drm->mode_config.max_height = PIXPAPER_HEIGHT;
> +
> + ret = drm_universal_plane_init(drm, &panel->plane, 1, &pixpaper_plane_funcs,
> + pixpaper_formats, ARRAY_SIZE(pixpaper_formats), NULL,
> + DRM_PLANE_TYPE_PRIMARY, NULL);
> + if (ret)
> + return ret;
> + drm_plane_helper_add(&panel->plane, &pixpaper_plane_helper_funcs);
> +
> + ret = drm_crtc_init_with_planes(drm, &panel->crtc, &panel->plane, NULL,
> + &pixpaper_crtc_funcs, NULL);
> + if (ret)
> + return ret;
> + drm_crtc_helper_add(&panel->crtc, &pixpaper_crtc_helper_funcs);
> +
> + ret = drm_encoder_init(drm, &panel->encoder, &pixpaper_encoder_funcs,
> + DRM_MODE_ENCODER_NONE, NULL);
> + if (ret)
> + return ret;
> +
> + ret = drm_connector_init(drm, &panel->connector,
> + &pixpaper_connector_funcs,
> + DRM_MODE_CONNECTOR_SPI);
> + if (ret)
> + return ret;
> +
> + drm_connector_helper_add(&panel->connector,
> + &pixpaper_connector_helper_funcs);
> + drm_connector_attach_encoder(&panel->connector, &panel->encoder);
> + panel->encoder.possible_crtcs = drm_crtc_mask(&panel->crtc);
> +
> + drm_mode_config_reset(drm);
> +
> + ret = drm_dev_register(drm, 0);
> + if (ret)
> + return ret;
> +
> + drm_client_setup(drm, NULL);
> +
> + return 0;
> +}
> +
> +static void pixpaper_remove(struct spi_device *spi)
> +{
> + struct pixpaper_panel *panel = spi_get_drvdata(spi);
> +
> + if (!panel)
> + return;
> +
> + drm_dev_unplug(&panel->drm);
> + drm_atomic_helper_shutdown(&panel->drm);
> +}
> +
> +static const struct spi_device_id pixpaper_ids[] = { { "pixpaper-426m", 0 }, {} };
> +MODULE_DEVICE_TABLE(spi, pixpaper_ids);
> +
> +static const struct of_device_id pixpaper_dt_ids[] = {
> + { .compatible = "mayqueen,pixpaper-426m" },
> + {}
> +};
> +MODULE_DEVICE_TABLE(of, pixpaper_dt_ids);
> +
> +static struct spi_driver pixpaper_spi_driver = {
> + .driver = {
> + .name = "pixpaper-426m",
> + .of_match_table = pixpaper_dt_ids,
> + },
> + .id_table = pixpaper_ids,
> + .probe = pixpaper_probe,
> + .remove = pixpaper_remove,
> +};
> +
> +module_spi_driver(pixpaper_spi_driver);
> +
> +MODULE_AUTHOR("LiangCheng Wang");
> +MODULE_DESCRIPTION("DRM SPI driver for PIXPAPER 4.26 monochrome e-ink panel");
> +MODULE_LICENSE("GPL");
>
^ permalink raw reply
* Re: [PATCH V12 7/9] iio: imu: inv_icm42607: Add Accelerometer for icm42607
From: Andy Shevchenko @ 2026-06-16 7:07 UTC (permalink / raw)
To: Chris Morgan
Cc: Chris Morgan, linux-iio, andy, nuno.sa, dlechner, jic23,
jean-baptiste.maneyrol, linux-rockchip, devicetree, heiko,
conor+dt, krzk+dt, robh
In-Reply-To: <PH0PR19MB9973380BF26E3EF16BD2D35ED0A5E62@PH0PR19MB997338.namprd19.prod.outlook.com>
On Mon, Jun 15, 2026 at 11:40:31AM -0500, Chris Morgan wrote:
> On Mon, Jun 15, 2026 at 06:07:15PM +0300, Andy Shevchenko wrote:
> > On Mon, Jun 15, 2026 at 09:51:40AM -0500, Chris Morgan wrote:
> > > On Mon, Jun 15, 2026 at 02:21:05PM +0300, Andy Shevchenko wrote:
> > > > On Thu, Jun 11, 2026 at 03:26:04PM -0500, Chris Morgan wrote:
...
> > > > > + for (i = 5; i < ARRAY_SIZE(inv_icm42607_accel_odr); ++i) {
> > > >
> > > > Why pre-increment? Same for all other cases.
> > >
> > > The register starts at 5 and all values below 5 are invalid. Starting
> > > this increment at 5 ensures we don't expose invalid values to
> > > userspace.
> >
> > It doesn't explain pre-increment. Post-increment should work as is.
>
> The array this references starts at 5, because those correspond to the
> values written to the odr register. That said, I do see a bug because
> the odr register is from highest to smallest and this array is
> backwards in the accel and gyro code. I'll fix that.
Good we even managed to find a bug, but what I initially pointed out is simply
as ++i versus i++. When I see ++i, it rises a flag: "this code is special", but
looking closer, I see nothing special about it.
> > > > > + if (i == odr)
> > > > > + break;
> > > > > + }
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [PATCH 6/7] hwmon: adm1275: Support ROHM BD12790
From: sashiko-bot @ 2026-06-16 6:59 UTC (permalink / raw)
To: Matti Vaittinen; +Cc: conor+dt, devicetree, linux-hwmon, robh
In-Reply-To: <8ca875d21f2d9a4d53a87b47a5e6efab48266178.1781591132.git.mazziesaccount@gmail.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] The driver compares the device-returned PMBUS_MFR_MODEL string using strncasecmp without verifying the returned length, leading to uninitialized stack memory reads.
- [High] Multiplication of power and current coefficients by `shunt` overflows a 32-bit unsigned integer for larger shunt resistors.
--
commit 8dff4bf5659b589eabe7fc876665de9339faae1f
Author: Matti Vaittinen <mazziesaccount@gmail.com>
hwmon: adm1275: Support ROHM BD12790
Add support for ROHM BD12790 hot-swap controller which is largely
similar to Analog Devices adm1272.
> diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c
> --- a/drivers/hwmon/pmbus/adm1275.c
> +++ b/drivers/hwmon/pmbus/adm1275.c
[ ... ]
> @@ -581,7 +606,8 @@ static int adm1275_probe(struct i2c_client *client)
> if (mid->driver_data == adm1272 || mid->driver_data == adm1273 ||
> mid->driver_data == adm1278 || mid->driver_data == adm1281 ||
> mid->driver_data == adm1293 || mid->driver_data == adm1294 ||
> - mid->driver_data == bd12780 || mid->driver_data == sq24905c)
> + mid->driver_data == bd12780 || mid->driver_data == bd12790 ||
> + mid->driver_data == sq24905c)
> config_read_fn = i2c_smbus_read_word_data;
> else
[Severity: High]
This is a pre-existing issue, but I noticed that just before this block in
adm1275_probe(), there might be an out-of-bounds stack read:
ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, block_buffer);
if (ret < 0) { ... }
for (mid = adm1275_id; mid->name[0]; mid++) {
if (!strncasecmp(mid->name, block_buffer, strlen(mid->name)))
break;
}
Does this code read uninitialized stack memory if the device returns a
string that matches a valid model name prefix, but is shorter than the valid
model name length?
Since i2c_smbus_read_block_data() reads up to 32 bytes into the uninitialized
stack array block_buffer without appending a null terminator, strncasecmp()
could read past the valid bytes returned in ret.
For example, if the device returns a shorter string like "adm12", checking
it against "adm1275" up to the length of "adm1275" will continue reading
into uninitialized stack bounds.
[ ... ]
> @@ -685,12 +722,23 @@ static int adm1275_probe(struct i2c_client *client)
> break;
> case adm1272:
> case adm1273:
> + case bd12790:
> + {
[ ... ]
[Severity: High]
This isn't a bug introduced by this patch, but looking further down in
adm1275_probe(), could the coefficient calculations for current and power
overflow a 32-bit unsigned integer?
info->m[PSC_CURRENT_OUT] =
coefficients[cindex].m * shunt / 1000;
info->m[PSC_POWER] =
coefficients[pindex].m * shunt / 1000;
If a larger shunt resistor value is provided via device tree
(e.g., 200,000 micro-ohms to measure lower currents), multiplying this by
larger coefficients like 30631 (used for ADM1293) evaluates to 6,126,200,000.
Since shunt is a u32 and the coefficient promotes to int, this operation uses
32-bit unsigned arithmetic.
Does this wrap-around the maximum 32-bit value of 4,294,967,295 and result
in drastically incorrect telemetry scaling factors?
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1781591132.git.mazziesaccount@gmail.com?part=6
^ permalink raw reply
* Re: [PATCH 7/7] hwmon: adm1275: Support module auto-loading
From: sashiko-bot @ 2026-06-16 6:59 UTC (permalink / raw)
To: Matti Vaittinen; +Cc: robh, linux-hwmon, conor+dt, devicetree
In-Reply-To: <634e76680ed93e58ebeb35db080138b791cb6c27.1781591132.git.mazziesaccount@gmail.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [Low] The commit message incorrectly references `spi_device_id` instead of `i2c_device_id`.
Pre-existing issues:
- [High] The newly added `adm1275_of_match` table is incomplete, leaving module auto-loading broken for the majority of supported devices.
--
commit ed700404448bb29c6f451edcac6e84d5a0f95644
Author: Matti Vaittinen <mazziesaccount@gmail.com>
hwmon: adm1275: Support module auto-loading
This commit adds an of_device_id table to the adm1275 driver to enable
automatic module loading when devices are parsed from the device tree
at boot.
> hwmon: adm1275: Support module auto-loading
>
> Populating the spi_device_id -table is not enough to make the
[Severity: Low]
This isn't a bug, but there's a typo in the commit message. The driver
is an I2C driver and uses an i2c_device_id table, not an spi_device_id
table.
[ ... ]
> diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c
> index 9e21dd4083e94..c27bb0e493547 100644
> --- a/drivers/hwmon/pmbus/adm1275.c
> +++ b/drivers/hwmon/pmbus/adm1275.c
> @@ -927,9 +927,17 @@ static int adm1275_probe(struct i2c_client *client)
> return pmbus_do_probe(client, info);
> }
>
> +static const struct of_device_id adm1275_of_match[] = {
> + { .compatible = "rohm,bd12780", },
> + { .compatible = "rohm,bd12790", },
[Severity: High]
This is a pre-existing issue, but the newly added adm1275_of_match
table appears to be incomplete.
Since udev relies on the of_device_id table to export the correct
MODALIAS for auto-loading, wouldn't omitting the other devices
supported by this driver (such as adm1075, adm1275, adm1276) leave
auto-loading broken for those devices on DT systems?
Should we include compatible strings for the rest of the supported
hardware here to ensure they also benefit from module auto-loading?
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, adm1275_of_match);
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1781591132.git.mazziesaccount@gmail.com?part=7
^ permalink raw reply
* Re: [PATCH 1/7] dt-bindings: adm1275: ROHM BD12780 hot-swap controller
From: sashiko-bot @ 2026-06-16 6:55 UTC (permalink / raw)
To: Matti Vaittinen; +Cc: robh, linux-hwmon, conor+dt, devicetree
In-Reply-To: <d63c4df5e9df845bc4f94b4abdcd068a23929974.1781591132.git.mazziesaccount@gmail.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Invalid use of the `items` object for the single-string compatible enum, allowing arrays of multiple arbitrary compatible strings.
--
commit 7132123aae357451c411a0cee036262cc9f62a45
Author: Matti Vaittinen <mazziesaccount@gmail.com>
dt-bindings: adm1275: ROHM BD12780 hot-swap controller
This commit adds support for the ROHM BD12780 and BD12780A hot-swap
controllers to the adm1275 device tree bindings. It introduces new
compatible strings, requiring BD12780A to use BD12780 as a fallback.
> diff --git a/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml b/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml
> index d6a7517f2a50c..bc67510ef3ab1 100644
> --- a/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml
> +++ b/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml
[ ... ]
> @@ -25,19 +25,35 @@ description: |
> properties:
> compatible:
> - enum:
> - - adi,adm1075
> - - adi,adm1272
> - - adi,adm1273
> - - adi,adm1275
> - - adi,adm1276
> - - adi,adm1278
> - - adi,adm1281
> - - adi,adm1293
> - - adi,adm1294
> - - silergy,mc09c
> + oneOf:
> + - items:
> + enum:
> + - adi,adm1075
> + - adi,adm1272
> + - adi,adm1273
[Severity: Low]
Does wrapping this enum inside an items list inadvertently allow multiple
exclusive strings instead of restricting it to exactly one?
In dt-schema, applying an items object directly to a string-array property
like compatible defines the schema for all elements of the array, but does
not impose bounds on the array length. This might allow a device tree to
define a compatible property with multiple strings from this list, such as:
compatible = "adi,adm1075", "adi,adm1272";
To enforce exactly one string from the list, should the items wrapper be
removed so that the enum resides directly under oneOf?
> + - adi,adm1275
> + - adi,adm1276
> + - adi,adm1278
> + - adi,adm1281
> + - adi,adm1293
> + - adi,adm1294
> + - rohm,bd12780
> + - silergy,mc09c
> +
> +# Require BD12780 as a fall-back for BD12780A.
> + - items:
> + - const: rohm,bd12780a
> + - const: rohm,bd12780
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1781591132.git.mazziesaccount@gmail.com?part=1
^ permalink raw reply
* Re: [PATCH 5/7] doc: adm1275: Add ROHM BD12790
From: sashiko-bot @ 2026-06-16 6:54 UTC (permalink / raw)
To: Matti Vaittinen; +Cc: robh, conor+dt, devicetree, linux-hwmon
In-Reply-To: <7bfa22c245b81c8027736019aff717cc1f5039c8.1781591132.git.mazziesaccount@gmail.com>
> From: Matti Vaittinen <mazziesaccount@gmail.com>
>
> Add the ROHM BD12790 to the list of the ICs supported by the adm1275
> driver.
>
> Signed-off-by: Matti Vaittinen <mazziesaccount@gmail.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1781591132.git.mazziesaccount@gmail.com?part=5
^ permalink raw reply
* Re: [PATCH 3/7] hwmon: adm1275: Support ROHM BD12780
From: sashiko-bot @ 2026-06-16 6:54 UTC (permalink / raw)
To: Matti Vaittinen; +Cc: conor+dt, robh, linux-hwmon, devicetree
In-Reply-To: <c92f1356fbf967dee3130f2eb0da08eb84800d47.1781591132.git.mazziesaccount@gmail.com>
> From: Matti Vaittinen <mazziesaccount@gmail.com>
>
> ROHM BD12780 and BD12780A are hot-swap controllers. They are largely
> similar to Analog Devices ADM1278. Besides the ID registers and some
> added functionality, the BD12780 and BD12780A mark PMON_CONFIG bits
> [15:14] as reserved. Hence TSFILT setting must be omitted on these ICs.
>
> The BD12780 has 3 pins usable for configuring the I2C address. The
> BD12780A lists the ADDR3-pin as "not connect".
>
> Support ROHM BD12780 and BD12780A controllers.
>
> Signed-off-by: Matti Vaittinen <mazziesaccount@gmail.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1781591132.git.mazziesaccount@gmail.com?part=3
^ permalink raw reply
* Re: [PATCH 4/7] dt-bindings: adm1275: ROHM BD12790 hot-swap controller
From: sashiko-bot @ 2026-06-16 6:53 UTC (permalink / raw)
To: Matti Vaittinen; +Cc: robh, devicetree, conor+dt, linux-hwmon
In-Reply-To: <ae80a037fa3b03ef5cfb446bf3e9c44efd4f04ec.1781591132.git.mazziesaccount@gmail.com>
> From: Matti Vaittinen <mazziesaccount@gmail.com>
>
> Support ROHM BD12790 hot-swap controller which is largely compatible
> with the Analog Devices adm1272.
>
> Signed-off-by: Matti Vaittinen <mazziesaccount@gmail.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1781591132.git.mazziesaccount@gmail.com?part=4
^ permalink raw reply
* Re: [PATCH 2/7] doc: Add ROHM BD12780 and BD12780A
From: sashiko-bot @ 2026-06-16 6:52 UTC (permalink / raw)
To: Matti Vaittinen; +Cc: robh, linux-hwmon, conor+dt, devicetree
In-Reply-To: <be64833b78ab8f915fce6209cfab0a458991033b.1781591132.git.mazziesaccount@gmail.com>
> From: Matti Vaittinen <mazziesaccount@gmail.com>
>
> Add the ROHM BD12780 and the BD12780A to the list of the ICs supported by
> the adm1275 driver.
>
> Signed-off-by: Matti Vaittinen <mazziesaccount@gmail.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1781591132.git.mazziesaccount@gmail.com?part=2
^ permalink raw reply
* [PATCH 7/7] hwmon: adm1275: Support module auto-loading
From: Matti Vaittinen @ 2026-06-16 6:47 UTC (permalink / raw)
To: Matti Vaittinen, Matti Vaittinen, Matti Vaittinen
Cc: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jonathan Corbet, Shuah Khan, Wensheng Wang, Ashish Yadav,
Matti Vaittinen, Kim Seer Paller, Cedric Encarnacion,
Chris Packham, Yuxi Wang, Charles Hsu, ChiShih Tsai, linux-hwmon,
devicetree, linux-kernel, linux-doc
In-Reply-To: <cover.1781591132.git.mazziesaccount@gmail.com>
[-- Attachment #1: Type: text/plain, Size: 1844 bytes --]
From: Matti Vaittinen <mazziesaccount@gmail.com>
Populating the spi_device_id -table is not enough to make the
driver module automatically load when device-tree node for the bd12780
is parsed at boot.
Adding the of_device_id tables causes the driver module to be
automatically load at boot. Testing has been done with rather old Debian
system.
When inspecting the generated module-aliases with the insmod, following
entries seem to be the difference:
alias: of:N*T*Crohm,bd12780C*
alias: of:N*T*Crohm,bd12780
I suspect these are required for the module loading to work.
Signed-off-by: Matti Vaittinen <mazziesaccount@gmail.com>
---
I did not add of_device_ids for other supported ICs as I can't verify it
doesn't cause side-effects. Please let me know if you think those IDs
should be added as well. I would be glad if I got more educated opinion
on adding the of-IDs :) (I can squash this to 3/7 and 6/7 in next
revision, and add own patch for adding of-IDs for other ICs if
required).
---
drivers/hwmon/pmbus/adm1275.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c
index 9e21dd4083e9..c27bb0e49354 100644
--- a/drivers/hwmon/pmbus/adm1275.c
+++ b/drivers/hwmon/pmbus/adm1275.c
@@ -927,9 +927,17 @@ static int adm1275_probe(struct i2c_client *client)
return pmbus_do_probe(client, info);
}
+static const struct of_device_id adm1275_of_match[] = {
+ { .compatible = "rohm,bd12780", },
+ { .compatible = "rohm,bd12790", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adm1275_of_match);
+
static struct i2c_driver adm1275_driver = {
.driver = {
.name = "adm1275",
+ .of_match_table = adm1275_of_match,
},
.probe = adm1275_probe,
.id_table = adm1275_id,
--
2.54.0
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply related
* Re: [PATCH v5 2/2] Input: isa1200 - new driver for Imagis ISA1200
From: Svyatoslav Ryhel @ 2026-06-16 6:45 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: linux-input, devicetree, linux-kernel, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Linus Walleij
In-Reply-To: <ajDEsU8oZWT7KB9d@google.com>
вт, 16 черв. 2026 р. о 07:16 Dmitry Torokhov <dmitry.torokhov@gmail.com> пише:
>
> Hi Svyatoslav,
>
> On Mon, Jun 15, 2026 at 09:19:27AM +0300, Svyatoslav Ryhel wrote:
> > чт, 28 трав. 2026 р. о 08:38 Svyatoslav Ryhel <clamor95@gmail.com> пише:
> > >
> > > вт, 12 трав. 2026 р. о 13:24 Svyatoslav Ryhel <clamor95@gmail.com> пише:
> > > >
> > > > From: Linus Walleij <linusw@kernel.org>
> > > >
> > > > The ISA1200 is a haptic feedback unit from Imagis Technology using two
> > > > motors for haptic feedback in mobile phones. Used in many mobile devices
> > > > c. 2012 including Samsung Galxy S Advance GT-I9070 (Janice), Samsung Beam
> > > > GT-I8350 (Gavini), LG Optimus 4X P880 and LG Optimus Vu P895.
> > > >
> > > > The exact datasheet for the ISA1200 is not available; all data was modeled
> > > > based on available downstream kernel sources for various devices and
> > > > fragments of information scattered across the internet.
> > > >
> > > > Tested-by: Linus Walleij <linusw@kernel.org> # GT-I9070 Janice
> > > > Signed-off-by: Linus Walleij <linusw@kernel.org>
> > > > Co-developed-by: Svyatoslav Ryhel <clamor95@gmail.com>
> > > > Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
> > > > ---
> > > > drivers/input/misc/Kconfig | 12 +
> > > > drivers/input/misc/Makefile | 1 +
> > > > drivers/input/misc/isa1200.c | 524 +++++++++++++++++++++++++++++++++++
> > > > 3 files changed, 537 insertions(+)
> > > > create mode 100644 drivers/input/misc/isa1200.c
> > > >
> > >
> > > Hello Dmitry! Do I need to make any further adjustments to this driver?
> >
> > Hello Dmitry! Do I need to make any further adjustments to this
> > driver? This driver is hanging in LKML for some time already without
> > responds from input maintainer. It is still relevant and I would like
> > it to move forward.
>
> There were valid sashiko comments on the patch regarding resetting
> "level" to 0 and also potential racing conditions, as well as suggestion
> to check number of gpios specified in the device tree.
>
> Please see if the following works for you:
>
> diff --git a/drivers/input/misc/isa1200.c b/drivers/input/misc/isa1200.c
> index ff82252a08e1..c61adc4b605c 100644
> --- a/drivers/input/misc/isa1200.c
> +++ b/drivers/input/misc/isa1200.c
> @@ -131,6 +131,7 @@ struct isa1200 {
> struct work_struct play_work;
> struct isa1200_config config;
>
> + bool suspended;
> bool active;
> int level;
> };
> @@ -247,17 +248,21 @@ static void isa1200_stop(struct isa1200 *isa)
> isa->supplies);
>
> isa->active = false;
> - isa->level = 0;
> }
>
> static void isa1200_play_work(struct work_struct *work)
> {
> struct isa1200 *isa = container_of(work, struct isa1200, play_work);
> -
> - if (isa->level)
> - isa1200_start(isa);
> - else
> - isa1200_stop(isa);
> + struct input_dev *input = isa->input;
> +
> + scoped_guard(mutex_try, &input->mutex) {
> + if (!isa->suspended) {
> + if (isa->level)
> + isa1200_start(isa);
> + else
> + isa1200_stop(isa);
> + }
> + }
> }
>
> static int isa1200_vibrator_play_effect(struct input_dev *input, void *data,
> @@ -280,7 +285,8 @@ static int isa1200_vibrator_play_effect(struct input_dev *input, void *data,
>
> if (isa->level != level) {
> isa->level = level;
> - schedule_work(&isa->play_work);
> + if (!READ_ONCE(isa->suspended))
> + schedule_work(&isa->play_work);
> }
>
> return 0;
> @@ -292,6 +298,7 @@ static void isa1200_vibrator_close(struct input_dev *input)
>
> cancel_work_sync(&isa->play_work);
> isa1200_stop(isa);
> + isa->level = 0;
> }
>
> static int isa1200_of_probe(struct i2c_client *client)
> @@ -331,6 +338,9 @@ static int isa1200_of_probe(struct i2c_client *client)
> return dev_err_probe(dev, PTR_ERR(isa->enable_gpios),
> "failed to get enable gpios\n");
>
> + if (isa->enable_gpios && isa->enable_gpios->ndescs > ISA1200_EN_PINS_MAX)
> + return dev_err_probe(dev, -EINVAL, "too many enable gpios\n");
> +
> ldo_node = device_get_named_child_node(dev, "ldo");
> if (!ldo_node)
> return dev_err_probe(dev, -ENODEV,
> @@ -479,9 +489,9 @@ static int isa1200_suspend(struct device *dev)
> guard(mutex)(&isa->input->mutex);
>
> if (input_device_enabled(isa->input)) {
> + WRITE_ONCE(isa->suspended, true);
> cancel_work_sync(&isa->play_work);
> - if (isa->level)
> - isa1200_stop(isa);
> + isa1200_stop(isa);
> }
>
> return 0;
> @@ -493,9 +503,11 @@ static int isa1200_resume(struct device *dev)
>
> guard(mutex)(&isa->input->mutex);
>
> - if (input_device_enabled(isa->input))
> + if (input_device_enabled(isa->input)) {
> + WRITE_ONCE(isa->suspended, false);
> if (isa->level)
> - isa1200_start(isa);
> + schedule_work(&isa->play_work);
> + }
>
> return 0;
> }
>
> --
> Dmitry
I have tested your code on my P895 and it works perfectly fine. Should
I resend with these changes or you can integrate them while picking
patchset?
Thank you for your suggestions and efforts!
Best regards,
Svyatoslav R.
^ permalink raw reply
* [PATCH 6/7] hwmon: adm1275: Support ROHM BD12790
From: Matti Vaittinen @ 2026-06-16 6:44 UTC (permalink / raw)
To: Matti Vaittinen, Matti Vaittinen, Matti Vaittinen
Cc: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jonathan Corbet, Shuah Khan, Wensheng Wang, Ashish Yadav,
Matti Vaittinen, Kim Seer Paller, Cedric Encarnacion,
Chris Packham, Yuxi Wang, Charles Hsu, ChiShih Tsai, linux-hwmon,
devicetree, linux-kernel, linux-doc
In-Reply-To: <cover.1781591132.git.mazziesaccount@gmail.com>
[-- Attachment #1: Type: text/plain, Size: 6767 bytes --]
From: Matti Vaittinen <mazziesaccount@gmail.com>
Add support for ROHM BD12790 hot-swap controller which is largely
similar to Analog Devices adm1272.
The BD12790 uses the same selectable 60V/100V voltage ranges and
15mV/30mV current-sense ranges as the ADM1272, and the same VRANGE
(bit 5) and IRANGE (bit 0) layout in PMON_CONFIG. It therefore uses
a dedicated coefficient table that mirrors adm1272_coefficients, with
the following differences derived from BD12790 datasheet Table 1 (p.18):
- power 60V/30mV: m=17560 (vs. 17561)
- power 100V/30mV: m=10536 (vs. 10535)
- temperature: b=31880 (vs. 31871, reflecting T[11:0] = 4.2*T + 3188)
Signed-off-by: Matti Vaittinen <mazziesaccount@gmail.com>
Assisted-by: GitHub Copilot:claude-sonnet-4.6
---
Originally this patch was AI-generated. I did pretty much re-write the
probe changes by hand, and also fixed some of the coefficient math
afterwards :/ But yeah, this one was AI "assisted". :)
drivers/hwmon/pmbus/Kconfig | 4 +--
drivers/hwmon/pmbus/adm1275.c | 53 +++++++++++++++++++++++++++++------
2 files changed, 47 insertions(+), 10 deletions(-)
diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig
index b3c27f3b2712..6ebc01e26db3 100644
--- a/drivers/hwmon/pmbus/Kconfig
+++ b/drivers/hwmon/pmbus/Kconfig
@@ -52,8 +52,8 @@ config SENSORS_ADM1275
help
If you say yes here you get hardware monitoring support for Analog
Devices ADM1075, ADM1272, ADM1273, ADM1275, ADM1276, ADM1278, ADM1281,
- ADM1293, ADM1294, ROHM BD12780, and SQ24905C Hot-Swap Controller and
- Digital Power Monitors.
+ ADM1293, ADM1294, ROHM BD12780, ROHM BD12790, and SQ24905C
+ Hot-Swap Controller and Digital Power Monitors.
This driver can also be built as a module. If so, the module will
be called adm1275.
diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c
index 838b8827eb76..9e21dd4083e9 100644
--- a/drivers/hwmon/pmbus/adm1275.c
+++ b/drivers/hwmon/pmbus/adm1275.c
@@ -19,7 +19,7 @@
#include "pmbus.h"
enum chips { adm1075, adm1272, adm1273, adm1275, adm1276, adm1278, adm1281,
- adm1293, adm1294, bd12780, sq24905c };
+ adm1293, adm1294, bd12780, bd12790, sq24905c };
#define ADM1275_MFR_STATUS_IOUT_WARN2 BIT(0)
#define ADM1293_MFR_STATUS_VAUX_UV_WARN BIT(5)
@@ -47,8 +47,8 @@ enum chips { adm1075, adm1272, adm1273, adm1275, adm1276, adm1278, adm1281,
#define ADM1278_VOUT_EN BIT(1)
#define ADM1278_PMON_DEFCONFIG (ADM1278_VOUT_EN | ADM1278_TEMP1_EN | ADM1278_TSFILT)
-/* The BD12780 data sheets mark TSFILT bit as reserved. */
-#define BD12780_PMON_DEFCONFIG (ADM1278_VOUT_EN | ADM1278_TEMP1_EN)
+/* The BD127x0 data sheets mark TSFILT bit as reserved. */
+#define BD127X0_PMON_DEFCONFIG (ADM1278_VOUT_EN | ADM1278_TEMP1_EN)
#define ADM1293_IRANGE_25 0
#define ADM1293_IRANGE_50 BIT(6)
@@ -136,6 +136,30 @@ static const struct coefficients adm1272_coefficients[] = {
};
+/*
+ * BD12790 coefficients derived from preliminary datasheet, Table 1 (p.18)
+ * and the PMBus direct-format relationship X = (Y * 10^(-R) - b) / m.
+ *
+ * Voltage: V[V] = 14.77e-3 * code (60V) / 24.62e-3 * code (100V)
+ * -> m = 6770, R=-2 / m = 4062, R=-2
+ * Current: code = I[A] * RS * 132802.1 + 2048 (15mV) / * 66401.06 + 2048 (30mV)
+ * -> m = 1328, b = 2048 * 10^(-R) = 20480, R=-1 / m = 664, same b and R
+ * Power: code = k * RS * PIN, k = 35119.94 / 17559.97 / 21071.44 / 10535.72
+ * -> m = round(k / 10^(-R)), R=-2 for 60V/15mV, R=-3 for the other three
+ * Temperature: code = 4.2 * T + 3188 -> m = 42, b = 3188 * 10 = 31880, R=-1
+ */
+static const struct coefficients bd12790_coefficients[] = {
+ [0] = { 6770, 0, -2 }, /* voltage, vrange 60V */
+ [1] = { 4062, 0, -2 }, /* voltage, vrange 100V */
+ [2] = { 1328, 20480, -1 }, /* current, vsense range 15mV */
+ [3] = { 664, 20480, -1 }, /* current, vsense range 30mV */
+ [4] = { 3512, 0, -2 }, /* power, vrange 60V, irange 15mV */
+ [5] = { 21071, 0, -3 }, /* power, vrange 100V, irange 15mV */
+ [6] = { 17560, 0, -3 }, /* power, vrange 60V, irange 30mV */
+ [7] = { 10536, 0, -3 }, /* power, vrange 100V, irange 30mV */
+ [8] = { 42, 31880, -1 }, /* temperature */
+};
+
static const struct coefficients adm1275_coefficients[] = {
[0] = { 19199, 0, -2 }, /* voltage, vrange set */
[1] = { 6720, 0, -1 }, /* voltage, vrange not set */
@@ -504,6 +528,7 @@ static const struct i2c_device_id adm1275_id[] = {
*/
{ "bd12780", bd12780 },
{ "bd12780a", /* driver data unused, see --^ */ },
+ { "bd12790", bd12790 },
{ "mc09c", sq24905c },
{ }
};
@@ -581,7 +606,8 @@ static int adm1275_probe(struct i2c_client *client)
if (mid->driver_data == adm1272 || mid->driver_data == adm1273 ||
mid->driver_data == adm1278 || mid->driver_data == adm1281 ||
mid->driver_data == adm1293 || mid->driver_data == adm1294 ||
- mid->driver_data == bd12780 || mid->driver_data == sq24905c)
+ mid->driver_data == bd12780 || mid->driver_data == bd12790 ||
+ mid->driver_data == sq24905c)
config_read_fn = i2c_smbus_read_word_data;
else
config_read_fn = i2c_smbus_read_byte_data;
@@ -655,12 +681,23 @@ static int adm1275_probe(struct i2c_client *client)
break;
case adm1272:
case adm1273:
+ case bd12790:
+ {
+ u16 defconfig;
+
data->have_vout = true;
data->have_pin_max = true;
data->have_temp_max = true;
data->have_power_sampling = true;
- coefficients = adm1272_coefficients;
+ if (data->id == bd12790) {
+ coefficients = bd12790_coefficients;
+ defconfig = BD127X0_PMON_DEFCONFIG;
+ } else {
+ coefficients = adm1272_coefficients;
+ defconfig = ADM1278_PMON_DEFCONFIG;
+ }
+
vindex = (config & ADM1275_VRANGE) ? 1 : 0;
cindex = (config & ADM1272_IRANGE) ? 3 : 2;
/* pindex depends on the combination of the above */
@@ -685,14 +722,14 @@ static int adm1275_probe(struct i2c_client *client)
PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP;
- ret = adm1275_enable_vout_temp(data, client, config,
- ADM1278_PMON_DEFCONFIG);
+ ret = adm1275_enable_vout_temp(data, client, config, defconfig);
if (ret)
return ret;
if (config & ADM1278_VIN_EN)
info->func[0] |= PMBUS_HAVE_VIN;
break;
+ }
case adm1275:
if (device_config & ADM1275_IOUT_WARN2_SELECT)
data->have_oc_fault = true;
@@ -738,7 +775,7 @@ static int adm1275_probe(struct i2c_client *client)
u16 defconfig;
if (data->id == bd12780)
- defconfig = BD12780_PMON_DEFCONFIG;
+ defconfig = BD127X0_PMON_DEFCONFIG;
else
defconfig = ADM1278_PMON_DEFCONFIG;
--
2.54.0
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply related
* [PATCH 5/7] doc: adm1275: Add ROHM BD12790
From: Matti Vaittinen @ 2026-06-16 6:38 UTC (permalink / raw)
To: Matti Vaittinen, Matti Vaittinen, Matti Vaittinen
Cc: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jonathan Corbet, Shuah Khan, Wensheng Wang, Ashish Yadav,
Matti Vaittinen, Kim Seer Paller, Cedric Encarnacion,
Chris Packham, Yuxi Wang, Charles Hsu, ChiShih Tsai, linux-hwmon,
devicetree, linux-kernel, linux-doc
In-Reply-To: <cover.1781591132.git.mazziesaccount@gmail.com>
[-- Attachment #1: Type: text/plain, Size: 896 bytes --]
From: Matti Vaittinen <mazziesaccount@gmail.com>
Add the ROHM BD12790 to the list of the ICs supported by the adm1275
driver.
Signed-off-by: Matti Vaittinen <mazziesaccount@gmail.com>
---
I didn't find public data-sheet yet. I will add a link when one is
available.
Documentation/hwmon/adm1275.rst | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/Documentation/hwmon/adm1275.rst b/Documentation/hwmon/adm1275.rst
index 8a793dd2b412..d8495be313b8 100644
--- a/Documentation/hwmon/adm1275.rst
+++ b/Documentation/hwmon/adm1275.rst
@@ -83,6 +83,14 @@ Supported chips:
Datasheet: https://fscdn.rohm.com/en/products/databook/datasheet/ic/power/power_switch/bd12780amuv-lb-e.pdf
+ * ROHM Semiconductor BD12790
+
+ Prefix: 'bd12790'
+
+ Addresses scanned: -
+
+ Datasheet: -
+
* Silergy SQ24905C
Prefix: 'mc09c'
--
2.54.0
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ 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