* [PATCH] dt-bindings: mfd: khadas,mcu: Drop type reference from "fan-supply"
From: Rob Herring (Arm) @ 2026-06-24 13:36 UTC (permalink / raw)
To: Neil Armstrong, Lee Jones, Krzysztof Kozlowski, Conor Dooley,
Ronald Claveau
Cc: Conor Dooley, linux-amlogic, devicetree, linux-kernel
"fan-supply" already has a type and shouldn't have a type $ref. Drop the
$ref to fix the warning.
Fixes: 39dd85d9246e ("dt-bindings: mfd: khadas: Add new compatible for Khadas VIM4 MCU")
Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
---
Applying this to my tree and sending to Linus for rc1.
---
Documentation/devicetree/bindings/mfd/khadas,mcu.yaml | 1 -
1 file changed, 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/mfd/khadas,mcu.yaml b/Documentation/devicetree/bindings/mfd/khadas,mcu.yaml
index 1f135618e3b6..c6f91e7bc8aa 100644
--- a/Documentation/devicetree/bindings/mfd/khadas,mcu.yaml
+++ b/Documentation/devicetree/bindings/mfd/khadas,mcu.yaml
@@ -28,7 +28,6 @@ properties:
fan-supply:
description: Phandle to the regulator that powers the fan.
- $ref: /schemas/types.yaml#/definitions/phandle
required:
- compatible
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v4 0/4] arm64: dts: qcom: Add IMDT QCS8550 SBC
From: William Bright @ 2026-06-24 13:34 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
Konrad Dybcio
Cc: devicetree, linux-kernel, linux-arm-msm
In-Reply-To: <20260610-imdt-qcs8550-sbc-rfc-v4-0-358e71d606bc@imd-tec.com>
Hi all,
Another gentle ping on this patch series.
Many thanks,
Will
^ permalink raw reply
* Re: [PATCH 2/8] arm64: dts: qcom: sm8450: Remove unneeded reserved memory nodes
From: Konrad Dybcio @ 2026-06-24 13:28 UTC (permalink / raw)
To: Esteban Urrutia, Bjorn Andersson, Michael Turquette, Stephen Boyd,
Brian Masney, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Rob Clark, Will Deacon, Robin Murphy,
Joerg Roedel (AMD), Vinod Koul, Neil Armstrong
Cc: linux-arm-msm, linux-clk, linux-kernel, devicetree, iommu,
linux-arm-kernel, linux-phy
In-Reply-To: <b3541802-3035-40ee-8327-a65bd5d2dfee@proton.me>
On 6/24/26 3:26 PM, Esteban Urrutia wrote:
>
>
> On 6/23/26 7:03 AM, Konrad Dybcio wrote:
>>> This is mentioned in the memory map description, but is not part
>>> of it.
>>>
>>> I booted up a 8450 HDK and it doesn't even have MTE, so it's
>>> probably valid
>>
>> i.e. it doesn't report MTE to Linux. I don't know if it's Gunyah
>> trapping it.
> Then, should device trees delete these memory regions on a case-by-case
> basis, or be left as is?
I'd delete it and reintroduce as necessary
Konrad
^ permalink raw reply
* Re: [PATCH v2 2/2] arm64: dts: qcom: kaanapali: fix traceNoC probe issue
From: Konrad Dybcio @ 2026-06-24 13:27 UTC (permalink / raw)
To: Jie Gan, Bjorn Andersson, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Tingwei Zhang, Jingyi Wang,
Abel Vesa, Suzuki K Poulose, Mike Leach, James Clark, Leo Yan,
Yuanfang Zhang
Cc: linux-arm-msm, devicetree, linux-kernel, coresight,
linux-arm-kernel
In-Reply-To: <20260624-fix-tracenoc-probe-issue-v2-2-786520f62f21@oss.qualcomm.com>
On 6/24/26 11:49 AM, Jie Gan wrote:
> The AMBA bus attempts to read the CID/PID of a device before invoking
> its probe function if the arm,primecell-periphid property is absent.
> This causes a deferred probe issue for the TraceNoC device, as the
> CID/PID cannot be read from the periphid register.
Why does it probe defer?
And is this required for all TNOC devices?
Konrad
^ permalink raw reply
* Re: [PATCH 2/8] arm64: dts: qcom: sm8450: Remove unneeded reserved memory nodes
From: Esteban Urrutia @ 2026-06-24 13:26 UTC (permalink / raw)
To: Konrad Dybcio, Bjorn Andersson, Michael Turquette, Stephen Boyd,
Brian Masney, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Rob Clark, Will Deacon, Robin Murphy,
Joerg Roedel (AMD), Vinod Koul, Neil Armstrong
Cc: linux-arm-msm, linux-clk, linux-kernel, devicetree, iommu,
linux-arm-kernel, linux-phy
In-Reply-To: <6123a923-21dd-4f69-9ac5-02165963027c@oss.qualcomm.com>
On 6/23/26 7:03 AM, Konrad Dybcio wrote:
>> This is mentioned in the memory map description, but is not part
>> of it.
>>
>> I booted up a 8450 HDK and it doesn't even have MTE, so it's
>> probably valid
>
> i.e. it doesn't report MTE to Linux. I don't know if it's Gunyah
> trapping it.
Then, should device trees delete these memory regions on a case-by-case
basis, or be left as is?
Regards,
Esteban
^ permalink raw reply
* Re: [PATCH v11] PCI: Add support for PCIe WAKE# interrupt
From: Krishna Chaitanya Chundru @ 2026-06-24 13:25 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-pci, devicetree, robh, conor+dt
In-Reply-To: <20260624114008.7F7A51F00A3A@smtp.kernel.org>
On 6/24/2026 5:10 PM, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
> - [High] Hot-removing an endpoint sharing a WAKE# GPIO breaks wakeup for all remaining endpoints on that line.
> - [High] pci_remove_of_wake_gpio() unconditionally clears the device's wakeup configuration and wake IRQ, even if it never acquired the WAKE# GPIO.
> - [High] WAKE# GPIO configuration forces device wakeup to be enabled by default, violating PCI PM policy.
> - [Medium] Calling platform_pci_configure_wake() after device_add() creates a race condition that overwrites driver or userspace wakeup configuration.
> - [Medium] Hardcoded IRQ_TYPE_LEVEL_LOW ignores Device Tree GPIO polarity for the WAKE# interrupt.
> - [Medium] WAKE# GPIO configuration permanently fails if the GPIO provider returns -EPROBE_DEFER.
> --
>
> commit 9e1cd6307066585f1f51b9e7de95ae7950936ba0
> Author: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
> PCI: Add support for PCIe WAKE# interrupt
>
> According to the PCI Express specification (PCIe r7.0, Section 5.3.3.2),
> two link wakeup mechanisms are defined: Beacon and WAKE#. This change adds
> support for the WAKE# mechanism in the PCI core.
>
> Link: https://lore.kernel.org/all/20250515090517.3506772-1-krishna.chundru@oss.qualcomm.com/
> Link: https://github.com/devicetree-org/dt-schema/pull/170
>
>> diff --git a/drivers/pci/of.c b/drivers/pci/of.c
>> index 8b18c4ba845cf..0f5effe1d7026 100644
>> --- a/drivers/pci/of.c
>> +++ b/drivers/pci/of.c
> [ ... ]
>
>> + ret = dev_pm_set_dedicated_wake_irq(&pdev->dev, wake_irq);
>> + if (ret < 0) {
>> + pci_err(pdev, "Failed to set WAKE# IRQ: %d\n", ret);
>> + return;
>> + }
>> +
>> + ret = irq_set_irq_type(wake_irq, IRQ_TYPE_LEVEL_LOW);
> [Severity: Medium]
> Does hardcoding IRQ_TYPE_LEVEL_LOW here override the logical polarity
> specified in the Device Tree?
>
> If the WAKE# GPIO is specified via the wake-gpios property with flags like
> GPIO_ACTIVE_HIGH, this explicit type setting might ignore those flags and
> fail to trigger on platforms with an inverted physical signal.
As per PCIe spec WAKE# is active low signal.
>
>> + if (ret < 0) {
>> + dev_pm_clear_wake_irq(&pdev->dev);
>> + pci_err(pdev, "Failed to set irq_type: %d\n", ret);
>> + return;
>> + }
>> +
>> + device_init_wakeup(&pdev->dev, true);
> [Severity: High]
> Could this forcefully enable wakeup by default and violate standard PCI PM
> policy?
>
> Calling device_init_wakeup() with true marks the device as wakeup capable
> and also sets dev->power.should_wakeup = true. The standard PCI core
> behavior usually marks devices as wakeup capable but leaves enablement to
> userspace opt-in.
if the client driver wants to enable wakeup IRQ, and if the WAKE# is described for
root port only, then we can't ask client driver to enable root port device wake up
capability and in suspend noirq we are not allowed to enable wakeirq, so we
are enabling here.
>> +}
>> +
>> +void pci_configure_of_wake_gpio(struct pci_dev *dev)
>> +{
>> + struct device_node *dn = pci_device_to_OF_node(dev);
>> + struct gpio_desc *gpio;
>> +
>> + if (!dn)
>> + return;
>> + /*
>> + * fwnode_gpiod_get() may fail with -EBUSY (e.g. shared WAKE#), but the
>> + * actual WAKE# trigger from the device would still work and the host
>> + * controller driver will enable power to the topology.
>> + *
>> + * -EPROBE_DEFER cannot be propagated here since pci_device_add() has no
>> + * retry mechanism.
>> + */
>> + gpio = fwnode_gpiod_get(of_fwnode_handle(dn), "wake", GPIOD_IN, NULL);
>> + if (!IS_ERR(gpio)) {
> [Severity: Medium]
> Since pci_device_add() has no retry mechanism, does swallowing -EPROBE_DEFER
> here mean WAKE# functionality will permanently fail if the GPIO controller
> providing the WAKE# line hasn't probed yet?
In device tree model -EPROBE_DEFER for GPIO is highly unlikely. since PERST gpio's
is already parsed in the controller driver before enumeration starts.
> [Severity: High]
> If multiple endpoints share a WAKE# GPIO, only the first device acquires
> it while the rest fail with -EBUSY. Can this break wakeup capabilities
> for all remaining endpoints if the first device is later hot-removed?
>
> When the first device is removed, it frees the shared GPIO via
> pci_remove_of_wake_gpio(), leaving the remaining active endpoints without
> an associated wake IRQ.
in this scenarios, ideally bridge also will share same WAKE# gpio, so if the
bridge
is removed below devices also removed. so it should not effect.
>> + dev->wake = gpio;
>> + pci_configure_wake_irq(dev, gpio);
>> + }
>> +}
>> +
>> +void pci_remove_of_wake_gpio(struct pci_dev *dev)
>> +{
>> + struct device_node *dn = pci_device_to_OF_node(dev);
>> +
>> + if (!dn)
>> + return;
>> +
>> + device_init_wakeup(&dev->dev, false);
>> + dev_pm_clear_wake_irq(&dev->dev);
>> + gpiod_put(dev->wake);
>> + dev->wake = NULL;
>> +}
> [Severity: High]
> Should this cleanup block be guarded by a check for dev->wake?
>
> As it stands, if a PCI device with an OF node failed to acquire the GPIO
> (e.g., it returned -EBUSY) or didn't have a wake property, this will still
> unconditionally clear its PME wakeup capability and detach any dedicated
> wake IRQ the driver might have configured itself.
ack.
- Krishna Chaitanya.
>> diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
>> index b63cd0c310bc0..143b0bd35b3c9 100644
>> --- a/drivers/pci/probe.c
>> +++ b/drivers/pci/probe.c
> [ ... ]
>
>> @@ -2775,6 +2775,8 @@ void pci_device_add(struct pci_dev *dev, struct pci_bus *bus)
>> /* Establish pdev->tsm for newly added (e.g. new SR-IOV VFs) */
>> pci_tsm_init(dev);
>>
>> + platform_pci_configure_wake(dev);
> [Severity: Medium]
> Does calling platform_pci_configure_wake() after device_add() create a race
> condition?
>
> By the time this is called, the device is already visible in sysfs and
> uevents have fired. Local userspace tools configuring power/wakeup or
> asynchronous driver probes could have their configurations silently
> overwritten by the subsequent device_init_wakeup() call.
>
^ permalink raw reply
* Re: [PATCH v5 3/3] arm64: dts: qcom: Add Vicharak Axon Mini
From: Konrad Dybcio @ 2026-06-24 13:24 UTC (permalink / raw)
To: Ajit Singh, Bjorn Andersson
Cc: Dmitry Baryshkov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
linux-arm-msm, devicetree, linux-kernel
In-Reply-To: <20260624125443.18729-4-blfizzyy@gmail.com>
On 6/24/26 2:54 PM, Ajit Singh wrote:
> Add DTS for the Vicharak Axon Mini board based on the Qualcomm
> QCS6490 SoC.
>
> This adds debug UART, eMMC, UFS, SDIO WLAN, USB 2.0 host, PCIe
> support along with regulators.
>
> The UFS ICE block is kept disabled because enabling it currently causes
> an SError during qcom_ice_create() on this board. UFS works without ICE.
>
> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
> Signed-off-by: Ajit Singh <blfizzyy@gmail.com>
> ---
Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Konrad
^ permalink raw reply
* RE: [PATCH] dt-bindings: clock: renesas,versaclock7: Update maintainer
From: Biju Das @ 2026-06-24 13:24 UTC (permalink / raw)
To: geert
Cc: Krzysztof Kozlowski, biju.das.au, Geert Uytterhoeven, Alex Helms,
Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, magnus.damm, Brian Masney,
linux-renesas-soc@vger.kernel.org, linux-clk@vger.kernel.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
Prabhakar Mahadev Lad
In-Reply-To: <CAMuHMdV67ZuhpSjiu-rWrgyPAMafEDVALD5b0k_bFMYkfQnP8A@mail.gmail.com>
Hi Geert,
> -----Original Message-----
> From: Geert Uytterhoeven <geert@linux-m68k.org>
> Sent: 24 June 2026 14:16
> Subject: Re: [PATCH] dt-bindings: clock: renesas,versaclock7: Update maintainer
>
> Hi Biju,
>
> On Wed, 24 Jun 2026 at 11:59, Biju Das <biju.das.jz@bp.renesas.com> wrote:
> > > From: Krzysztof Kozlowski <krzk@kernel.org> On 24/06/2026 11:46,
> > > Biju Das wrote:
> > > >> From: Krzysztof Kozlowski <krzk@kernel.org>
> > > >> Sent: 24 June 2026 10:42
> > > >> Subject: Re: [PATCH] dt-bindings: clock: renesas,versaclock7:
> > > >> Update maintainer
> > > >>
> > > >> On Tue, Jun 23, 2026 at 05:20:37PM +0100, Biju wrote:
> > > >>> From: Biju Das <biju.das.jz@bp.renesas.com>
> > > >>>
> > > >>> Alex's email is bouncing. Update the maintainers list with my
> > > >>> contact details to take over the schema maintenance.
> > > >>>
> > > >>> Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
> > > >>> ---
> > > >>> Ref [1]
> > > >>> [1] https://lore.kernel.org/all/ajqWevofEJ3fv856@redhat.com/
> > > >>> ---
> > > >>> .../devicetree/bindings/clock/renesas,versaclock7.yaml | 2 +-
> > > >>> 1 file changed, 1 insertion(+), 1 deletion(-)
> > > >>
> > > >> Please also update MAINTAINERS file.
> > > >
> > > > It is taken care in [1]
> > > >
> > > > [1]
> > > > https://lore.kernel.org/all/CAMuHMdW0-WsZuuc7PoVNC5DBUoY9dP+ULmGTQ
> > > > 76VW
> > > > MO_SjpbuQ@mail.gmail.com/
> > >
> > > Just squash them together.
> > >
> > > When you fix such issue like wrong email, fix it everywhere, not
> > > just one-by-one where the maintainers need to poke you to fix in
> > > other places as well. It is generic rule for bugs as well. The wrong email address is the most
> obvious example here, because it is also extremely easy. Just use git grep.
> >
> > Ok, but both patches were already queued by Geert for 7.3.
>
> I will squash them while committing.
Thank you for handling this.
Cheers,
Biju
^ permalink raw reply
* Re: [PATCH] dt-bindings: clock: renesas,versaclock7: Update maintainer
From: Geert Uytterhoeven @ 2026-06-24 13:16 UTC (permalink / raw)
To: Biju Das
Cc: Krzysztof Kozlowski, biju.das.au, Geert Uytterhoeven, Alex Helms,
Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, magnus.damm, Brian Masney,
linux-renesas-soc@vger.kernel.org, linux-clk@vger.kernel.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
Prabhakar Mahadev Lad
In-Reply-To: <TY3PR01MB11346A6077B4F7380078EA3B486ED2@TY3PR01MB11346.jpnprd01.prod.outlook.com>
Hi Biju,
On Wed, 24 Jun 2026 at 11:59, Biju Das <biju.das.jz@bp.renesas.com> wrote:
> > From: Krzysztof Kozlowski <krzk@kernel.org>
> > On 24/06/2026 11:46, Biju Das wrote:
> > >> From: Krzysztof Kozlowski <krzk@kernel.org>
> > >> Sent: 24 June 2026 10:42
> > >> Subject: Re: [PATCH] dt-bindings: clock: renesas,versaclock7: Update
> > >> maintainer
> > >>
> > >> On Tue, Jun 23, 2026 at 05:20:37PM +0100, Biju wrote:
> > >>> From: Biju Das <biju.das.jz@bp.renesas.com>
> > >>>
> > >>> Alex's email is bouncing. Update the maintainers list with my
> > >>> contact details to take over the schema maintenance.
> > >>>
> > >>> Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
> > >>> ---
> > >>> Ref [1]
> > >>> [1] https://lore.kernel.org/all/ajqWevofEJ3fv856@redhat.com/
> > >>> ---
> > >>> .../devicetree/bindings/clock/renesas,versaclock7.yaml | 2 +-
> > >>> 1 file changed, 1 insertion(+), 1 deletion(-)
> > >>
> > >> Please also update MAINTAINERS file.
> > >
> > > It is taken care in [1]
> > >
> > > [1]
> > > https://lore.kernel.org/all/CAMuHMdW0-WsZuuc7PoVNC5DBUoY9dP+ULmGTQ76VW
> > > MO_SjpbuQ@mail.gmail.com/
> >
> > Just squash them together.
> >
> > When you fix such issue like wrong email, fix it everywhere, not just one-by-one where the maintainers
> > need to poke you to fix in other places as well. It is generic rule for bugs as well. The wrong email
> > address is the most obvious example here, because it is also extremely easy. Just use git grep.
>
> Ok, but both patches were already queued by Geert for 7.3.
I will squash them while committing.
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply
* Re: [PATCH 3/6] drm/tiny: Add DRM driver for Solomon SSD16xx e-paper display controllers
From: Devarsh Thakkar @ 2026-06-24 13:19 UTC (permalink / raw)
To: Thomas Zimmermann, David Airlie, Simona Vetter, Maarten Lankhorst,
Maxime Ripard, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Neil Armstrong, Bjorn Andersson, dri-devel, devicetree,
linux-kernel
Cc: praneeth, vigneshr, s-jain1, r-donadkar, r-sharma3, afd, Sen Wang,
LiangCheng Wang, Aldea, Andrei, Judith Mendez, D, Yashas
In-Reply-To: <0920e4a7-9619-42b6-ba59-160f38a5c090@suse.de>
Hi Thomas,
On 23/06/26 14:47, Thomas Zimmermann wrote:
> Hi,
>
> sorry, I've lost track of what the most recent status is here.
>
Thanks for the response.
> Am 18.06.26 um 17:41 schrieb Devarsh Thakkar:
> [...]
>>>>>> +config DRM_PANEL_SSD16XX
>>>>>
>>>>> Just call it DRM_SSD16XX without the panel. In DRM, things named
>>>>> 'panel' are usually built around struct drm_panel, which doesn't
>>>>> seem the case here.
>>>>>
>>>>
>>
>> I see drivers/gpu/drm/tiny/panel-mipi-dbi.c [0] using
>> CONFIG_DRM_PANEL_MIPI_DBI [1] in the same directory, and that driver
>> does not use struct drm_panel either - the only drm_panel reference
>> there is a call to of_get_drm_panel_display_mode(), which is a DT
>> display-mode helper unrelated to the panel framework.
>
> It's either a misnomer or there for historical reasons IMHO. Just mipi-
> dbi would have been better. Or maybe it should use the framework around
> drm_panel. As it is now, the naming is somwhat misleading.
>
>>
Ah ok thanks for sharing, I would then stick to ssd16xx.c and
DRM_SSD16XX as you earlier suggested.
>> Given this existing precedent in drm/tiny/ itself and also for the
>> reasons explained below as this driver houses both controller specific
>> and panel specific logic, I would prefer to keep DRM_PANEL_SSD16XX.
>
> No need to duplicate bad decisions. Also, can your driver use the panel
> framework?
>
As I understand, the driver cannot use the panel framework as struct
drm_panel requires a hardware host (DSI/DBI/DPI controller) that calls
drm_panel_prepare(), drm_panel_enable() etc. This is a standalone SPI
driver, similar to the other e-paper drivers in drivers/gpu/drm/tiny/.
You had also asked to check directory for this driver, I am thinking to
keep driver in drm/tiny/ only instead of the solomon/ directory, since
solomon/ currently only has OLED SSD130x drivers which use I2C or SPI to
drive an emissive display with a completely different programming model
and no common factor with this e-paper driver. Hope this is fine.
There were also some open discussion points for concerns you raised
regarding the DRM properties - rotation, refresh_mode, clear_on_* and
color_mode and other props - for which I have elaborated my reasoning
and had also listed the modifications I would be making in my earlier
reply [0]. I am not sure you had a chance to review that, but in V2 I
will be keeping these as separate patches so they can be reviewed
independently.
[0]:
https://lore.kernel.org/all/eccf407a-c469-4744-a56f-aa7366c58be3@ti.com/
Best regards,
Devarsh Thakkar
> Best regards
> Thomas
>
>
>
>>
>>
>>
>>>> Oh ok, I preferred DRM_PANEL_SSD16XX since it also enumerates and
>>>> uses panel specific data/compatible such as this driver supporting
>>>> gooddisplay,gdey042t81 and more can be added too (just like panel-
>>>> ilitek* for e.g.) unlike controller only drivers which need to be
>>>> linked to separate panel drivers.
>>>>
>>>> Do you prefer to change it to DRM_SSD16XX_PANEL to not conflict with
>>>> DRM_PANEL* drivers and for better context or still prefer to keep it
>>>> as DRM_SSD16XX ?
>>>>
>>>>>> +
>>
>> <snip>
>>
>>>>>> diff --git a/drivers/gpu/drm/tiny/panel-ssd16xx.c b/drivers/gpu/
>>>>>> drm/ tiny/panel-ssd16xx.c
>>>>>> new file mode 100644
>>>>>> inde`x 000000000000..b232837c54ff
>>>>>> --- /dev/null
>>>>>> +++ b/drivers/gpu/drm/tiny/panel-ssd16xx.c
>>>>>
>>>>> Again, remove 'panel'.
>>>>
>>
>> Also for the naming too, I'd prefer to keep panel-ssd16xx.c similar to
>> panel-mipi-dbi.c [0] for the reasons mentioned below.
>>
>>>> Yes I can remove the panel, but I am just concerned if it won't
>>>> mislead folks to understand ssd16xx as a controller only driver,
>>>> requiring a separate panel driver to interface with ?
>>>>
>>>> Basically panel-ssd16xx naming was chosen since this driver houses
>>>> both the ssd16xx controller context and also the panel being used
>>>> along with that (similar to panel-ilitek-ili9881c.c) and i did not
>>>> want to confuse it with a controller only driver (similar to
>>>> tc358775.c), if it is overalapping a known pattern reserved for
>>>> drm_panel drivers do you think we should rename it to ssd16xx-
>>>> panel.c instead or you prefer ssd16xx.c as more appropriate one ?
>>>>
>>
>> Kindly let me know if it sounds okay.
>>
>> [0] : https://gitlab.com/linux-kernel/linux-next/-/blob/next-20260618/
>> drivers/gpu/drm/tiny/panel-mipi-dbi.c?ref_type=tags
>> [1] : https://gitlab.com/linux-kernel/linux-next/-/blob/next-20260618/
>> drivers/gpu/drm/tiny/Makefile?ref_type=tags#L8
>>
>> Regards
>> Devarsh
>>
>>>>>
>>>>>> @@ -0,0 +1,2548 @@
>>>>>> +// SPDX-License-Identifier: GPL-2.0-only
>>>>>> +/*
>>>>>> + * DRM driver for e-paper display panels using Solomon SSD16xx
>>>>>> family controllers
>>>>>> + *
>>>>>> + * Copyright (C) 2026 Texas Instruments Incorporated - https://
>>>>>> www.ti.com/
>>>>>> + *
>>>>>> + * Author: Devarsh Thakkar <devarsht@ti.com>
>>>>>> + *
>>>>>> + * References: https://github.com/Lesords/epaper
>>>>>> + */
>>>>>> +
>>>>>> +#include <linux/delay.h>
>>>>>> +#include <linux/module.h>
>>>>>> +#include <linux/of.h>
>>>>>> +#include <linux/property.h>
>>>>>> +#include <linux/spi/spi.h>
>>>>>> +
>>>>>> +#include <drm/clients/drm_client_setup.h>
>>>>>> +#include <drm/drm_atomic.h>
>>>>>> +#include <drm/drm_atomic_helper.h>
>>>>>> +#include <drm/drm_damage_helper.h>
>>>>>> +#include <drm/drm_drv.h>
>>>>>> +#include <drm/drm_fb_helper.h>
>>>>>> +#include <drm/drm_fbdev_dma.h>
>>>>>> +#include <drm/drm_fb_dma_helper.h>
>>>>>> +#include <drm/drm_framebuffer.h>
>>>>>> +#include <drm/drm_gem_dma_helper.h>
>>>>>> +#include <drm/drm_gem_framebuffer_helper.h>
>>>>>> +#include <drm/drm_probe_helper.h>
>>>>>
>>>>>> +#include <drm/drm_simple_kms_helper.h>
>>>>>
>>>>> Obsolete. Anything you use from this header should be open-coded in
>>>>> the driver.
>>>>>
>>>>
>>>> Agreed, will remove it in V2.
>>>>
>>>>>> +#include <drm/drm_print.h>
>>>>>
>>>>>
>>>>> Please remove all of the parameters below. They might be nice for
>>>>> your debugging, but they do not belong in the upstream driver.
>>>>>
>>>>
>>>> As mentioned previously, had kept these params mainly for legacy
>>>> non- drm fbdev based applications.
>>>>
>>>>>> +
>>>>>> +static int rotation = -1;
>>>>>> +module_param(rotation, int, 0644);
>>>>>> +MODULE_PARM_DESC(rotation,
>>>>>> + "Display rotation (-1=use DT, 0/180=landscape,
>>>>>> 90/270=portrait)");
>>>>>
>>>>> Please remove this. There is a rotation property in struct
>>>>> drm_connector, which stores the rotation. IIRC it can be overridden
>>>>> on the kernel command line.
>>>>>
>>>>
>>>> As I understand you are referring to below fields from drm_connector
>>>> struct, please correct me if I am wrong here but I think the
>>>> rotation/ orientation functionality supported by ssd16xx controller
>>>> does not match much with below model but instead matches what is
>>>> done in drivers/gpu/ drm/drm_mipi_dbi.c (although that does not
>>>> support runtime rotation) as explained below :
>>>>
>>>> drm_connector (rotation specific members):
>>>>
>>>>
>>>> 1. panel_orientation (display_info.panel_orientation):
>>>> Readable from DT via of_drm_get_panel_orientation(), overridable from
>>>> cmdline. However it is not writable by userspace at runtime (which
>>>> we require). More importantly, when Weston reads panel_orientation
>>>> it applies an output transform and then attempts to offload rotation
>>>> to the plane via plane.rotation. This model assumes the plane can
>>>> geometrically map a 300x400 source framebuffer to a 400x300 CRTC
>>>> i.e. hardware scan- out rotation. Our driver has no such hardware as
>>>> explained in detail below.
>>>>
>>>>
>>>> 2. rotation_reflection (cmdline_mode.rotation_reflection):
>>>> Cmdline-only (video=...:rotate=N), no DT path (we require both DT-
>>>> path and runtime suport). Also I think this is strictly for in-
>>>> kernel drm_clients and also It currently returns false for 90/270
>>>> unless
>>>> the plane has a hardware rotation property.
>>>>
>>>>
>>>> Both paths therefore ultimately require hardware plane rotation that
>>>> this driver does not have and both seem to be supported just
>>>> statically i.e. cmdline or dt property.
>>>>
>>>> Our use-case needs to support runtime rotation configuration ours is
>>>> not a mounted display but a portable hand-held device (https://
>>>> www.beagleboard.org/boards/beaglebadge) and we have an accelerometer
>>>> in our device which can detect panel orientation and based on
>>>> accelerometer reading the drm app can runtime set the custom drm
>>>> rotation property to switch to new orientation dynamically.
>>>>
>>>> Also our driver is fundamentally different from a GPU display
>>>> pipeline or controllers supporting transpose function. The SSD16xx
>>>> display controller has no transpose or rotation function but instead
>>>> supports different scan-modes, so there is no hardware path that can
>>>> take a 400x300 plane and transpose it to a 300x400 display output.
>>>> The controller is a simple RAM writer: the CPU writes a byte stream
>>>> over SPI, and the controller's internal cursor
>>>> advances sequentially according to the data entry mode register
>>>> (command
>>>> 0x11), which selects between X++/Y++ and X--/Y-- scan directions with a
>>>> configurable start position.
>>>>
>>>>
>>>> For portrait orientation we therefore change the DRM mode itself to
>>>> 300x400 from the original 400x300, so the application is asked to
>>>> provide a 300x400 framebuffer.
>>>> The driver then writes this buffer column-by-column over SPI to the
>>>> display controller's RAM. Since the controller supports different scan
>>>> start positions (cursor at origin vs cursor at maximum address)
>>>> combined
>>>> with the appropriate X/Y scan direction, we are able to correctly
>>>> render
>>>> the 300x400 buffer onto the panel when it is held in portrait
>>>> orientation (90*, 270*).
>>>>
>>>>
>>>> This means the CRTC mode must reflect the logical dimensions directly,
>>>> exactly as drm_mipi_dbi_dev_init() does via mipi_dbi_rotate_mode() for
>>>> MIPI DBI drivers. Accepting a 300x400 framebuffer onto a 400x300 CRTC
>>>> (as the panel_orientation + plane.rotation model requires) is not
>>>> possible: drm_atomic_helper_check_plane_state(DRM_PLANE_NO_SCALING)
>>>> enforces src_w == crtc_w and src_h == crtc_h, and there is no hardware
>>>> to perform the geometric remapping between the two sizes.
>>>>
>>>>
>>>> For runtime rotation changes (which are required as the panel is not
>>>> physically fixed), we therefor wanted to use a custom drm connector
>>>> property. We can look to use the standard DRM_MODE_ROTATE_* bitmask
>>>> (not a custom enum, that was used in v1), we can also look to check
>>>> if driver can triggers a full modeset through the normal DRM path,
>>>> connector_get_modes returns the correctly dimensioned mode for the
>>>> new orientation, and userspace receives a mode-changed event with
>>>> the new dimensions.
>>>>
>>>>
>>>> This is semantically what MIPI DBI tiny drivers do at boot (fixed
>>>> from DT), made runtime-changeable via the custom drm connector
>>>> property in this driver.
>>>>
>>>> Maybe, I can try to use standard bitmask instead of custom enum to
>>>> re- use standard macros :
>>>>
>>>> drm_property_create_bitmask(drm, 0, "rotation",
>>>> rotation_props,
>>>> ARRAY_SIZE(rotation_props),
>>>> DRM_MODE_ROTATE_0 DRM_MODE_ROTATE_90 |
>>>> DRM_MODE_ROTATE_180 |DRM_MODE_ROTATE_270);
>>>>
>>>> but keep it as connector property?
>>>>
>>>>>> +
>>>>>> +static int refresh_mode = -1;
>>>>>> +module_param(refresh_mode, int, 0644);
>>>>>> +MODULE_PARM_DESC(refresh_mode,
>>>>>> + "Refresh mode (-1=panel default, 0=partial ~300-500ms,
>>>>>> 1=full ~1.5-2s, 2=fast ~1.0-1.5s)");
>>>>>> +
>>>>>> +static int border_waveform_init_lut = -1;
>>>>>> +module_param(border_waveform_init_lut, int, 0644);
>>>>>> +MODULE_PARM_DESC(border_waveform_init_lut,
>>>>>> + "Border waveform index during clear/init (-1=panel
>>>>>> default, 0-9=enum index)");
>>>>>> +
>>>>>> +static int border_waveform_lut = -1;
>>>>>> +module_param(border_waveform_lut, int, 0644);
>>>>>> +MODULE_PARM_DESC(border_waveform_lut,
>>>>>> + "Border waveform index during display updates (-1=panel
>>>>>> default, 0-9=enum index)");
>>>>>> +
>>>>>
>>>>> Please remove it. Only the panel default. If you have panels where
>>>>> the default is known to be incorrect, you can add specific
>>>>> workarounds in the driver.
>>>>>
>>>>
>>>> I think the most of these params are kept to sane defaults but they
>>>> may change w.r.t use-cases and each panel can be used in context of
>>>> multiple use-cases.
>>>>
>>>>>
>>>>>> +static bool border_refresh_on_every_update;
>>>>>> +module_param(border_refresh_on_every_update, bool, 0644);
>>>>>> +MODULE_PARM_DESC(border_refresh_on_every_update,
>>>>>> + "Re-send border waveform command before each display
>>>>>> update (default: false)");
>>>>>
>>>>> Pick a sane default.
>>>>>
>>>>
>>>> Yes driver is picking a sane default already for this (refresh
>>>> border on init once with white border and keep it as floating in
>>>> later updates), but just a back-door for the application in case it
>>>> wants to avoid ghosting totally altogether or has specific needs
>>>> w.r.t border handling.
>>>>
>>>>>> +
>>>>>> +static int clear_on_init = -1;
>>>>>> +module_param(clear_on_init, int, 0644);
>>>>>> +MODULE_PARM_DESC(clear_on_init,
>>>>>> + "Clear display on first app launch (-1=disabled,
>>>>>> 0=partial, 1=full, 2=fast)");
>>>>>> +
>>>>>> +static int clear_on_close = -1;
>>>>>> +module_param(clear_on_close, int, 0644);
>>>>>> +MODULE_PARM_DESC(clear_on_close,
>>>>>> + "Clear display on app close/CRTC disable (-1=disabled,
>>>>>> 0=partial, 1=full, 2=fast)");
>>>>>> +
>>>>>> +static int clear_on_disable = -1;
>>>>>> +module_param(clear_on_disable, int, 0644);
>>>>>> +MODULE_PARM_DESC(clear_on_disable,
>>>>>> + "Clear display on CRTC disable/DPMS off (-1=disabled,
>>>>>> 0=partial, 1=full, 2=fast)");
>>>>>> +
>>>>>> +static int refresh_mode_init = -1;
>>>>>> +module_param(refresh_mode_init, int, 0644);
>>>>>> +MODULE_PARM_DESC(refresh_mode_init,
>>>>>> + "Skip baseline establishment on first enable
>>>>>> (-1=disabled, 0=partial, 1=full, 2=fast)");
>>>>>
>>>>> Use 'disabled' for all of them.
>>>>>
>>>>>> +
>>>>>> +static int color_mode = -1;
>>>>>> +module_param(color_mode, int, 0644);
>>>>>> +MODULE_PARM_DESC(color_mode,
>>>>>> + "Color mode (-1=panel default, 0=black-white, 1=3-color;
>>>>>> 3- color only valid for panels with red plane support)");
>>>>>
>>>>> 'Panel default.' Colors should be controlled by DRM clients via
>>>>> the framebuffer.
>>>>>
>>>>
>>>> As mentioned previously, say user-space is only supporting and
>>>> giving XR24 or XR32 format, from that we can't infer whether user-
>>>> space want to drive display in B/W mode or color-mode.
>>>>
>>>>>> +
>>>>>> +/*
>>>>>> -----------------------------------------------------------------------
>>>>>> + * SSD16xx family common: commands, data values, and bit
>>>>>> definitions.
>>>>>> + * These apply equally to SSD1673, SSD1680, and SSD1683.
>>>>>> + *
>>>>>> -----------------------------------------------------------------------
>>>>>> + */
>>>>>> +
>>>>>> +/* SPI command codes (common) */
>>>>>> +#define SSD16XX_CMD_DRIVER_OUTPUT_CONTROL 0x01
>>>>>> +#define SSD16XX_CMD_DATA_ENTRY_MODE 0x11
>>>>>> +#define SSD16XX_CMD_SW_RESET 0x12
>>>>>> +#define SSD16XX_CMD_MASTER_ACTIVATION 0x20
>>>>>> +#define SSD16XX_CMD_DISPLAY_UPDATE_CONTROL1 0x21
>>>>>> +#define SSD16XX_CMD_DISPLAY_UPDATE_CONTROL2 0x22
>>>>>> +#define SSD16XX_CMD_WRITE_RAM_BW 0x24
>>>>>> +#define SSD16XX_CMD_BORDER_WAVEFORM_CONTROL 0x3C
>>>>>> +#define SSD16XX_CMD_SET_RAM_X_ADDRESS_START_END 0x44
>>>>>> +#define SSD16XX_CMD_SET_RAM_Y_ADDRESS_START_END 0x45
>>>>>> +#define SSD16XX_CMD_SET_RAM_X_ADDRESS_COUNTER 0x4E
>>>>>> +#define SSD16XX_CMD_SET_RAM_Y_ADDRESS_COUNTER 0x4F
>>>>>> +
>>>>>> +/*
>>>>>> + * Data Entry Mode (command 0x11) AM/IDY/IDX bit encoding (common).
>>>>>> + *
>>>>>> + * Bit 2 (AM): Address update direction: 0 = X direction, 1 = Y
>>>>>> direction
>>>>>> + * ID[1:0] when AM=0 (X-direction modes, address counter advances
>>>>>> in X):
>>>>>> + * 00 = X decrement, Y decrement 01 = X increment, Y decrement
>>>>>> + * 10 = X decrement, Y increment 11 = X increment, Y
>>>>>> increment (default)
>>>>>> + *
>>>>>> + * Rotation to data entry mode mapping (actual implementation
>>>>>> uses two modes,
>>>>>> + * with scan direction controlled via RAM cursor positioning and
>>>>>> manual tweaking):
>>>>>> + * 0°/270° → 0x03 (X++, Y++) Landscape/Portrait-CW: cursor at
>>>>>> (0, 0)
>>>>>> + * 90°/180° → 0x00 (X--, Y--) Portrait-CCW/Upside-down: cursor
>>>>>> at (max, max)
>>>>>> + *
>>>>>> + * The pixel packing in convert_fb_to_1bpp is grouped by physical
>>>>>> layout:
>>>>>> + * - Portrait (90°/270°): column-major packing, rightmost
>>>>>> column first
>>>>>> + * - Landscape (0°/180°): row-major packing, top to bottom,
>>>>>> left to right
>>>>>> + * Hardware cursor position and scan mode handle the final
>>>>>> orientation.
>>>>>> + */
>>>>>> +#define SSD16XX_DATA_ENTRY_XDEC_YDEC 0x00 /* X--, Y-- (X-
>>>>>> mode) */
>>>>>> +#define SSD16XX_DATA_ENTRY_XINC_YINC 0x03 /* X++, Y++ (X-
>>>>>> mode, default) */
>>>>>> +
>>>>>> +/* POR reset value: GD=0 (G0 first), SM=0 (interlaced), TB=0 (G0-
>>>>>> >G299) */
>>>>>> +#define SSD16XX_DRIVER_OUTPUT_CTRL_DEFAULT 0x00
>>>>>> +
>>>>>> +/* Display Update Control 1 (0x21) byte 2 default (common) */
>>>>>> +#define SSD16XX_CTRL1_BYTE2_DEFAULT 0x00
>>>>>> +
>>>>>> +/*
>>>>>> + * Display Update Control 2 (0x22) individual bit definitions
>>>>>> (common).
>>>>>> + * NOTE: BIT(3) is NOT common — see SSD1683_CTRL2_MODE2 in the
>>>>>> SSD1683
>>>>>> + * section below; it has a completely different meaning in SSD1673.
>>>>>> + */
>>>>>> +#define SSD16XX_CTRL2_ENABLE_CLK BIT(7)
>>>>>> +#define SSD16XX_CTRL2_ENABLE_ANALOG BIT(6)
>>>>>> +#define SSD16XX_CTRL2_LOAD_TEMPERATURE BIT(5)
>>>>>> +#define SSD16XX_CTRL2_LOAD_LUT BIT(4)
>>>>>> +#define SSD16XX_CTRL2_DISPLAY BIT(2)
>>>>>> +#define SSD16XX_CTRL2_DISABLE_ANALOG BIT(1)
>>>>>> +#define SSD16XX_CTRL2_DISABLE_CLK BIT(0)
>>>>>> +
>>>>>> +#define SSD16XX_SPI_BITS_PER_WORD 8
>>>>>> +#define SSD16XX_SPI_SPEED_DEFAULT 1000000
>>>>>> +
>>>>>> +/* Maximum time to wait for the BUSY pin to deassert after a
>>>>>> display update */
>>>>>> +#define SSD16XX_BUSY_WAIT_TIMEOUT_MS 6000
>>>>>> +
>>>>>> +/*
>>>>>> -----------------------------------------------------------------------
>>>>>> + * SSD1683 / SSD1680 specific: commands, data values, and bit
>>>>>> definitions.
>>>>>> + *
>>>>>> -----------------------------------------------------------------------
>>>>>> + */
>>>>>> +
>>>>>> +/*
>>>>>> + * Deep Sleep Mode values (command 0x10).
>>>>>> + */
>>>>>> +#define SSD1683_DEEP_SLEEP_MODE_1 0x01 /* RAM
>>>>>> retained */
>>>>>> +#define SSD1683_DEEP_SLEEP_MODE_2 0x03 /* RAM lost
>>>>>> (max power) */
>>>>>> +
>>>>>> +/*
>>>>>> + * Temperature Sensor Selection (command 0x18).
>>>>>> + */
>>>>>> +#define SSD1683_CMD_TEMPERATURE_SENSOR_CONTROL 0x18
>>>>>> +#define SSD1683_TEMP_SENSOR_INTERNAL 0x80 /* Bit 7:
>>>>>> use internal sensor */
>>>>>> +
>>>>>> +/*
>>>>>> + * Write RED RAM (command 0x26).
>>>>>> + */
>>>>>> +#define SSD1683_CMD_WRITE_RAM_RED 0x26
>>>>>> +
>>>>>> +/*
>>>>>> + * Border Waveform Control (command 0x3C) byte values.
>>>>>> + */
>>>>>> +#define SSD1683_BORDER_WAVEFORM_LUT0 0x00 /* GS
>>>>>> Transition LUT0 (black) */
>>>>>> +#define SSD1683_BORDER_WAVEFORM_LUT1 0x01 /* GS
>>>>>> Transition LUT1 (white) */
>>>>>> +#define SSD1683_BORDER_WAVEFORM_LUT2 0x02 /* GS
>>>>>> Transition LUT2 (black) */
>>>>>> +#define SSD1683_BORDER_WAVEFORM_LUT3 0x03 /* GS
>>>>>> Transition LUT3 (gray) */
>>>>>> +#define SSD1683_BORDER_WAVEFORM_FIXLVL_VSS 0x40 /* Fix Level
>>>>>> VSS (0V, black) */
>>>>>> +#define SSD1683_BORDER_WAVEFORM_FIXLVL_VSH1 0x50 /* Fix Level
>>>>>> VSH1 (+15V, black) */
>>>>>> +#define SSD1683_BORDER_WAVEFORM_FIXLVL_VSL 0x60 /* Fix Level
>>>>>> VSL (-15V, white) */
>>>>>> +#define SSD1683_BORDER_WAVEFORM_FIXLVL_VSH2 0x70 /* Fix Level
>>>>>> VSH2 (+15V alt, black) */
>>>>>> +#define SSD1683_BORDER_WAVEFORM_VCOM 0x80 /* Follow VCOM
>>>>>> (-2V~-3V, preserve) */
>>>>>> +#define SSD1683_BORDER_WAVEFORM_HIZ 0xC0 /* HiZ
>>>>>> (floating, default) */
>>>>>> +
>>>>>> +/*
>>>>>> + * Display Update Control 1 (0x21) byte 1 — RED RAM control.
>>>>>> + */
>>>>>> +#define SSD1683_CTRL1_NORMAL 0x00 /* Both BW and RED
>>>>>> RAMs enabled */
>>>>>> +#define SSD1683_CTRL1_BYPASS_RED_RAM 0x40 /* Bypass RED
>>>>>> RAM (force RED=0) */
>>>>>> +
>>>>>> +/*
>>>>>> + * Display Update Control 2 (0x22) BIT(3) — "Display Mode
>>>>>> 2" (partial/BW).
>>>>>> + */
>>>>>> +#define SSD1683_CTRL2_MODE2 BIT(3)
>>>>>> +
>>>>>> +/* Composite CTRL2 sequences for each refresh mode */
>>>>>> +#define SSD1683_CTRL2_FULL_REFRESH (SSD16XX_CTRL2_ENABLE_CLK | \
>>>>>> + SSD16XX_CTRL2_ENABLE_ANALOG | \
>>>>>> + SSD16XX_CTRL2_LOAD_TEMPERATURE | \
>>>>>> + SSD16XX_CTRL2_LOAD_LUT | \
>>>>>> + SSD16XX_CTRL2_DISPLAY | \
>>>>>> + SSD16XX_CTRL2_DISABLE_ANALOG | \
>>>>>> + SSD16XX_CTRL2_DISABLE_CLK) /* 0xF7, ~1.5-2s */
>>>>>> +
>>>>>> +#define SSD1683_CTRL2_FAST_REFRESH (SSD16XX_CTRL2_ENABLE_CLK | \
>>>>>> + SSD16XX_CTRL2_ENABLE_ANALOG | \
>>>>>> + SSD16XX_CTRL2_DISPLAY | \
>>>>>> + SSD16XX_CTRL2_DISABLE_ANALOG | \
>>>>>> + SSD16XX_CTRL2_DISABLE_CLK) /* 0xC7,
>>>>>> ~1.0-1.5s */
>>>>>> +
>>>>>> +#define SSD1683_CTRL2_PARTIAL_REFRESH (SSD16XX_CTRL2_ENABLE_CLK | \
>>>>>> + SSD16XX_CTRL2_ENABLE_ANALOG | \
>>>>>> + SSD16XX_CTRL2_LOAD_TEMPERATURE | \
>>>>>> + SSD16XX_CTRL2_LOAD_LUT | \
>>>>>> + SSD1683_CTRL2_MODE2 | \
>>>>>> + SSD16XX_CTRL2_DISPLAY | \
>>>>>> + SSD16XX_CTRL2_DISABLE_ANALOG | \
>>>>>> + SSD16XX_CTRL2_DISABLE_CLK) /* 0xFF,
>>>>>> ~300-500ms */
>>>>>> +
>>>>>> +/*
>>>>>> + * Standalone LUT pre-load sequence (0x91 = ENABLE_CLK | LOAD_LUT
>>>>>> | LOAD_TEMPERATURE |
>>>>>> + * DISABLE_CLK).
>>>>>> + * Pre-loads the OTP LUT without triggering a display update.
>>>>>> Required for
>>>>>> + * FAST refresh mode (0xC7) which omits LOAD_LUT from each update
>>>>>> cycle.
>>>>>> + */
>>>>>> +#define SSD1683_CTRL2_LOAD_TEMP_LUT (SSD16XX_CTRL2_ENABLE_CLK | \
>>>>>> + SSD16XX_CTRL2_LOAD_LUT | \
>>>>>> + SSD16XX_CTRL2_LOAD_TEMPERATURE | \
>>>>>> + SSD16XX_CTRL2_DISABLE_CLK) /* 0xB1 */
>>>>>> +
>>>>>> +MODULE_IMPORT_NS("DMA_BUF");
>>>>>> +
>>>>>> +enum ssd16xx_controller {
>>>>>> + SSD1683 = 1,
>>>>>> +};
>>>>>> +
>>>>>> +enum ssd16xx_model {
>>>>>> + GDEY042T81 = 1,
>>>>>> +};
>>>>>> +
>>>>>> +enum ssd16xx_refresh_mode {
>>>>>> + SSD16XX_REFRESH_PARTIAL = 0, /* Partial refresh (~300-500ms) */
>>>>>> + SSD16XX_REFRESH_FULL, /* Full refresh (~1.5-2s) */
>>>>>> + SSD16XX_REFRESH_FAST, /* Fast refresh, skip temp load
>>>>>> (~1.0-1.5s) */
>>>>>> +};
>>>>>> +
>>>>>> +enum ssd16xx_color_mode {
>>>>>> + SSD16XX_COLOR_MODE_BW = 0, /* Black/white only; RED RAM
>>>>>> always bypassed */
>>>>>> + SSD16XX_COLOR_MODE_3COLOR = 1, /* 3-colour BWR; RED RAM used
>>>>>> for red pixels */
>>>>>> +};
>>>>>> +
>>>>>> +/* Border waveform enum indices (0-9); mapped to HW bytes via
>>>>>> + * controller_cfg->border_waveform_table[]
>>>>>> + */
>>>>>> +enum ssd16xx_border_waveform {
>>>>>> + SSD16XX_BORDER_LUT0 = 0, /* GS Transition LUT0 (black) */
>>>>>> + SSD16XX_BORDER_LUT1, /* GS Transition LUT1 (white) */
>>>>>> + SSD16XX_BORDER_LUT2, /* GS Transition LUT2 (black) */
>>>>>> + SSD16XX_BORDER_LUT3, /* GS Transition LUT3 (gray) */
>>>>>> + SSD16XX_BORDER_VSS, /* Fix Level VSS (black) */
>>>>>> + SSD16XX_BORDER_VSH1, /* Fix Level VSH1 (black) */
>>>>>> + SSD16XX_BORDER_VSL, /* Fix Level VSL (white) */
>>>>>> + SSD16XX_BORDER_VSH2, /* Fix Level VSH2 (black) */
>>>>>> + SSD16XX_BORDER_VCOM, /* Follow VCOM (preserve) */
>>>>>> + SSD16XX_BORDER_HIZ, /* HiZ (floating, default) */
>>>>>> +};
>>>>>> +
>>>>>> +/* SSD1683/SSD1680 border waveform byte encoding for command 0x3C */
>>>>>> +static const u8 ssd1683_border_waveform_table[] = {
>>>>>> + [SSD16XX_BORDER_LUT0] = SSD1683_BORDER_WAVEFORM_LUT0,
>>>>>> + [SSD16XX_BORDER_LUT1] = SSD1683_BORDER_WAVEFORM_LUT1,
>>>>>> + [SSD16XX_BORDER_LUT2] = SSD1683_BORDER_WAVEFORM_LUT2,
>>>>>> + [SSD16XX_BORDER_LUT3] = SSD1683_BORDER_WAVEFORM_LUT3,
>>>>>> + [SSD16XX_BORDER_VSS] = SSD1683_BORDER_WAVEFORM_FIXLVL_VSS,
>>>>>> + [SSD16XX_BORDER_VSH1] = SSD1683_BORDER_WAVEFORM_FIXLVL_VSH1,
>>>>>> + [SSD16XX_BORDER_VSL] = SSD1683_BORDER_WAVEFORM_FIXLVL_VSL,
>>>>>> + [SSD16XX_BORDER_VSH2] = SSD1683_BORDER_WAVEFORM_FIXLVL_VSH2,
>>>>>> + [SSD16XX_BORDER_VCOM] = SSD1683_BORDER_WAVEFORM_VCOM,
>>>>>> + [SSD16XX_BORDER_HIZ] = SSD1683_BORDER_WAVEFORM_HIZ,
>>>>>> +};
>>>>>> +
>>>>>> +struct ssd16xx_controller_config {
>>>>>> + u16 max_width;
>>>>>> + u16 max_height;
>>>>>> + u8 ram_x_address_bits;
>>>>>> + u8 ram_y_address_bits;
>>>>>> +
>>>>>> + /*
>>>>>> + * has_temp_sensor_ctrl: controller supports command 0x18
>>>>>> (Temperature
>>>>>> + * Sensor Selection). Present in SSD1683/SSD1680; absent in
>>>>>> SSD1673
>>>>>> + * which uses command 0x1A (direct temperature write) instead.
>>>>>> + */
>>>>>> + bool has_temp_sensor_ctrl;
>>>>>> +
>>>>>> + /*
>>>>>> + * Deep sleep mode byte values for command 0x10.
>>>>>> + * deep_sleep_mode_level1: lower-power sleep, RAM content
>>>>>> retained
>>>>>> + * (MODE_1 on SSD1683/SSD1680; used for runtime idle /
>>>>>> app- close).
>>>>>> + * deep_sleep_mode_level2: maximum power savings, RAM may
>>>>>> be lost
>>>>>> + * (MODE_2 on SSD1683/SSD1680; used for system suspend).
>>>>>> + * Chips with a single sleep mode set both fields to the same
>>>>>> value.
>>>>>> + */
>>>>>> + u8 deep_sleep_mode_level1;
>>>>>> + u8 deep_sleep_mode_level2;
>>>>>> +
>>>>>> + /*
>>>>>> + * border_waveform_table: chip-specific byte values for the
>>>>>> 10 logical
>>>>>> + * border waveform modes (indexed by enum
>>>>>> ssd16xx_border_waveform).
>>>>>> + * The encoding of command 0x3C differs between SSD1683/
>>>>>> SSD1680 and
>>>>>> + * SSD1673, so each controller provides its own translation
>>>>>> table.
>>>>>> + */
>>>>>> + const u8 *border_waveform_table;
>>>>>> +
>>>>>> + /*
>>>>>> + * Display Update Control 1 (cmd 0x21) byte 1 values.
>>>>>> + * ctrl1_normal: both BW and RED RAMs participate in
>>>>>> the waveform.
>>>>>> + * ctrl1_bypass_red_ram: RED RAM bypassed; waveform driven
>>>>>> from BW RAM only.
>>>>>> + * SSD1673 has no RED RAM so both fields carry the same value.
>>>>>> + */
>>>>>> + u8 ctrl1_normal;
>>>>>> + u8 ctrl1_bypass_red_ram;
>>>>>> +
>>>>>> + /*
>>>>>> + * Display Update Control 2 (cmd 0x22) composite sequences
>>>>>> for each
>>>>>> + * refresh mode (indexed by enum ssd16xx_refresh_mode) and the
>>>>>> + * standalone LUT pre-load sequence used before fast refresh.
>>>>>> + * Values differ between SSD1683/SSD1680 and SSD1673 (MODE2
>>>>>> bit, etc.).
>>>>>> + */
>>>>>> + u8 ctrl2_refresh[3]; /* indexed by
>>>>>> SSD16XX_REFRESH_PARTIAL/ FULL/FAST */
>>>>>> + u8 ctrl2_load_temp_lut; /* standalone LUT pre-load (no
>>>>>> display update) */
>>>>>> +};
>>>>>> +
>>>>>> +struct ssd16xx_panel_config {
>>>>>> + /* Data Entry Mode - controls X/Y increment direction for
>>>>>> landscape (0°) */
>>>>>> + u8 data_entry_mode;
>>>>>> +
>>>>>> + /* Driver Output Control - third byte (scan direction) */
>>>>>> + u8 driver_output_ctrl_byte3;
>>>>>> +
>>>>>> + /* Default refresh mode for this panel */
>>>>>> + enum ssd16xx_refresh_mode default_refresh_mode;
>>>>>> +
>>>>>> + /* Default border waveform during clear/init (enum index 0-9) */
>>>>>> + enum ssd16xx_border_waveform default_border_waveform_init;
>>>>>> +
>>>>>> + /* Default border waveform during display updates (enum index
>>>>>> 0-9) */
>>>>>> + enum ssd16xx_border_waveform default_border_waveform_update;
>>>>>> +
>>>>>> + /* Whether to re-send border waveform command before each
>>>>>> display update */
>>>>>> + bool default_border_refresh_on_every_update;
>>>>>> +
>>>>>> + /*
>>>>>> + * Default clear-on-init behaviour.
>>>>>> + * -1=disabled, 0=partial, 1=full, 2=fast (matches enum
>>>>>> ssd16xx_refresh_mode)
>>>>>> + */
>>>>>> + int default_clear_on_init;
>>>>>> +
>>>>>> + /* Default clear-on-close behaviour (-1=disabled, 0=partial,
>>>>>> 1=full, 2=fast) */
>>>>>> + int default_clear_on_close;
>>>>>> +
>>>>>> + /* Default clear-on-disable behaviour (-1=disabled,
>>>>>> 0=partial, 1=full, 2=fast) */
>>>>>> + int default_clear_on_disable;
>>>>>> +
>>>>>> + /*
>>>>>> + * Default refresh-mode-init: -1=disabled, else skip baseline
>>>>>> establishment
>>>>>> + * and start directly in this refresh mode.
>>>>>> + */
>>>>>> + int default_refresh_mode_init;
>>>>>> +
>>>>>> + /*
>>>>>> + * Whether this panel has a physical red colour plane (3-
>>>>>> colour BWR).
>>>>>> + * false: 2-colour black/white only; the RED RAM is always
>>>>>> bypassed.
>>>>>> + * true: 3-colour panel; full-refresh writes to the RED RAM
>>>>>> so that
>>>>>> + * red pixels are driven through the red waveform.
>>>>>> + */
>>>>>> + bool red_supported;
>>>>>> +
>>>>>> + /* Panel-specific display mode (resolution and physical
>>>>>> dimensions) */
>>>>>> + const struct drm_display_mode *mode;
>>>>>> +};
>>>>>> +
>>>>>> +struct ssd16xx_panel {
>>>>>
>>>>> Better call this 'struct ssd16xx_device' and the rsp variables
>>>>> 'ssd16xx'. As mentioned, the name 'panel' already has a specific
>>>>> meaning in DRM.
>>>>>
>>>>
>>>> Alright I can do that, I thought folks won't confuse it since this
>>>> is not importing drm_panel struct.
>>>>
>>>>>
>>>>>> + struct drm_device drm;
>>>>>> +
>>>>>> + struct drm_plane primary_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;
>>>>>> +
>>>>>> + enum ssd16xx_model model;
>>>>>> + enum ssd16xx_controller controller;
>>>>>> + const struct ssd16xx_controller_config *controller_cfg;
>>>>>> + const struct ssd16xx_panel_config *panel_cfg;
>>>>>> + struct drm_display_mode *mode;
>>>>>> + u32 width;
>>>>>> + u32 height;
>>>>>> +
>>>>>> + bool initialized;
>>>>>> + bool reinit_pending; /* HW re-init required after
>>>>>> orientation change */
>>>>>> + bool init_refresh_pending; /* First frame after
>>>>>> refresh_mode_init enable */
>>>>>> + bool first_clear_done; /* clear_on_init has already fired
>>>>>> once */
>>>>>> + bool display_cleared_on_deinit; /* Avoid redundant clear in
>>>>>> atomic_disable/master_drop */
>>>>>> +
>>>>>> + int orientation; /* Display orientation in degrees:
>>>>>> 0/90/180/270 */
>>>>>> + enum ssd16xx_refresh_mode refresh_mode; /* Active refresh
>>>>>> mode */
>>>>>> + enum ssd16xx_color_mode color_mode; /* Active color mode
>>>>>> (BW or 3-color) */
>>>>>> + bool fast_lut_pending; /* LUT pre-load needed before next
>>>>>> fast refresh */
>>>>>> +
>>>>>> + /* Border waveform (as enum indices) */
>>>>>> + int border_waveform_init_idx; /* Border waveform during
>>>>>> clear/ init */
>>>>>> + int border_waveform_update_idx; /* Border waveform during
>>>>>> display updates */
>>>>>> + bool border_refresh_on_every_update; /* Re-send border cmd
>>>>>> each display update */
>>>>>> + bool border_waveform_pending; /* One-shot: send border cmd
>>>>>> on next update */
>>>>>> +
>>>>>> + /* Display control */
>>>>>> + int clear_on_init; /* -1=disabled, 0=partial, 1=full,
>>>>>> 2=fast */
>>>>>> + int clear_on_close; /* -1=disabled, 0=partial, 1=full,
>>>>>> 2=fast */
>>>>>> + int clear_on_disable; /* -1=disabled, 0=partial, 1=full,
>>>>>> 2=fast */
>>>>>> + int refresh_mode_init; /* -1=disabled, else use this mode for
>>>>>> the first frame */
>>>>>> +
>>>>>> + u8 *tx_buf; /* 1bpp frame buffer (mono + white) */
>>>>>> + u8 *tx_red_buf; /* 1bpp red-channel buffer (3-color panels
>>>>>> only) */
>>>>>> + u16 *tx_buf9; /* 9-bit SPI expansion buffer (3-wire mode
>>>>>> only) */
>>>>>> +
>>>>>> + struct drm_framebuffer *last_fb; /* Last drawn FB for
>>>>>> reinit redraws */
>>>>>> + struct drm_property *rotation_property;
>>>>>> + struct drm_property *refresh_mode_property;
>>>>>> + struct drm_property *border_waveform_init_property;
>>>>>> + struct drm_property *border_waveform_update_property;
>>>>>> + struct drm_property *border_refresh_on_every_update_property;
>>>>>> + struct drm_property *clear_on_init_property;
>>>>>> + struct drm_property *clear_on_close_property;
>>>>>> + struct drm_property *clear_on_disable_property;
>>>>>> + struct drm_property *refresh_mode_init_property;
>>>>>> + struct drm_property *color_mode_property;
>>>>>> +};
>>>>>> +
>>>>>> +static inline struct ssd16xx_panel *to_ssd16xx_panel(struct
>>>>>> drm_device *drm)
>>>>>> +{
>>>>>> + return container_of(drm, struct ssd16xx_panel, drm);
>>>>>> +}
>>>>>> +
>>>>>> +static inline struct ssd16xx_panel *crtc_to_ssd16xx_panel(struct
>>>>>> drm_crtc *crtc)
>>>>>> +{
>>>>>> + return container_of(crtc, struct ssd16xx_panel, crtc);
>>>>>> +}
>>>>>> +
>>>>>> +static inline struct ssd16xx_panel *plane_to_ssd16xx_panel(struct
>>>>>> drm_plane *plane)
>>>>>> +{
>>>>>> + return container_of(plane, struct ssd16xx_panel, primary_plane);
>>>>>> +}
>>>>>> +
>>>>>> +static const struct ssd16xx_controller_config
>>>>>> ssd16xx_controller_configs[] = {
>>>>>> + [SSD1683] = {
>>>>>> + .max_width = 400,
>>>>>> + .max_height = 300,
>>>>>> + .ram_x_address_bits = 8,
>>>>>> + .ram_y_address_bits = 16,
>>>>>> + .has_temp_sensor_ctrl = true,
>>>>>> + .deep_sleep_mode_level1 = SSD1683_DEEP_SLEEP_MODE_1,
>>>>>> + .deep_sleep_mode_level2 = SSD1683_DEEP_SLEEP_MODE_2,
>>>>>> + .border_waveform_table = ssd1683_border_waveform_table,
>>>>>> + .ctrl1_normal = SSD1683_CTRL1_NORMAL,
>>>>>> + .ctrl1_bypass_red_ram = SSD1683_CTRL1_BYPASS_RED_RAM,
>>>>>> + .ctrl2_refresh = {
>>>>>> + [SSD16XX_REFRESH_PARTIAL] =
>>>>>> SSD1683_CTRL2_PARTIAL_REFRESH,
>>>>>> + [SSD16XX_REFRESH_FULL] = SSD1683_CTRL2_FULL_REFRESH,
>>>>>> + [SSD16XX_REFRESH_FAST] = SSD1683_CTRL2_FAST_REFRESH,
>>>>>> + },
>>>>>> + .ctrl2_load_temp_lut = SSD1683_CTRL2_LOAD_TEMP_LUT,
>>>>>> + },
>>>>>> +};
>>>>>> +
>>>>>> +/* GDEY042T81: 4.2" 400x300 panel, 84.8x63.6mm active area */
>>>>>> +static const struct drm_display_mode gdey042t81_mode = {
>>>>>> + DRM_SIMPLE_MODE(400, 300, 85, 64),
>>>>>> +};
>>>>>> +
>>>>>> +static const struct ssd16xx_panel_config ssd16xx_panel_configs[] = {
>>>>>> + [GDEY042T81] = {
>>>>>> + .data_entry_mode = SSD16XX_DATA_ENTRY_XINC_YINC,
>>>>>> + .driver_output_ctrl_byte3 =
>>>>>> SSD16XX_DRIVER_OUTPUT_CTRL_DEFAULT,
>>>>>> + .default_refresh_mode = SSD16XX_REFRESH_PARTIAL,
>>>>>> + .default_border_waveform_init = SSD16XX_BORDER_LUT1, /*
>>>>>> white, clean clear */
>>>>>> + .default_border_waveform_update = SSD16XX_BORDER_HIZ, /*
>>>>>> floating, preserve */
>>>>>> + .default_border_refresh_on_every_update = false,
>>>>>> + .default_clear_on_init = -1,
>>>>>> + .default_clear_on_close = -1,
>>>>>> + .default_clear_on_disable = -1,
>>>>>> + .default_refresh_mode_init = SSD16XX_REFRESH_FULL,
>>>>>> + .red_supported = false, /* 2-colour black/white panel */
>>>>>> + .mode = &gdey042t81_mode,
>>>>>> + },
>>>>>> +};
>>>>>> +
>>>>>> +static void ssd16xx_wait_for_panel(struct ssd16xx_panel *panel,
>>>>>> + int *err)
>>>>>> +{
>>>>>> + unsigned long timeout_jiffies = jiffies +
>>>>>> + msecs_to_jiffies(SSD16XX_BUSY_WAIT_TIMEOUT_MS);
>>>>>> + unsigned long start_ms = jiffies_to_msecs(jiffies);
>>>>>> + int busy_val;
>>>>>> +
>>>>>> + if (*err)
>>>>>> + return;
>>>>>
>>>>> This is good. It'll simplify error handling in other places.
>>>>>
>>>>>> +
>>>>>> + busy_val = gpiod_get_value_cansleep(panel->busy);
>>>>>> + drm_dbg(&panel->drm, "BUSY initial value: %d\n", busy_val);
>>>>>> +
>>>>>> + while (gpiod_get_value_cansleep(panel->busy) == 1) {
>>>>>> + if (time_after(jiffies, timeout_jiffies)) {
>>>>>> + drm_err(&panel->drm, "Busy wait timed out after
>>>>>> %lums\n",
>>>>>> + jiffies_to_msecs(jiffies) - start_ms);
>>>>>> + *err = -ETIMEDOUT;
>>>>>> + return;
>>>>>> + }
>>>>>> + usleep_range(100, 200);
>>>>>> + }
>>>>>> +
>>>>>> + drm_dbg(&panel->drm, "BUSY became ready after %lums\n",
>>>>>> + jiffies_to_msecs(jiffies) - start_ms);
>>>>>> +}
>>>>>> +
>>>>>> +static void ssd16xx_spi_sync(struct spi_device *spi, struct
>>>>>> spi_message *msg,
>>>>>> + int *err)
>>>>>> +{
>>>>>> + int ret;
>>>>>> +
>>>>>> + if (*err)
>>>>>> + return;
>>>>>> +
>>>>>> + ret = spi_sync(spi, msg);
>>>>>> + if (ret < 0)
>>>>>> + *err = ret;
>>>>>> +}
>>>>>> +
>>>>>> +static void ssd16xx_send_cmd(struct ssd16xx_panel *panel, u8 cmd,
>>>>>> + int *err)
>>>>>> +{
>>>>>> + u16 word;
>>>>>> + struct spi_transfer xfer = {};
>>>>>> + struct spi_message msg;
>>>>>> +
>>>>>> + if (*err)
>>>>>> + return;
>>>>>> +
>>>>>> + spi_message_init(&msg);
>>>>>> + spi_message_add_tail(&xfer, &msg);
>>>>>> +
>>>>>> + if (panel->dc) {
>>>>>> + /* 4-wire SPI: D/C# GPIO low selects command mode */
>>>>>> + xfer.tx_buf = &cmd;
>>>>>> + xfer.len = 1;
>>>>>> + gpiod_set_value_cansleep(panel->dc, 0);
>>>>>> + } else {
>>>>>> + /*
>>>>>> + * 3-wire SPI (9-bit): bit 8 is the D/C# bit.
>>>>>> + * D/C# = 0 means the following 8 bits are a command.
>>>>>> + */
>>>>>> + word = cmd; /* bit 8 = 0 for command */
>>>>>> + xfer.tx_buf = &word;
>>>>>> + xfer.len = sizeof(u16);
>>>>>> + xfer.bits_per_word = 9;
>>>>>> + }
>>>>>> +
>>>>>> + ssd16xx_spi_sync(panel->spi, &msg, err);
>>>>>> +}
>>>>>> +
>>>>>> +static void ssd16xx_send_data(struct ssd16xx_panel *panel, u8 data,
>>>>>> + int *err)
>>>>>> +{
>>>>>> + u16 word;
>>>>>> + struct spi_transfer xfer = {};
>>>>>> + struct spi_message msg;
>>>>>> +
>>>>>> + if (*err)
>>>>>> + return;
>>>>>> +
>>>>>> + spi_message_init(&msg);
>>>>>> + spi_message_add_tail(&xfer, &msg);
>>>>>> +
>>>>>> + if (panel->dc) {
>>>>>> + /* 4-wire SPI: D/C# GPIO high selects data mode */
>>>>>> + xfer.tx_buf = &data;
>>>>>> + xfer.len = 1;
>>>>>> + gpiod_set_value_cansleep(panel->dc, 1);
>>>>>> + } else {
>>>>>> + /*
>>>>>> + * 3-wire SPI (9-bit): bit 8 is the D/C# bit.
>>>>>> + * D/C# = 1 means the following 8 bits are data.
>>>>>> + */
>>>>>> + word = 0x100 | data;
>>>>>> + xfer.tx_buf = &word;
>>>>>> + xfer.len = sizeof(u16);
>>>>>> + xfer.bits_per_word = 9;
>>>>>> + }
>>>>>> +
>>>>>> + ssd16xx_spi_sync(panel->spi, &msg, err);
>>>>>> +}
>>>>>> +
>>>>>> +static void ssd16xx_send_x_param(struct ssd16xx_panel *panel, u16 x,
>>>>>> + int *err)
>>>>>> +{
>>>>>> + if (*err)
>>>>>> + return;
>>>>>> +
>>>>>> + if (panel->controller_cfg->ram_x_address_bits == 8) {
>>>>>> + ssd16xx_send_data(panel, (u8)x, err);
>>>>>> + } else {
>>>>>> + ssd16xx_send_data(panel, x & 0xFF, err);
>>>>>> + ssd16xx_send_data(panel, (x >> 8) & 0xFF, err);
>>>>>> + }
>>>>>> +}
>>>>>> +
>>>>>> +static void ssd16xx_send_y_param(struct ssd16xx_panel *panel, u16 y,
>>>>>> + int *err)
>>>>>> +{
>>>>>> + if (*err)
>>>>>> + return;
>>>>>> +
>>>>>> + if (panel->controller_cfg->ram_y_address_bits == 8) {
>>>>>> + ssd16xx_send_data(panel, (u8)y, err);
>>>>>> + } else {
>>>>>> + ssd16xx_send_data(panel, y & 0xFF, err);
>>>>>> + ssd16xx_send_data(panel, (y >> 8) & 0xFF, err);
>>>>>> + }
>>>>>> +}
>>>>>> +
>>>>>> +static void ssd16xx_send_data_bulk(struct ssd16xx_panel *panel,
>>>>>> + const u8 *data, size_t len,
>>>>>> + int *err)
>>>>>> +{
>>>>>> + struct spi_transfer xfer = {};
>>>>>> + struct spi_message msg;
>>>>>> +
>>>>>> + if (*err)
>>>>>> + return;
>>>>>> +
>>>>>> + if (!data || !len)
>>>>>> + return;
>>>>>> +
>>>>>> + spi_message_init(&msg);
>>>>>> + spi_message_add_tail(&xfer, &msg);
>>>>>> +
>>>>>> + if (panel->dc) {
>>>>>> + /* 4-wire SPI: D/C# GPIO high selects data mode */
>>>>>> + xfer.tx_buf = data;
>>>>>> + xfer.len = len;
>>>>>> + gpiod_set_value_cansleep(panel->dc, 1);
>>>>>> + ssd16xx_spi_sync(panel->spi, &msg, err);
>>>>>> + } else {
>>>>>> + /* 3-wire (9-bit): expand u8 → u16 with D/C#=1 in bit 8. */
>>>>>> + size_t i;
>>>>>> + u16 *buf = panel->tx_buf9;
>>>>>> +
>>>>>> + for (i = 0; i < len; i++)
>>>>>> + buf[i] = 0x100 | data[i];
>>>>>> +
>>>>>> + xfer.tx_buf = buf;
>>>>>> + xfer.len = len * sizeof(u16);
>>>>>> + xfer.bits_per_word = 9;
>>>>>> + ssd16xx_spi_sync(panel->spi, &msg, err);
>>>>>> + }
>>>>>> +}
>>>>>> +
>>>>>> +static void ssd16xx_display_update(struct ssd16xx_panel *panel,
>>>>>> + u8 ctrl1_byte1, u8 ctrl1_byte2, u8 ctrl2_mode,
>>>>>> + int *err)
>>>>>> +{
>>>>>> + if (*err)
>>>>>> + return;
>>>>>> +
>>>>>> + drm_dbg(&panel->drm,
>>>>>> + "display_update: Setting ctrl1=0x%02x,0x%02x mode=0x%02x\n",
>>>>>> + ctrl1_byte1, ctrl1_byte2, ctrl2_mode);
>>>>>> +
>>>>>> + ssd16xx_send_cmd(panel, SSD16XX_CMD_DISPLAY_UPDATE_CONTROL1,
>>>>>> err);
>>>>>> + ssd16xx_send_data(panel, ctrl1_byte1, err);
>>>>>> + ssd16xx_send_data(panel, ctrl1_byte2, err);
>>>>>> +
>>>>>> + ssd16xx_send_cmd(panel, SSD16XX_CMD_DISPLAY_UPDATE_CONTROL2,
>>>>>> err);
>>>>>> + ssd16xx_send_data(panel, ctrl2_mode, err);
>>>>>> + ssd16xx_send_cmd(panel, SSD16XX_CMD_MASTER_ACTIVATION, err);
>>>>>> +
>>>>>> + drm_dbg(&panel->drm,
>>>>>> + "display_update: Master activation sent, waiting...\n");
>>>>>> +
>>>>>> + ssd16xx_wait_for_panel(panel, err);
>>>>>> +}
>>>>>> +
>>>>>> +static void ssd16xx_hw_reset(struct ssd16xx_panel *panel)
>>>>>> +{
>>>>>> + gpiod_set_value_cansleep(panel->reset, 1);
>>>>>> + usleep_range(10000, 11000);
>>>>>> + gpiod_set_value_cansleep(panel->reset, 0);
>>>>>> + usleep_range(10000, 11000);
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * ssd16xx_preload_fast_lut() - pre-load the OTP LUT for fast
>>>>>> refresh mode.
>>>>>> + *
>>>>>> + * Fast refresh (CTRL2 = 0xC7) omits the LOAD_LUT step on every
>>>>>> update to save
>>>>>> + * time. It relies on the LUT being loaded upfront via this
>>>>>> standalone sequence
>>>>>> + * (CTRL2 = 0xB1: ENABLE_CLK | LOAD_LUT |
>>>>>> SSD16XX_CTRL2_LOAD_TEMPERATURE | DISABLE_CLK,
>>>>>> + * no display update).
>>>>>> + *
>>>>>> + * Must be called when:
>>>>>> + * a) hw_init runs with refresh_mode == FAST, and
>>>>>> + * b) switching to fast refresh from a mode that did not leave
>>>>>> a valid Mode1
>>>>>> + * LUT in the controller (i.e. previous mode was not FULL
>>>>>> refresh, which
>>>>>> + * carries LOAD_LUT in its own CTRL2 sequence).
>>>>>> + */
>>>>>> +static int ssd16xx_preload_fast_lut(struct ssd16xx_panel *panel)
>>>>>> +{
>>>>>> + int err = 0;
>>>>>> +
>>>>>> + ssd16xx_send_cmd(panel, SSD16XX_CMD_DISPLAY_UPDATE_CONTROL1,
>>>>>> &err);
>>>>>> + ssd16xx_send_data(panel, panel->controller_cfg-
>>>>>> >ctrl1_bypass_red_ram, &err);
>>>>>> + ssd16xx_send_data(panel, SSD16XX_CTRL1_BYTE2_DEFAULT, &err);
>>>>>> +
>>>>>> + ssd16xx_send_cmd(panel, SSD16XX_CMD_DISPLAY_UPDATE_CONTROL2,
>>>>>> &err);
>>>>>> + ssd16xx_send_data(panel, panel->controller_cfg-
>>>>>> >ctrl2_load_temp_lut, &err);
>>>>>> +
>>>>>> + ssd16xx_send_cmd(panel, SSD16XX_CMD_MASTER_ACTIVATION, &err);
>>>>>> + ssd16xx_wait_for_panel(panel, &err);
>>>>>> +
>>>>>> + return err;
>>>>>> +}
>>>>>> +
>>>>>> +static int ssd16xx_hw_init(struct ssd16xx_panel *panel)
>>>>>> +{
>>>>>> + int err = 0;
>>>>>> + u16 ram_height = panel->controller_cfg->max_height;
>>>>>> + u8 data_entry_mode;
>>>>>> +
>>>>>> + ssd16xx_hw_reset(panel);
>>>>>> +
>>>>>> + /* Software reset */
>>>>>> + ssd16xx_send_cmd(panel, SSD16XX_CMD_SW_RESET, &err);
>>>>>> + ssd16xx_wait_for_panel(panel, &err);
>>>>>> +
>>>>>> + /* Driver output control (0x01): MUX ratio and scan
>>>>>> direction. */
>>>>>> + ssd16xx_send_cmd(panel, SSD16XX_CMD_DRIVER_OUTPUT_CONTROL,
>>>>>> &err);
>>>>>> + ssd16xx_send_y_param(panel, ram_height - 1, &err);
>>>>>> + ssd16xx_send_data(panel, panel->panel_cfg-
>>>>>> >driver_output_ctrl_byte3, &err);
>>>>>> +
>>>>>> + /* Internal temperature sensor (SSD1683/SSD1680 only; not
>>>>>> present in SSD1673) */
>>>>>> + if (panel->controller_cfg->has_temp_sensor_ctrl) {
>>>>>> + ssd16xx_send_cmd(panel,
>>>>>> SSD1683_CMD_TEMPERATURE_SENSOR_CONTROL, &err);
>>>>>> + ssd16xx_send_data(panel, SSD1683_TEMP_SENSOR_INTERNAL,
>>>>>> &err);
>>>>>> + }
>>>>>> +
>>>>>> + /*
>>>>>> + * For FAST refresh mode, pre-load the LUT once here during
>>>>>> initialization.
>>>>>> + * FAST mode ctrl2 (0xC7) omits LOAD_LUT on every update for
>>>>>> speed, so the
>>>>>> + * LUT must be loaded upfront. FULL (0xF7) and PARTIAL (0xFF)
>>>>>> load LUT on
>>>>>> + * every update, so no preload is needed for those modes.
>>>>>> + */
>>>>>> + if (panel->refresh_mode == SSD16XX_REFRESH_FAST) {
>>>>>> + ssd16xx_send_cmd(panel,
>>>>>> SSD16XX_CMD_DISPLAY_UPDATE_CONTROL1, &err);
>>>>>> + ssd16xx_send_data(panel, panel->controller_cfg-
>>>>>> >ctrl1_bypass_red_ram, &err);
>>>>>> + ssd16xx_send_data(panel, SSD16XX_CTRL1_BYTE2_DEFAULT, &err);
>>>>>> +
>>>>>> + ssd16xx_send_cmd(panel,
>>>>>> SSD16XX_CMD_DISPLAY_UPDATE_CONTROL2, &err);
>>>>>> + ssd16xx_send_data(panel, panel->controller_cfg-
>>>>>> >ctrl2_load_temp_lut, &err);
>>>>>> +
>>>>>> + ssd16xx_send_cmd(panel, SSD16XX_CMD_MASTER_ACTIVATION,
>>>>>> &err);
>>>>>> + ssd16xx_wait_for_panel(panel, &err);
>>>>>> + }
>>>>>> +
>>>>>> + /*
>>>>>> + * Set Data Entry Mode (0x11) based on orientation. This
>>>>>> controls
>>>>>> + * how the RAM address counter auto-advances after each byte
>>>>>> write.
>>>>>> + *
>>>>>> + * Implementation uses two data entry modes:
>>>>>> + * - 90°/180° use XDEC_YDEC (0x00): X--, Y-- with cursor at
>>>>>> (max, max)
>>>>>> + * - 0°/270° use XINC_YINC (0x03): X++, Y++ with cursor at
>>>>>> (0, 0)
>>>>>> + *
>>>>>> + * The convert_fb_to_1bpp packing is grouped by physical layout:
>>>>>> + * - Portrait orientations (90°/270°): column-major packing
>>>>>> + * - Landscape orientations (0°/180°): row-major packing
>>>>>> + *
>>>>>> + * Final scan direction and image orientation are controlled
>>>>>> by the
>>>>>> + * combination of data entry mode and RAM cursor position set
>>>>>> in fb_dirty.
>>>>>> + *
>>>>>> + * The RAM address window and cursor are NOT set here; fb_dirty
>>>>>> + * always programmes them (with the correct end-before-start
>>>>>> order
>>>>>> + * for decrement modes) immediately before writing frame data.
>>>>>> + */
>>>>>> + switch (panel->orientation) {
>>>>>
>>>>> As mentioned, use the connector property instead.
>>>>>
>>>>>> + case 90:
>>>>>> + case 180:
>>>>>> + data_entry_mode = SSD16XX_DATA_ENTRY_XDEC_YDEC;
>>>>>> + break;
>>>>>> + default: /* 0°/270° */
>>>>>> + data_entry_mode = SSD16XX_DATA_ENTRY_XINC_YINC;
>>>>>> + break;
>>>>>> + }
>>>>>> +
>>>>>> + ssd16xx_send_cmd(panel, SSD16XX_CMD_DATA_ENTRY_MODE, &err);
>>>>>> + ssd16xx_send_data(panel, data_entry_mode, &err);
>>>>>> + drm_dbg(&panel->drm, "hw_init: orientation=%u°
>>>>>> data_entry=0x%02x\n",
>>>>>> + panel->orientation, data_entry_mode);
>>>>>> +
>>>>>> + ssd16xx_wait_for_panel(panel, &err);
>>>>>> +
>>>>>> + if (err)
>>>>>> + drm_err(&panel->drm, "Hardware initialization failed:
>>>>>> %d\n", err);
>>>>>> +
>>>>>> + return err;
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * Clear display by writing all-white to both BW and RED RAM.
>>>>>> + * The ctrl2 argument selects the waveform (full/partial/fast
>>>>>> refresh).
>>>>>> + * Border waveform is set to init value before clearing, then
>>>>>> restored
>>>>>> + * to the update value to preserve the border during subsequent
>>>>>> updates.
>>>>>> + */
>>>>>> +static int ssd16xx_clear_display(struct ssd16xx_panel *panel, u8
>>>>>> ctrl2)
>>>>>> +{
>>>>>> + const u8 *bw_tbl = panel->controller_cfg->border_waveform_table;
>>>>>> + int err = 0;
>>>>>> + unsigned int data_size = (panel->width * panel->height) / 8;
>>>>>> + u8 *white_buffer = panel->tx_buf;
>>>>>> +
>>>>>> + memset(white_buffer, 0xFF, data_size);
>>>>>> +
>>>>>> + ssd16xx_send_cmd(panel,
>>>>>> SSD16XX_CMD_SET_RAM_X_ADDRESS_COUNTER, &err);
>>>>>> + ssd16xx_send_x_param(panel, 0x00, &err);
>>>>>> +
>>>>>> + ssd16xx_send_cmd(panel,
>>>>>> SSD16XX_CMD_SET_RAM_Y_ADDRESS_COUNTER, &err);
>>>>>> + ssd16xx_send_y_param(panel, 0x00, &err);
>>>>>> +
>>>>>> + ssd16xx_send_cmd(panel, SSD16XX_CMD_WRITE_RAM_BW, &err);
>>>>>> + ssd16xx_send_data_bulk(panel, white_buffer, data_size, &err);
>>>>>> +
>>>>>> + ssd16xx_send_cmd(panel, SSD1683_CMD_WRITE_RAM_RED, &err);
>>>>>> + ssd16xx_send_data_bulk(panel, white_buffer, data_size, &err);
>>>>>> +
>>>>>> + /* Set border waveform for the clear operation */
>>>>>> + drm_dbg(&panel->drm, "clear_display: Set border init
>>>>>> waveform: 0x%02x\n",
>>>>>> + bw_tbl[panel->border_waveform_init_idx]);
>>>>>> + ssd16xx_send_cmd(panel, SSD16XX_CMD_BORDER_WAVEFORM_CONTROL,
>>>>>> &err);
>>>>>> + ssd16xx_send_data(panel,
>>>>>> + bw_tbl[panel->border_waveform_init_idx],
>>>>>> + &err);
>>>>>> +
>>>>>> + /* 3-colour mode: CTRL1_NORMAL (read both RAMs); BW mode:
>>>>>> bypass RED. */
>>>>>> + ssd16xx_display_update(panel,
>>>>>> + panel->color_mode == SSD16XX_COLOR_MODE_3COLOR
>>>>>> + ? panel->controller_cfg->ctrl1_normal
>>>>>> + : panel->controller_cfg->ctrl1_bypass_red_ram,
>>>>>> + SSD16XX_CTRL1_BYTE2_DEFAULT, ctrl2, &err);
>>>>>> +
>>>>>> + /* Restore border waveform to update/preservation value */
>>>>>> + drm_dbg(&panel->drm, "clear_display: Restored border update
>>>>>> waveform: 0x%02x\n",
>>>>>> + bw_tbl[panel->border_waveform_update_idx]);
>>>>>> + ssd16xx_send_cmd(panel, SSD16XX_CMD_BORDER_WAVEFORM_CONTROL,
>>>>>> &err);
>>>>>> + ssd16xx_send_data(panel,
>>>>>> + bw_tbl[panel->border_waveform_update_idx],
>>>>>> + &err);
>>>>>> +
>>>>>> + return err;
>>>>>> +}
>>>>>> +
>>>>>> +static u8 ssd16xx_refresh_mode_to_ctrl2(struct ssd16xx_panel *panel,
>>>>>> + enum ssd16xx_refresh_mode mode)
>>>>>> +{
>>>>>> + if (mode < ARRAY_SIZE(panel->controller_cfg->ctrl2_refresh))
>>>>>> + return panel->controller_cfg->ctrl2_refresh[mode];
>>>>>> + return panel->controller_cfg-
>>>>>> >ctrl2_refresh[SSD16XX_REFRESH_FULL];
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * Clear display on new DRM master open (if clear_on_init >= 0).
>>>>>> + * Guarded by panel->first_clear_done; master_drop resets it
>>>>>> unconditionally
>>>>>> + * so each new client session gets a fresh clear.
>>>>>> + */
>>>>>> +static int ssd16xx_clear_display_on_init(struct ssd16xx_panel
>>>>>> *panel)
>>>>>> +{
>>>>>> + int ret;
>>>>>> +
>>>>>> + if (panel->clear_on_init < 0 || panel->first_clear_done)
>>>>>> + return 0;
>>>>>> +
>>>>>> + drm_dbg(&panel->drm, "clear_on_init: running, mode=%d\n",
>>>>>> + panel->clear_on_init);
>>>>>> + ret = ssd16xx_clear_display(panel,
>>>>>> + ssd16xx_refresh_mode_to_ctrl2(panel, panel-
>>>>>> >clear_on_init));
>>>>>> + if (ret)
>>>>>> + return ret;
>>>>>> +
>>>>>> + panel->first_clear_done = true;
>>>>>> + return 0;
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * Clear display when the displaying client exits (if
>>>>>> clear_on_close >= 0).
>>>>>> + * Called from ssd16xx_drm_master_drop().
>>>>>> + */
>>>>>> +static int ssd16xx_clear_display_on_exit(struct ssd16xx_panel
>>>>>> *panel)
>>>>>> +{
>>>>>> + int ret;
>>>>>> +
>>>>>> + if (panel->clear_on_close < 0)
>>>>>> + return 0;
>>>>>> +
>>>>>> + drm_dbg(&panel->drm, "clear_on_close: running, mode=%d\n",
>>>>>> + panel->clear_on_close);
>>>>>> + ret = ssd16xx_clear_display(panel,
>>>>>> + ssd16xx_refresh_mode_to_ctrl2(panel, panel-
>>>>>> >clear_on_close));
>>>>>> + if (ret)
>>>>>> + return ret;
>>>>>> +
>>>>>> + return 0;
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * ssd16xx_pixel_luma() - return ITU-R BT.601 luminance (0-255)
>>>>>> for one pixel.
>>>>>> + *
>>>>>> + * For colour formats the result is (299*R + 587*G + 114*B) / 1000;
>>>>>> + * for luma-only formats the luma byte is returned directly.
>>>>>> + *
>>>>>> + * R1 is never passed here — it is already 1bpp and is handled
>>>>>> directly by
>>>>>> + * the callers.
>>>>>> + */
>>>>>> +static u8 ssd16xx_pixel_luma(struct iosys_map *src,
>>>>>> + struct drm_framebuffer *fb,
>>>>>> + unsigned int x, unsigned int y)
>>>>>> +{
>>>>>> + switch (fb->format->format) {
>>>>>> + case DRM_FORMAT_XRGB8888: {
>>>>>> + u32 *line = (u32 *)(src->vaddr + y * fb->pitches[0]);
>>>>>> + u32 px = line[x];
>>>>>> + u8 r = (px >> 16) & 0xFF, g = (px >> 8) & 0xFF, b = px &
>>>>>> 0xFF;
>>>>>> +
>>>>>> + return (u8)((299u * r + 587u * g + 114u * b) / 1000u);
>>>>>> + }
>>>>>> + case DRM_FORMAT_RGB888: {
>>>>>> + u8 *line = (u8 *)(src->vaddr + y * fb->pitches[0]);
>>>>>> + u8 r = line[x * 3], g = line[x * 3 + 1], b = line[x * 3 +
>>>>>> 2];
>>>>>> +
>>>>>> + return (u8)((299u * r + 587u * g + 114u * b) / 1000u);
>>>>>> + }
>>>>>> + case DRM_FORMAT_RGB565: {
>>>>>> + u16 *line = (u16 *)(src->vaddr + y * fb->pitches[0]);
>>>>>> + u16 px = line[x];
>>>>>> + u8 r = ((px >> 11) & 0x1F) << 3;
>>>>>> + u8 g = ((px >> 5) & 0x3F) << 2;
>>>>>> + u8 b = (px & 0x1F) << 3;
>>>>>> +
>>>>>> + return (u8)((299u * r + 587u * g + 114u * b) / 1000u);
>>>>>> + }
>>>>>> + case DRM_FORMAT_R8: {
>>>>>> + u8 *line = (u8 *)(src->vaddr + y * fb->pitches[0]);
>>>>>> +
>>>>>> + return line[x];
>>>>>> + }
>>>>>> + case DRM_FORMAT_NV12:
>>>>>> + case DRM_FORMAT_NV16:
>>>>>> + return ((u8 *)(src->vaddr))[y * fb->pitches[0] + x];
>>>>>> + case DRM_FORMAT_YUYV: {
>>>>>> + u8 *line = (u8 *)(src->vaddr + y * fb->pitches[0]);
>>>>>> +
>>>>>> + return line[x * 2];
>>>>>> + }
>>>>>> + case DRM_FORMAT_UYVY: {
>>>>>> + u8 *line = (u8 *)(src->vaddr + y * fb->pitches[0]);
>>>>>> +
>>>>>> + return line[x * 2 + 1];
>>>>>> + }
>>>>>> + default:
>>>>>> + return 0;
>>>>>> + }
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * ssd16xx_pixel_is_white() - test whether a pixel maps to white
>>>>>> in 1bpp output.
>>>>>> + *
>>>>>> + * Uses fixed threshold of 127. Pixels with luma strictly greater
>>>>>> than 127
>>>>>> + * are rendered white.
>>>>>> + */
>>>>>> +static bool ssd16xx_pixel_is_white(struct iosys_map *src,
>>>>>> + struct drm_framebuffer *fb,
>>>>>> + unsigned int x, unsigned int y)
>>>>>> +{
>>>>>> + /* R1 is already binarised; avoid the luma computation
>>>>>> entirely. */
>>>>>> + if (fb->format->format == DRM_FORMAT_R1) {
>>>>>> + u8 *line = (u8 *)(src->vaddr + y * fb->pitches[0]);
>>>>>> +
>>>>>> + return !!(line[x / 8] & (1 << (7 - (x % 8))));
>>>>>> + }
>>>>>> + return ssd16xx_pixel_luma(src, fb, x, y) > 127;
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * ssd16xx_pixel_is_red() - test whether a pixel is dominated by
>>>>>> the red channel.
>>>>>> + *
>>>>>> + * Only meaningful for formats that carry RGB information
>>>>>> (XRGB8888, RGB888,
>>>>>> + * RGB565). For luma-only and monochrome formats there is no red
>>>>>> channel, so
>>>>>> + * the function always returns false; callers should use
>>>>>> ssd16xx_pixel_is_white()
>>>>>> + * to obtain the BW value for those formats.
>>>>>> + *
>>>>>> + * Returns true when the red component exceeds 50% intensity AND
>>>>>> is strictly
>>>>>> + * greater than both green and blue (dominant red hue).
>>>>>> + */
>>>>>> +static bool ssd16xx_pixel_is_red(struct iosys_map *src,
>>>>>> + struct drm_framebuffer *fb,
>>>>>> + unsigned int x, unsigned int y)
>>>>>> +{
>>>>>> + u32 format = fb->format->format;
>>>>>> +
>>>>>> + switch (format) {
>>>>>> + case DRM_FORMAT_XRGB8888: {
>>>>>> + u32 *line = (u32 *)(src->vaddr + y * fb->pitches[0]);
>>>>>> + u32 px = line[x];
>>>>>> + u8 r = (px >> 16) & 0xFF;
>>>>>> + u8 g = (px >> 8) & 0xFF;
>>>>>> + u8 b = px & 0xFF;
>>>>>> +
>>>>>> + return r > 127 && r > g && r > b;
>>>>>> + }
>>>>>> + case DRM_FORMAT_RGB888: {
>>>>>> + u8 *line = (u8 *)(src->vaddr + y * fb->pitches[0]);
>>>>>> + u8 r = line[x * 3];
>>>>>> + u8 g = line[x * 3 + 1];
>>>>>> + u8 b = line[x * 3 + 2];
>>>>>> +
>>>>>> + return r > 127 && r > g && r > b;
>>>>>> + }
>>>>>> + case DRM_FORMAT_RGB565: {
>>>>>> + u16 *line = (u16 *)(src->vaddr + y * fb->pitches[0]);
>>>>>> + u16 px = line[x];
>>>>>> + u8 r = ((px >> 11) & 0x1F) << 3;
>>>>>> + u8 g = ((px >> 5) & 0x3F) << 2;
>>>>>> + u8 b = (px & 0x1F) << 3;
>>>>>> +
>>>>>> + return r > 127 && r > g && r > b;
>>>>>> + }
>>>>>> + default:
>>>>>> + return false; /* No colour channel information */
>>>>>> + }
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * ssd16xx_convert_fb_to_3color() - split a framebuffer into BW
>>>>>> and RED planes.
>>>>>> + * @bw_dst: output buffer for the black/white RAM plane
>>>>>> (1=white, 0=black)
>>>>>> + * @red_dst: output buffer for the red RAM plane (1=red, 0=not red)
>>>>>> + * @src: mapped framebuffer memory
>>>>>> + * @fb: DRM framebuffer descriptor
>>>>>> + * @rect: region to convert (must be aligned to 8-pixel
>>>>>> boundaries)
>>>>>> + *
>>>>>> + * Each output buffer must be at least rect_width/8 * rect_height
>>>>>> bytes.
>>>>>> + * Pixels are classified as:
>>>>>> + * - red: written to red_dst as 1, bw_dst as 0 (black)
>>>>>> + * - white: written to bw_dst as 1, red_dst as 0
>>>>>> + * - black: written to both as 0
>>>>>> + *
>>>>>> + * For monochrome formats (R1) where no colour information is
>>>>>> available the
>>>>>> + * source data is copied verbatim to bw_dst and red_dst is
>>>>>> cleared to 0xFF
>>>>>> + * (all-white = no red pixels).
>>>>>> + */
>>>>>> +static void ssd16xx_convert_fb_to_3color(u8 *bw_dst, u8 *red_dst,
>>>>>> + struct iosys_map *src,
>>>>>> + struct drm_framebuffer *fb,
>>>>>> + struct drm_rect *rect)
>>>>>> +{
>>>>>> + unsigned int x, y;
>>>>>> + u8 bw_byte = 0, red_byte = 0;
>>>>>> + unsigned int bit_pos = 0;
>>>>>> + unsigned int dst_idx = 0;
>>>>>> +
>>>>>> + drm_dbg(fb->dev,
>>>>>> + "convert_3color: fmt=%p4cc rect=(%d,%d)-(%d,%d) path=%s\n",
>>>>>> + &fb->format->format,
>>>>>> + rect->x1, rect->y1, rect->x2, rect->y2,
>>>>>> + fb->format->format == DRM_FORMAT_R1 ? "R1-direct" :
>>>>>> "color- pixel");
>>>>>> +
>>>>>> + /*
>>>>>> + * R1 is already monochrome — no colour channel exists.
>>>>>> + * Copy BW data directly and leave the red plane all-white
>>>>>> (transparent).
>>>>>> + */
>>>>>> + if (fb->format->format == DRM_FORMAT_R1) {
>>>>>> + unsigned int src_pitch = fb->pitches[0];
>>>>>> + unsigned int width_bytes = drm_rect_width(rect) / 8;
>>>>>> + unsigned int data_size = width_bytes *
>>>>>> drm_rect_height(rect);
>>>>>> +
>>>>>> + for (y = rect->y1; y < rect->y2; y++) {
>>>>>> + u8 *line = src->vaddr + y * src_pitch + (rect->x1 / 8);
>>>>>> +
>>>>>> + memcpy(bw_dst + dst_idx, line, width_bytes);
>>>>>> + dst_idx += width_bytes;
>>>>>> + }
>>>>>> + memset(red_dst, 0xFF, data_size); /* 0xFF = all white: no
>>>>>> red pixels */
>>>>>> + return;
>>>>>> + }
>>>>>> +
>>>>>> + /* Use fixed threshold of 127 for grayscale to monochrome
>>>>>> conversion. */
>>>>>> + for (y = rect->y1; y < rect->y2; y++) {
>>>>>> + for (x = rect->x1; x < rect->x2; x++) {
>>>>>> + bool is_red = ssd16xx_pixel_is_red(src, fb, x, y);
>>>>>> +
>>>>>> + if (is_red)
>>>>>> + red_byte |= (1 << (7 - bit_pos));
>>>>>> + else if (ssd16xx_pixel_is_white(src, fb, x, y))
>>>>>> + bw_byte |= (1 << (7 - bit_pos));
>>>>>> + /* else: black pixel — both bits remain 0 */
>>>>>> + if (++bit_pos == 8) {
>>>>>> + bw_dst[dst_idx] = bw_byte;
>>>>>> + red_dst[dst_idx] = red_byte;
>>>>>> + dst_idx++;
>>>>>> + bw_byte = 0;
>>>>>> + red_byte = 0;
>>>>>> + bit_pos = 0;
>>>>>> + }
>>>>>> + }
>>>>>> +
>>>>>> + /* Flush any partial byte at the end of each row */
>>>>>> + if (bit_pos > 0) {
>>>>>> + bw_dst[dst_idx] = bw_byte;
>>>>>> + red_dst[dst_idx] = red_byte;
>>>>>> + dst_idx++;
>>>>>> + bw_byte = 0;
>>>>>> + red_byte = 0;
>>>>>> + bit_pos = 0;
>>>>>> + }
>>>>>> + }
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * ssd16xx_convert_r8_to_red_only() - map an R8 framebuffer to
>>>>>> the RED RAM plane.
>>>>>> + *
>>>>>> + * Used when the panel has a physical red colour plane
>>>>>> (red_supported == true)
>>>>>> + * and the framebuffer format is DRM_FORMAT_R8. Pixels with
>>>>>> value >= 128 are
>>>>>> + * treated as red ink; the BW RAM is set to all-white so that
>>>>>> only red ink
>>>>>> + * appears on the white background.
>>>>>> + *
>>>>>> + * Hardware orientation is handled by the caller via RAM counter
>>>>>> positioning;
>>>>>> + * data is written in normal row-major order here (same as
>>>>>> convert_fb_to_3color).
>>>>>> + */
>>>>>> +static void ssd16xx_convert_r8_to_red_only(u8 *bw_dst, u8 *red_dst,
>>>>>> + struct iosys_map *src,
>>>>>> + struct drm_framebuffer *fb,
>>>>>> + struct drm_rect *rect)
>>>>>> +{
>>>>>> + unsigned int src_pitch = fb->pitches[0];
>>>>>> + unsigned int width = drm_rect_width(rect);
>>>>>> + unsigned int height = drm_rect_height(rect);
>>>>>> + unsigned int data_size = DIV_ROUND_UP(width, 8) * height;
>>>>>> + unsigned int dst_idx = 0;
>>>>>> + unsigned int x, y;
>>>>>> + u8 red_byte = 0;
>>>>>> + unsigned int bit_pos = 0;
>>>>>> +
>>>>>> + /* BW RAM: all-white background - no black ink, only red ink
>>>>>> shows */
>>>>>> +�� memset(bw_dst, 0xFF, data_size);
>>>>>> +
>>>>>> + /* RED RAM: R8 >= 128 -> red ink (1-bit set) */
>>>>>> + for (y = rect->y1; y < rect->y2; y++) {
>>>>>> + u8 *line = src->vaddr + y * src_pitch;
>>>>>> +
>>>>>> + for (x = rect->x1; x < rect->x2; x++) {
>>>>>> + if (line[x] >= 128)
>>>>>> + red_byte |= (1 << (7 - bit_pos));
>>>>>> + if (++bit_pos == 8) {
>>>>>> + red_dst[dst_idx++] = red_byte;
>>>>>> + red_byte = 0;
>>>>>> + bit_pos = 0;
>>>>>> + }
>>>>>> + }
>>>>>> + if (bit_pos > 0) {
>>>>>> + red_dst[dst_idx++] = red_byte;
>>>>>> + red_byte = 0;
>>>>>> + bit_pos = 0;
>>>>>> + }
>>>>>> + }
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * Convert framebuffer to 1-bit monochrome for e-paper display.
>>>>>> + *
>>>>>> + * Supported formats: XRGB8888, RGB888, RGB565, R8, NV12, NV16,
>>>>>> YUYV, UYVY, R1.
>>>>>> + * For colour and luma formats, Otsu's global binarisation method
>>>>>> computes an
>>>>>> + * optimal per-image threshold from the luminance histogram.
>>>>>> + * R1 is the controller's native format and bypasses conversion
>>>>>> entirely.
>>>>>> + *
>>>>>> + * Output layout:
>>>>>> + * 0°/180° landscape: row-major, left-to-right, top-to-bottom
>>>>>> + * 90°/270° CW portrait: column-major, rightmost column first
>>>>>> + */
>>>>>> +static void ssd16xx_convert_fb_to_1bpp(u8 *dst, struct iosys_map
>>>>>> *src,
>>>>>> + struct drm_framebuffer *fb,
>>>>>> + struct drm_rect *rect,
>>>>>> + unsigned int orientation)
>>>>>> +{
>>>>>> + u32 format = fb->format->format;
>>>>>> + int x, y;
>>>>>> + u8 byte = 0;
>>>>>> + unsigned int bit_pos = 0;
>>>>>> + unsigned int dst_idx = 0;
>>>>>> +
>>>>>> + /* Use fixed threshold of 127 for grayscale to monochrome
>>>>>> conversion. */
>>>>>> + drm_dbg(fb->dev,
>>>>>> + "convert_1bpp: fmt=%p4cc rect=(%d,%d)-(%d,%d) orient=%u°
>>>>>> path=%s\n",
>>>>>> + &fb->format->format,
>>>>>> + rect->x1, rect->y1, rect->x2, rect->y2,
>>>>>> + orientation,
>>>>>> + (format == DRM_FORMAT_R1 && orientation == 0 && rect->x1
>>>>>> % 8 == 0) ? "R1-fast" :
>>>>>> + (orientation == 90 || orientation == 270) ? "portrait" :
>>>>>> "landscape");
>>>>>> +
>>>>>> + /*
>>>>>> + * R1 fast path: 0° landscape with byte-aligned rect.
>>>>>> + * R1 is already 1bpp so landscape rows map directly to
>>>>>> output bytes via
>>>>>> + * memcpy — no per-pixel computation needed. rect->x1 must be a
>>>>>> + * multiple of 8 so that (rect->x1 / 8) gives the correct
>>>>>> byte offset;
>>>>>> + * if not, the generic pixel-by-pixel loop below handles non-
>>>>>> aligned
>>>>>> + * rects safely.
>>>>>> + */
>>>>>> + if (format == DRM_FORMAT_R1 && orientation == 0 && rect->x1 %
>>>>>> 8 == 0) {
>>>>>> + unsigned int src_pitch = fb->pitches[0];
>>>>>> + unsigned int width_bytes = drm_rect_width(rect) / 8;
>>>>>> +
>>>>>> + for (y = rect->y1; y < rect->y2; y++) {
>>>>>> + u8 *src_line = src->vaddr + y * src_pitch + (rect-
>>>>>> >x1 / 8);
>>>>>> +
>>>>>> + memcpy(dst + dst_idx, src_line, width_bytes);
>>>>>> + dst_idx += width_bytes;
>>>>>> + }
>>>>>> + return;
>>>>>> + }
>>>>>> +
>>>>>> + switch (orientation) {
>>>>>> + case 90:
>>>>>> + case 270:
>>>>>> + /*
>>>>>> + * Portrait (90° or 270°): column-major packing.
>>>>>> + * Each portrait source column becomes one physical RAM row.
>>>>>> + * The data entry mode and cursor position control scan
>>>>>> direction.
>>>>>> + */
>>>>>> + for (x = rect->x2 - 1; x >= (int)rect->x1; x--) {
>>>>>> + for (y = rect->y1; y < rect->y2; y++) {
>>>>>> + if (ssd16xx_pixel_is_white(src, fb, x, y))
>>>>>> + byte |= (1 << (7 - bit_pos));
>>>>>> + if (++bit_pos == 8) {
>>>>>> + dst[dst_idx++] = byte;
>>>>>> + byte = 0;
>>>>>> + bit_pos = 0;
>>>>>> + }
>>>>>> + }
>>>>>> + if (bit_pos > 0) {
>>>>>> + dst[dst_idx++] = byte;
>>>>>> + byte = 0;
>>>>>> + bit_pos = 0;
>>>>>> + }
>>>>>> + }
>>>>>> + break;
>>>>>> +
>>>>>> + case 0:
>>>>>> + case 180:
>>>>>> + default:
>>>>>> + /*
>>>>>> + * Landscape (0° or 180°): row-major packing.
>>>>>> + * Each landscape source row becomes one physical RAM row.
>>>>>> + * The data entry mode and cursor position control scan
>>>>>> direction.
>>>>>> + */
>>>>>> + for (y = rect->y1; y < rect->y2; y++) {
>>>>>> + for (x = rect->x1; x < rect->x2; x++) {
>>>>>> + if (ssd16xx_pixel_is_white(src, fb, x, y))
>>>>>> + byte |= (1 << (7 - bit_pos));
>>>>>> + if (++bit_pos == 8) {
>>>>>> + dst[dst_idx++] = byte;
>>>>>> + byte = 0;
>>>>>> + bit_pos = 0;
>>>>>> + }
>>>>>> + }
>>>>>> + if (bit_pos > 0) {
>>>>>> + dst[dst_idx++] = byte;
>>>>>> + byte = 0;
>>>>>> + bit_pos = 0;
>>>>>> + }
>>>>>> + }
>>>>>> + break;
>>>>>> + }
>>>>>> +}
>>>>>> +
>>>>>> +static int ssd16xx_fb_dirty(struct drm_framebuffer *fb, struct
>>>>>> drm_rect *rect,
>>>>>> + struct ssd16xx_panel *panel)
>>>>>> +{
>>>>>> + const u8 *ctrl2_tbl = panel->controller_cfg->ctrl2_refresh;
>>>>>> + struct drm_gem_dma_object *dma_obj =
>>>>>> drm_fb_dma_get_gem_obj(fb, 0);
>>>>>> + struct iosys_map map;
>>>>>> + int err = 0;
>>>>>> + unsigned int data_size = (panel->width * panel->height) / 8;
>>>>>> + u8 *mono_buffer = NULL;
>>>>>> + u8 *red_buffer = NULL;
>>>>>> + u16 ram_x_start, ram_x_end, ram_y_start, ram_y_end;
>>>>>> +
>>>>>> + /* Process full display area; convert handles orientation
>>>>>> traversal. */
>>>>>> + rect->x1 = 0;
>>>>>> + rect->y1 = 0;
>>>>>> + rect->x2 = panel->width;
>>>>>> + rect->y2 = panel->height;
>>>>>> +
>>>>>> + drm_dbg(&panel->drm,
>>>>>> + "fb_dirty: fb=%dx%d, refresh_mode=%d, orientation=%d\n",
>>>>>> + fb->width, fb->height, panel->refresh_mode, panel-
>>>>>> >orientation);
>>>>>> +
>>>>>> + mono_buffer = panel->tx_buf;
>>>>>> + memset(mono_buffer, 0, data_size);
>>>>>> +
>>>>>> + /* 3-colour FULL/FAST: populate red channel. */
>>>>>> + if (panel->color_mode == SSD16XX_COLOR_MODE_3COLOR &&
>>>>>> + (panel->refresh_mode == SSD16XX_REFRESH_FULL ||
>>>>>> + panel->refresh_mode == SSD16XX_REFRESH_FAST)) {
>>>>>> + red_buffer = panel->tx_red_buf;
>>>>>> + memset(red_buffer, 0, data_size);
>>>>>> + }
>>>>>> +
>>>>>> + iosys_map_set_vaddr(&map, dma_obj->vaddr);
>>>>>> +
>>>>>> + if (red_buffer && fb->format->format == DRM_FORMAT_R8)
>>>>>> + ssd16xx_convert_r8_to_red_only(mono_buffer, red_buffer,
>>>>>> &map, fb, rect);
>>>>>> + else if (red_buffer)
>>>>>> + ssd16xx_convert_fb_to_3color(mono_buffer, red_buffer,
>>>>>> &map, fb, rect);
>>>>>> + else
>>>>>> + ssd16xx_convert_fb_to_1bpp(mono_buffer, &map, fb, rect,
>>>>>> panel->orientation);
>>>>>> +
>>>>>> + drm_dbg(&panel->drm,
>>>>>> + "fb_dirty: mono[0..3]=0x%02x 0x%02x 0x%02x 0x%02x
>>>>>> (data_size=%u)\n",
>>>>>> + mono_buffer[0], mono_buffer[1], mono_buffer[2],
>>>>>> mono_buffer[3],
>>>>>> + data_size);
>>>>>> +
>>>>>> + /* Set RAM window and cursor for current orientation. */
>>>>>> + ram_x_start = 0;
>>>>>> + ram_x_end = (panel->controller_cfg->max_width / 8) - 1;
>>>>>> + ram_y_start = 0;
>>>>>> + ram_y_end = panel->controller_cfg->max_height - 1;
>>>>>> +
>>>>>> + switch (panel->orientation) {
>>>>>> + case 90:
>>>>>> + case 180:
>>>>>> + /* 90°/180°: XDEC_YDEC mode, send end-before-start;
>>>>>> cursor at (max, max). */
>>>>>> + ssd16xx_send_cmd(panel,
>>>>>> SSD16XX_CMD_SET_RAM_X_ADDRESS_START_END, &err);
>>>>>> + ssd16xx_send_x_param(panel, ram_x_end, &err);
>>>>>> + ssd16xx_send_x_param(panel, ram_x_start, &err);
>>>>>> +
>>>>>> + ssd16xx_send_cmd(panel,
>>>>>> SSD16XX_CMD_SET_RAM_Y_ADDRESS_START_END, &err);
>>>>>> + ssd16xx_send_y_param(panel, ram_y_end, &err);
>>>>>> + ssd16xx_send_y_param(panel, ram_y_start, &err);
>>>>>> +
>>>>>> + ssd16xx_send_cmd(panel,
>>>>>> SSD16XX_CMD_SET_RAM_X_ADDRESS_COUNTER, &err);
>>>>>> + ssd16xx_send_x_param(panel, ram_x_end, &err);
>>>>>> +
>>>>>> + ssd16xx_send_cmd(panel,
>>>>>> SSD16XX_CMD_SET_RAM_Y_ADDRESS_COUNTER, &err);
>>>>>> + ssd16xx_send_y_param(panel, ram_y_end, &err);
>>>>>> + break;
>>>>>> +
>>>>>> + default: /* 0°/270° */
>>>>>> + /* 0°/270°: XINC_YINC mode, cursor at (0, 0). */
>>>>>> + ssd16xx_send_cmd(panel,
>>>>>> SSD16XX_CMD_SET_RAM_X_ADDRESS_START_END, &err);
>>>>>> + ssd16xx_send_x_param(panel, ram_x_start, &err);
>>>>>> + ssd16xx_send_x_param(panel, ram_x_end, &err);
>>>>>> +
>>>>>> + ssd16xx_send_cmd(panel,
>>>>>> SSD16XX_CMD_SET_RAM_Y_ADDRESS_START_END, &err);
>>>>>> + ssd16xx_send_y_param(panel, ram_y_start, &err);
>>>>>> + ssd16xx_send_y_param(panel, ram_y_end, &err);
>>>>>> +
>>>>>> + ssd16xx_send_cmd(panel,
>>>>>> SSD16XX_CMD_SET_RAM_X_ADDRESS_COUNTER, &err);
>>>>>> + ssd16xx_send_x_param(panel, ram_x_start, &err);
>>>>>> +
>>>>>> + ssd16xx_send_cmd(panel,
>>>>>> SSD16XX_CMD_SET_RAM_Y_ADDRESS_COUNTER, &err);
>>>>>> + ssd16xx_send_y_param(panel, ram_y_start, &err);
>>>>>> + break;
>>>>>> + }
>>>>>> +
>>>>>> + ssd16xx_send_cmd(panel, SSD16XX_CMD_WRITE_RAM_BW, &err);
>>>>>> + ssd16xx_send_data_bulk(panel, mono_buffer, data_size, &err);
>>>>>> +
>>>>>> + /* Re-send border waveform when: every-update mode, init
>>>>>> frame, or
>>>>>> + * the border_waveform_update property just changed (one-shot).
>>>>>> + */
>>>>>> + drm_dbg(&panel->drm,
>>>>>> + "fb_dirty: border check: every_update=%d init_pending=%d
>>>>>> border_pending=%d idx=%d hw=0x%02x\n",
>>>>>> + panel->border_refresh_on_every_update, panel-
>>>>>> >init_refresh_pending,
>>>>>> + panel->border_waveform_pending, panel-
>>>>>> >border_waveform_update_idx,
>>>>>> + panel->controller_cfg->border_waveform_table[panel-
>>>>>> >border_waveform_update_idx]);
>>>>>> + if (panel->border_refresh_on_every_update || panel-
>>>>>> >init_refresh_pending ||
>>>>>> + panel->border_waveform_pending) {
>>>>>> + u8 idx = panel->border_waveform_update_idx;
>>>>>> + u8 border = panel->controller_cfg-
>>>>>> >border_waveform_table[idx];
>>>>>> +
>>>>>> + drm_dbg(&panel->drm, "fb_dirty: Sending border waveform:
>>>>>> 0x%02x\n",
>>>>>> + border);
>>>>>> + ssd16xx_send_cmd(panel,
>>>>>> SSD16XX_CMD_BORDER_WAVEFORM_CONTROL, &err);
>>>>>> + ssd16xx_send_data(panel, border, &err);
>>>>>> + panel->border_waveform_pending = false;
>>>>>> + }
>>>>>> +
>>>>>> + switch (panel->refresh_mode) {
>>>>>> + case SSD16XX_REFRESH_FULL:
>>>>>> + /*
>>>>>> + * BW full refresh: write RED RAM BEFORE display_update
>>>>>> + * to avoid a post-BUSY write timing issue on some
>>>>>> + * controller revisions that silently corrupts RED RAM.
>>>>>> + * RED RAM is then bypassed (CTRL1_BYPASS_RED_RAM) so
>>>>>> + * stale RED RAM content does not affect the output.
>>>>>> + */
>>>>>> + ssd16xx_send_cmd(panel, SSD1683_CMD_WRITE_RAM_RED, &err);
>>>>>> + if (red_buffer) {
>>>>>> + /* 3-colour: write red channel before activating */
>>>>>> + ssd16xx_send_data_bulk(panel, red_buffer, data_size,
>>>>>> &err);
>>>>>> + ssd16xx_display_update(panel, panel->controller_cfg-
>>>>>> >ctrl1_normal,
>>>>>> + SSD16XX_CTRL1_BYTE2_DEFAULT,
>>>>>> + ctrl2_tbl[SSD16XX_REFRESH_FULL], &err);
>>>>>> + } else {
>>>>>> + ssd16xx_send_data_bulk(panel, mono_buffer, data_size,
>>>>>> &err);
>>>>>> + ssd16xx_display_update(panel, panel->controller_cfg-
>>>>>> >ctrl1_bypass_red_ram,
>>>>>> + SSD16XX_CTRL1_BYTE2_DEFAULT,
>>>>>> + ctrl2_tbl[SSD16XX_REFRESH_FULL], &err);
>>>>>> + }
>>>>>> + break;
>>>>>> + case SSD16XX_REFRESH_FAST:
>>>>>> + /*
>>>>>> + * Fast refresh: LUT pre-loaded during hw_init;
>>>>>> BYPASS_RED_RAM
>>>>>> + * so RED RAM does not affect the current output.
>>>>>> + * Write RED RAM BEFORE display_update (same reasoning as
>>>>>> FULL)
>>>>>> + * so it holds the just-displayed frame as a valid
>>>>>> reference for
>>>>>> + * any subsequent PARTIAL refresh.
>>>>>> + */
>>>>>> +
>>>>>> + ssd16xx_send_cmd(panel, SSD1683_CMD_WRITE_RAM_RED, &err);
>>>>>> + if (red_buffer) {
>>>>>> + /* 3-colour: write red channel before activating */
>>>>>> + ssd16xx_send_data_bulk(panel, red_buffer, data_size,
>>>>>> &err);
>>>>>> + ssd16xx_display_update(panel, panel->controller_cfg-
>>>>>> >ctrl1_normal,
>>>>>> + SSD16XX_CTRL1_BYTE2_DEFAULT,
>>>>>> + ctrl2_tbl[SSD16XX_REFRESH_FAST], &err);
>>>>>> + } else {
>>>>>> + ssd16xx_send_data_bulk(panel, mono_buffer, data_size,
>>>>>> &err);
>>>>>> + ssd16xx_display_update(panel, panel->controller_cfg-
>>>>>> >ctrl1_bypass_red_ram,
>>>>>> + SSD16XX_CTRL1_BYTE2_DEFAULT,
>>>>>> + ctrl2_tbl[SSD16XX_REFRESH_FAST], &err);
>>>>>> + }
>>>>>> + break;
>>>>>> + case SSD16XX_REFRESH_PARTIAL:
>>>>>> + default:
>>>>>> + /*
>>>>>> + * Partial refresh: both RAMs used for transition waveforms.
>>>>>> + * RED RAM must hold the PREVIOUS frame (= current display
>>>>>> + * content) so the controller can compute pixel transitions.
>>>>>> + * Write RED RAM AFTER display_update so it captures the
>>>>>> + * just-displayed frame as the reference for the next
>>>>>> partial.
>>>>>> + */
>>>>>> + drm_dbg(&panel->drm,
>>>>>> + "fb_dirty: partial pre-update: mono[0]=0x%02x
>>>>>> (BW=new, RED=prev)\n",
>>>>>> + mono_buffer[0]);
>>>>>> + ssd16xx_display_update(panel, panel->controller_cfg-
>>>>>> >ctrl1_normal,
>>>>>> + SSD16XX_CTRL1_BYTE2_DEFAULT,
>>>>>> + ctrl2_tbl[SSD16XX_REFRESH_PARTIAL], &err);
>>>>>> + ssd16xx_send_cmd(panel, SSD1683_CMD_WRITE_RAM_RED, &err);
>>>>>> + ssd16xx_send_data_bulk(panel, mono_buffer, data_size, &err);
>>>>>> + drm_dbg(&panel->drm,
>>>>>> + "fb_dirty: partial post-update: wrote RED baseline
>>>>>> mono[0]=0x%02x\n",
>>>>>> + mono_buffer[0]);
>>>>>> + break;
>>>>>> + }
>>>>>> +
>>>>>> + return err;
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> -----------------------------------------------------------------------------
>>>>>> + * Plane Functions
>>>>>> + */
>>>>>> +
>>>>>> +static void ssd16xx_plane_destroy(struct drm_plane *plane)
>>>>>> +{
>>>>>> + drm_plane_cleanup(plane);
>>>>>> +}
>>>>>> +
>>>>>> +static void ssd16xx_plane_reset(struct drm_plane *plane)
>>>>>> +{
>>>>>> + drm_atomic_helper_plane_reset(plane);
>>>>>> +}
>>>>>
>>>>> Please avoid these wrappers.
>>>>>
>>>>
>>>> Understood, will update in V2.
>>>>
>>>>>> +
>>>>>> +static const struct drm_plane_funcs ssd16xx_plane_funcs = {
>>>>>> + .update_plane = drm_atomic_helper_update_plane,
>>>>>> + .disable_plane = drm_atomic_helper_disable_plane,
>>>>>> + .destroy = ssd16xx_plane_destroy,
>>>>>> + .reset = ssd16xx_plane_reset,
>>>>>> + .atomic_duplicate_state =
>>>>>> drm_atomic_helper_plane_duplicate_state,
>>>>>> + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
>>>>>> +};
>>>>>> +
>>>>>> +static int ssd16xx_plane_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_state *crtc_state;
>>>>>> +
>>>>>> + if (!new_plane_state->crtc)
>>>>>> + return 0;
>>>>>> +
>>>>>> + crtc_state = drm_atomic_get_new_crtc_state(state,
>>>>>> new_plane_state->crtc);
>>>>>> +
>>>>>> + return drm_atomic_helper_check_plane_state(new_plane_state,
>>>>>> crtc_state,
>>>>>> + DRM_PLANE_NO_SCALING,
>>>>>> + DRM_PLANE_NO_SCALING,
>>>>>> + false, false);
>>>>>> +}
>>>>>> +
>>>>>> +static void ssd16xx_plane_atomic_update(struct drm_plane *plane,
>>>>>> + struct drm_atomic_state *state)
>>>>>> +{
>>>>>> + struct drm_plane_state *old_state =
>>>>>> drm_atomic_get_old_plane_state(state, plane);
>>>>>> + struct drm_plane_state *new_state =
>>>>>> drm_atomic_get_new_plane_state(state, plane);
>>>>>> + struct ssd16xx_panel *panel = plane_to_ssd16xx_panel(plane);
>>>>>> + enum ssd16xx_refresh_mode saved_mode;
>>>>>> + u8 saved_border_waveform_idx;
>>>>>> + struct drm_framebuffer *fb = new_state->fb;
>>>>>> + struct drm_rect rect;
>>>>>> + int ret;
>>>>>> +
>>>>>> + drm_dbg(&panel->drm, "plane_atomic_update: fb=%p,
>>>>>> initialized=%d\n",
>>>>>> + fb, panel->initialized);
>>>>>> +
>>>>>> + if (!fb || !panel->initialized)
>>>>>> + return;
>>>>>> +
>>>>>> + /*
>>>>>> + * If a rotation change is pending, skip the update here —
>>>>>> crtc_atomic_flush
>>>>>> + * will re-init the hardware for the new orientation and redraw.
>>>>>> + */
>>>>>> + if (panel->reinit_pending) {
>>>>>> + drm_dbg(&panel->drm, "plane_atomic_update: skipping
>>>>>> (reinit pending)\n");
>>>>>> + return;
>>>>>> + }
>>>>>> +
>>>>>> + if (!drm_atomic_helper_damage_merged(old_state, new_state,
>>>>>> &rect)) {
>>>>>> + rect.x1 = 0;
>>>>>> + rect.y1 = 0;
>>>>>> + rect.x2 = fb->width;
>>>>>> + rect.y2 = fb->height;
>>>>>> + drm_dbg(&panel->drm, "plane_atomic_update: no damage,
>>>>>> using full screen\n");
>>>>>> + }
>>>>>> +
>>>>>> + drm_dbg(&panel->drm, "plane_atomic_update: calling fb_dirty
>>>>>> rect=(%d,%d)-(%d,%d)\n",
>>>>>> + rect.x1, rect.y1, rect.x2, rect.y2);
>>>>>> + /*
>>>>>> + * When refresh_mode_init was set, use the specified mode for
>>>>>> this first
>>>>>> + * frame only, then restore the user-configured refresh_mode so
>>>>>> + * subsequent updates continue with the configured mode.
>>>>>> + */
>>>>>> + saved_mode = panel->refresh_mode;
>>>>>> + saved_border_waveform_idx = panel->border_waveform_update_idx;
>>>>>> + if (panel->init_refresh_pending) {
>>>>>> + panel->refresh_mode = panel->refresh_mode_init;
>>>>>> + panel->border_waveform_update_idx = panel-
>>>>>> >border_waveform_init_idx;
>>>>>> + }
>>>>>> +
>>>>>> + /*
>>>>>> + * Fast refresh (0xC7) omits LOAD_LUT on every update cycle
>>>>>> and relies
>>>>>> + * on the LUT being pre-loaded upfront. The property setter
>>>>>> arms
>>>>>> + * fast_lut_pending whenever the user switches into fast
>>>>>> mode. Consume
>>>>>> + * the flag here (once) before the first fast-refresh frame
>>>>>> so the
>>>>>> + * controller's LUT is in the correct state.
>>>>>> + */
>>>>>> + if (panel->fast_lut_pending) {
>>>>>> + ret = ssd16xx_preload_fast_lut(panel);
>>>>>> + if (ret) {
>>>>>> + drm_err(&panel->drm,
>>>>>> + "plane_atomic_update: fast LUT preload failed:
>>>>>> %d\n", ret);
>>>>>> + }
>>>>>> +
>>>>>> + panel->fast_lut_pending = false;
>>>>>> + }
>>>>>> +
>>>>>> + ret = ssd16xx_fb_dirty(fb, &rect, panel);
>>>>>> + if (ret)
>>>>>> + drm_err(&panel->drm, "plane_atomic_update: display update
>>>>>> failed: %d\n", ret);
>>>>>> + else
>>>>>> + panel->last_fb = fb;
>>>>>> +
>>>>>> + panel->refresh_mode = saved_mode;
>>>>>> + panel->border_waveform_update_idx = saved_border_waveform_idx;
>>>>>> +
>>>>>> + /*
>>>>>> + * If this was the init frame (which used
>>>>>> border_waveform_init_idx
>>>>>> + * inside fb_dirty), arm border_waveform_pending so the normal
>>>>>> + * (non-init) border value is sent at the start of the next
>>>>>> update.
>>>>>> + */
>>>>>> + if (panel->init_refresh_pending) {
>>>>>> + panel->init_refresh_pending = false;
>>>>>> + panel->border_waveform_pending = true;
>>>>>> + }
>>>>>> +}
>>>>>> +
>>>>>> +static const struct drm_plane_helper_funcs
>>>>>> ssd16xx_plane_helper_funcs = {
>>>>>> + .atomic_check = ssd16xx_plane_atomic_check,
>>>>>> + .atomic_update = ssd16xx_plane_atomic_update,
>>>>>> +};
>>>>>> +
>>>>>> +/*
>>>>>> -----------------------------------------------------------------------------
>>>>>> + * CRTC Functions
>>>>>> + */
>>>>>> +
>>>>>> +static void ssd16xx_crtc_destroy(struct drm_crtc *crtc)
>>>>>> +{
>>>>>> + drm_crtc_cleanup(crtc);
>>>>>> +}
>>>>>> +
>>>>>> +static const struct drm_crtc_funcs ssd16xx_crtc_funcs = {
>>>>>> + .reset = drm_atomic_helper_crtc_reset,
>>>>>> + .destroy = ssd16xx_crtc_destroy,
>>>>>> + .set_config = drm_atomic_helper_set_config,
>>>>>> + .page_flip = drm_atomic_helper_page_flip,
>>>>>> + .atomic_duplicate_state =
>>>>>> drm_atomic_helper_crtc_duplicate_state,
>>>>>> + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
>>>>>> +};
>>>>>> +
>>>>>> +static enum drm_mode_status ssd16xx_crtc_mode_valid(struct
>>>>>> drm_crtc *crtc,
>>>>>> + const struct drm_display_mode *mode)
>>>>>> +{
>>>>>> + struct ssd16xx_panel *panel = crtc_to_ssd16xx_panel(crtc);
>>>>>> +
>>>>>> + /* Accept only our panel's native mode (landscape or
>>>>>> portrait) */
>>>>>> + if ((mode->hdisplay == panel->mode->hdisplay &&
>>>>>> + mode->vdisplay == panel->mode->vdisplay) ||
>>>>>> + (mode->hdisplay == panel->mode->vdisplay &&
>>>>>> + mode->vdisplay == panel->mode->hdisplay))
>>>>>> + return MODE_OK;
>>>>>> +
>>>>>> + return MODE_BAD;
>>>>>> +}
>>>>>> +
>>>>>> +static int ssd16xx_crtc_atomic_check(struct drm_crtc *crtc,
>>>>>> + struct drm_atomic_state *state)
>>>>>> +{
>>>>>> + return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static void ssd16xx_crtc_atomic_disable(struct drm_crtc *crtc,
>>>>>> + struct drm_atomic_state *state)
>>>>>> +{
>>>>>> + struct ssd16xx_panel *panel = crtc_to_ssd16xx_panel(crtc);
>>>>>> + int ret, idx;
>>>>>> +
>>>>>> + if (!drm_dev_enter(&panel->drm, &idx))
>>>>>> + return;
>>>>>> +
>>>>>> + if (panel->clear_on_disable < 0 || panel-
>>>>>> >display_cleared_on_deinit)
>>>>>> + goto out;
>>>>>> +
>>>>>> + drm_dbg(&panel->drm, "clear_on_disable: running, mode=%d\n",
>>>>>> + panel->clear_on_disable);
>>>>>> + ret = ssd16xx_clear_display(panel,
>>>>>> + ssd16xx_refresh_mode_to_ctrl2(panel,
>>>>>> + panel->clear_on_disable));
>>>>>> + if (ret) {
>>>>>> + drm_err(&panel->drm, "atomic_disable: clear failed:
>>>>>> %d\n", ret);
>>>>>> + goto out;
>>>>>> + }
>>>>>> +
>>>>>> + panel->display_cleared_on_deinit = true;
>>>>>> +out:
>>>>>> + drm_dev_exit(idx);
>>>>>> +}
>>>>>> +
>>>>>> +static void ssd16xx_crtc_atomic_enable(struct drm_crtc *crtc,
>>>>>> + struct drm_atomic_state *state)
>>>>>> +{
>>>>>> + struct ssd16xx_panel *panel = crtc_to_ssd16xx_panel(crtc);
>>>>>> + struct drm_crtc_state *crtc_state =
>>>>>> drm_atomic_get_new_crtc_state(state, crtc);
>>>>>> + int ret, idx;
>>>>>> +
>>>>>> + if (!drm_dev_enter(&panel->drm, &idx))
>>>>>> + return;
>>>>>> +
>>>>>> + panel->display_cleared_on_deinit = false;
>>>>>> +
>>>>>> + drm_dbg(&panel->drm, "atomic_enable: %dx%d\n",
>>>>>> + crtc_state->mode.hdisplay, crtc_state->mode.vdisplay);
>>>>>> +
>>>>>> + panel->width = crtc_state->mode.hdisplay;
>>>>>> + panel->height = crtc_state->mode.vdisplay;
>>>>>> +
>>>>>> + ret = ssd16xx_hw_init(panel);
>>>>>> + if (ret) {
>>>>>> + drm_err(&panel->drm, "crtc_atomic_enable: HW init failed:
>>>>>> %d\n", ret);
>>>>>> + goto out;
>>>>>> + }
>>>>>> + panel->initialized = true;
>>>>>> +
>>>>>> + /* Clear display on first app launch if configured */
>>>>>> + ret = ssd16xx_clear_display_on_init(panel);
>>>>>> + if (ret)
>>>>>> + drm_err(&panel->drm, "crtc_atomic_enable: clear on init
>>>>>> failed: %d\n", ret);
>>>>>> +
>>>>>> + /*
>>>>>> + * If refresh_mode_init is set, arm init_refresh_pending so
>>>>>> + * plane_atomic_update uses the specified mode for the first
>>>>>> frame
>>>>>> + * then restores the user-configured or panel default
>>>>>> refresh_mode.
>>>>>> + */
>>>>>> + if (panel->refresh_mode_init >= 0) {
>>>>>> + drm_dbg(&panel->drm,
>>>>>> + "atomic_enable: refresh_mode_init=%d, using for first
>>>>>> frame\n",
>>>>>> + panel->refresh_mode_init);
>>>>>> + panel->init_refresh_pending = true;
>>>>>> + }
>>>>>> +
>>>>>> +out:
>>>>>> + drm_dev_exit(idx);
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * Re-initialize hardware and redraw the current framebuffer when
>>>>>> the
>>>>>> + * display orientation changes at runtime via the rotation
>>>>>> connector property.
>>>>>> + * Called by the DRM atomic helper after atomic_enable/disable
>>>>>> have run.
>>>>>> + */
>>>>>> +static void ssd16xx_crtc_atomic_flush(struct drm_crtc *crtc,
>>>>>> + struct drm_atomic_state *state)
>>>>>> +{
>>>>>> + struct ssd16xx_panel *panel = crtc_to_ssd16xx_panel(crtc);
>>>>>> + struct drm_framebuffer *fb;
>>>>>> + struct drm_rect full;
>>>>>> + int ret, idx;
>>>>>> +
>>>>>> + if (!panel->reinit_pending || !panel->initialized)
>>>>>> + return;
>>>>>> +
>>>>>> + if (!drm_dev_enter(&panel->drm, &idx))
>>>>>> + return;
>>>>>> +
>>>>>> + panel->reinit_pending = false;
>>>>>> +
>>>>>> + drm_dbg(&panel->drm, "atomic_flush: reinit, orientation=%u°\n",
>>>>>> + panel->orientation);
>>>>>> +
>>>>>> + ret = ssd16xx_hw_init(panel);
>>>>>> + if (ret) {
>>>>>> + drm_err(&panel->drm, "Orientation re-init failed: %d\n",
>>>>>> ret);
>>>>>> + goto out;
>>>>>> + }
>>>>>> +
>>>>>> + fb = panel->primary_plane.state ? panel->primary_plane.state->fb
>>>>>> + : panel->last_fb;
>>>>>> + if (fb) {
>>>>>> + full.x1 = 0;
>>>>>> + full.y1 = 0;
>>>>>> + full.x2 = fb->width;
>>>>>> + full.y2 = fb->height;
>>>>>> + ret = ssd16xx_fb_dirty(fb, &full, panel);
>>>>>> + if (ret)
>>>>>> + drm_err(&panel->drm, "atomic_flush: display update
>>>>>> failed: %d\n", ret);
>>>>>> + else
>>>>>> + panel->last_fb = fb;
>>>>>> + }
>>>>>> +
>>>>>> +out:
>>>>>> + drm_dev_exit(idx);
>>>>>> +}
>>>>>> +
>>>>>> +static const struct drm_crtc_helper_funcs
>>>>>> ssd16xx_crtc_helper_funcs = {
>>>>>> + .mode_valid = ssd16xx_crtc_mode_valid,
>>>>>> + .atomic_check = ssd16xx_crtc_atomic_check,
>>>>>> + .atomic_disable = ssd16xx_crtc_atomic_disable,
>>>>>> + .atomic_enable = ssd16xx_crtc_atomic_enable,
>>>>>> + .atomic_flush = ssd16xx_crtc_atomic_flush,
>>>>>> +};
>>>>>> +
>>>>>> +/*
>>>>>> -----------------------------------------------------------------------------
>>>>>> + * Connector Functions
>>>>>> + */
>>>>>> +
>>>>>> +static int ssd16xx_connector_get_modes(struct drm_connector
>>>>>> *connector)
>>>>>> +{
>>>>>> + struct ssd16xx_panel *panel = to_ssd16xx_panel(connector->dev);
>>>>>> + bool mode_is_portrait = (panel->mode->hdisplay < panel->mode-
>>>>>> >vdisplay);
>>>>>> + bool orient_is_portrait = (panel->orientation == 90 || panel-
>>>>>> >orientation == 270);
>>>>>> +
>>>>>> + drm_dbg(&panel->drm, "connector_get_modes: orientation=%u°\n",
>>>>>> + panel->orientation);
>>>>>> +
>>>>>> + /* For portrait, swap dimensions so clients see logical size. */
>>>>>> + if (mode_is_portrait != orient_is_portrait) {
>>>>>> + struct drm_display_mode *mode;
>>>>>> +
>>>>>> + mode = drm_mode_duplicate(&panel->drm, panel->mode);
>>>>>> + if (!mode)
>>>>>> + return 0;
>>>>>> + swap(mode->hdisplay, mode->vdisplay);
>>>>>> + swap(mode->hsync_start, mode->vsync_start);
>>>>>> + swap(mode->hsync_end, mode->vsync_end);
>>>>>> + swap(mode->htotal, mode->vtotal);
>>>>>> + swap(mode->width_mm, mode->height_mm);
>>>>>> + mode->type |= DRM_MODE_TYPE_PREFERRED;
>>>>>> + drm_mode_set_name(mode);
>>>>>> + drm_mode_probed_add(connector, mode);
>>>>>> + return 1;
>>>>>> + }
>>>>>> +
>>>>>> + return drm_connector_helper_get_modes_fixed(connector, panel-
>>>>>> >mode);
>>>>>> +}
>>>>>> +
>>>>>> +static const struct drm_connector_helper_funcs
>>>>>> ssd16xx_connector_helper_funcs = {
>>>>>> + .get_modes = ssd16xx_connector_get_modes,
>>>>>> +};
>>>>>> +
>>>>>> +/* Enum values for the rotation connector property (degrees
>>>>>> clockwise) */
>>>>>> +static const struct drm_prop_enum_list ssd16xx_rotation_enum[] = {
>>>>>> + { 0, "0" },
>>>>>> + { 90, "90" },
>>>>>> + { 180, "180" },
>>>>>> + { 270, "270" },
>>>>>> +};
>>>>>> +
>>>>>> +/* Enum values for the refresh_mode connector property */
>>>>>> +static const struct drm_prop_enum_list
>>>>>> ssd16xx_refresh_mode_enum[] = {
>>>>>> + { SSD16XX_REFRESH_PARTIAL, "partial" },
>>>>>> + { SSD16XX_REFRESH_FULL, "full" },
>>>>>> + { SSD16XX_REFRESH_FAST, "fast" },
>>>>>> +};
>>>>>> +
>>>>>> +/*
>>>>>> + * Enum for clear_on_init, clear_on_close, refresh_mode_init
>>>>>> properties.
>>>>>> + * Value 0 = disabled; values 1-3 = partial/full/fast (refresh
>>>>>> mode + 1).
>>>>>> + * The +1 offset allows a single enum to represent both
>>>>>> "disabled" and the
>>>>>> + * three refresh modes without sign-extending the DRM property
>>>>>> value.
>>>>>> + */
>>>>>> +static const struct drm_prop_enum_list
>>>>>> ssd16xx_init_refresh_enum[] = {
>>>>>> + { 0, "disabled" },
>>>>>> + { 1, "partial" },
>>>>>> + { 2, "full" },
>>>>>> + { 3, "fast" },
>>>>>> +};
>>>>>> +
>>>>>> +/* Enum values for the color_mode connector property */
>>>>>> +static const struct drm_prop_enum_list ssd16xx_color_mode_enum[] = {
>>>>>> + { SSD16XX_COLOR_MODE_BW, "black-white" },
>>>>>> + { SSD16XX_COLOR_MODE_3COLOR, "3-color" },
>>>>>> +};
>>>>>> +
>>>>>> +/* Enum values for border_waveform connector properties (one per
>>>>>> HW mode) */
>>>>>> +static const struct drm_prop_enum_list
>>>>>> ssd16xx_border_waveform_enum[] = {
>>>>>> + { SSD16XX_BORDER_LUT0, "lut0_black" },
>>>>>> + { SSD16XX_BORDER_LUT1, "lut1_white" },
>>>>>> + { SSD16XX_BORDER_LUT2, "lut2_black" },
>>>>>> + { SSD16XX_BORDER_LUT3, "lut3_gray" },
>>>>>> + { SSD16XX_BORDER_VSS, "vss_black" },
>>>>>> + { SSD16XX_BORDER_VSH1, "vsh1_black" },
>>>>>> + { SSD16XX_BORDER_VSL, "vsl_white" },
>>>>>> + { SSD16XX_BORDER_VSH2, "vsh2_black" },
>>>>>> + { SSD16XX_BORDER_VCOM, "vcom_preserve" },
>>>>>> + { SSD16XX_BORDER_HIZ, "hiz_float" },
>>>>>> +};
>>>>>> +
>>>>>> +static int ssd16xx_connector_create_properties(struct
>>>>>> ssd16xx_panel *panel)
>>>>>> +{
>>>>>> + struct drm_device *drm = &panel->drm;
>>>>>> + struct drm_connector *connector = &panel->connector;
>>>>>> +
>>>>>> + panel->rotation_property =
>>>>>> + drm_property_create_enum(drm, 0, "rotation",
>>>>>> + ssd16xx_rotation_enum,
>>>>>> + ARRAY_SIZE(ssd16xx_rotation_enum));
>>>>>> + if (!panel->rotation_property)
>>>>>> + return -ENOMEM;
>>>>>> + drm_object_attach_property(&connector->base,
>>>>>> + panel->rotation_property, panel->orientation);
>>>>>> +
>>>>>> + panel->refresh_mode_property =
>>>>>> + drm_property_create_enum(drm, 0, "refresh_mode",
>>>>>> + ssd16xx_refresh_mode_enum,
>>>>>> + ARRAY_SIZE(ssd16xx_refresh_mode_enum));
>>>>>> + if (!panel->refresh_mode_property)
>>>>>> + return -ENOMEM;
>>>>>> + drm_object_attach_property(&connector->base,
>>>>>> + panel->refresh_mode_property, panel-
>>>>>> >refresh_mode);
>>>>>> +
>>>>>> + panel->border_waveform_init_property =
>>>>>> + drm_property_create_enum(drm, 0, "border_waveform_init",
>>>>>> + ssd16xx_border_waveform_enum,
>>>>>> + ARRAY_SIZE(ssd16xx_border_waveform_enum));
>>>>>> + if (!panel->border_waveform_init_property)
>>>>>> + return -ENOMEM;
>>>>>> + drm_object_attach_property(&connector->base,
>>>>>> + panel->border_waveform_init_property,
>>>>>> + panel->border_waveform_init_idx);
>>>>>> +
>>>>>> + panel->border_waveform_update_property =
>>>>>> + drm_property_create_enum(drm, 0, "border_waveform_update",
>>>>>> + ssd16xx_border_waveform_enum,
>>>>>> + ARRAY_SIZE(ssd16xx_border_waveform_enum));
>>>>>> + if (!panel->border_waveform_update_property)
>>>>>> + return -ENOMEM;
>>>>>> + drm_object_attach_property(&connector->base,
>>>>>> + panel->border_waveform_update_property,
>>>>>> + panel->border_waveform_update_idx);
>>>>>> +
>>>>>> + panel->border_refresh_on_every_update_property =
>>>>>> + drm_property_create_bool(drm, 0,
>>>>>> "border_refresh_on_every_update");
>>>>>> + if (!panel->border_refresh_on_every_update_property)
>>>>>> + return -ENOMEM;
>>>>>> + drm_object_attach_property(&connector->base,
>>>>>> + panel->border_refresh_on_every_update_property,
>>>>>> + panel->border_refresh_on_every_update);
>>>>>> +
>>>>>> + panel->clear_on_init_property =
>>>>>> + drm_property_create_enum(drm, 0, "clear_on_init",
>>>>>> + ssd16xx_init_refresh_enum,
>>>>>> + ARRAY_SIZE(ssd16xx_init_refresh_enum));
>>>>>> + if (!panel->clear_on_init_property)
>>>>>> + return -ENOMEM;
>>>>>> + /* Property value 0=disabled, 1-3=mode; field is -1/0/1/2 →
>>>>>> val = field+1 */
>>>>>> + drm_object_attach_property(&connector->base,
>>>>>> + panel->clear_on_init_property,
>>>>>> + panel->clear_on_init + 1);
>>>>>> +
>>>>>> + panel->clear_on_close_property =
>>>>>> + drm_property_create_enum(drm, 0, "clear_on_close",
>>>>>> + ssd16xx_init_refresh_enum,
>>>>>> + ARRAY_SIZE(ssd16xx_init_refresh_enum));
>>>>>> + if (!panel->clear_on_close_property)
>>>>>> + return -ENOMEM;
>>>>>> + drm_object_attach_property(&connector->base,
>>>>>> + panel->clear_on_close_property,
>>>>>> + panel->clear_on_close + 1);
>>>>>> +
>>>>>> + panel->clear_on_disable_property =
>>>>>> + drm_property_create_enum(drm, 0, "clear_on_disable",
>>>>>> + ssd16xx_init_refresh_enum,
>>>>>> + ARRAY_SIZE(ssd16xx_init_refresh_enum));
>>>>>> + if (!panel->clear_on_disable_property)
>>>>>> + return -ENOMEM;
>>>>>> + drm_object_attach_property(&connector->base,
>>>>>> + panel->clear_on_disable_property,
>>>>>> + panel->clear_on_disable + 1);
>>>>>> +
>>>>>> + panel->refresh_mode_init_property =
>>>>>> + drm_property_create_enum(drm, 0, "refresh_mode_init",
>>>>>> + ssd16xx_init_refresh_enum,
>>>>>> + ARRAY_SIZE(ssd16xx_init_refresh_enum));
>>>>>> + if (!panel->refresh_mode_init_property)
>>>>>> + return -ENOMEM;
>>>>>> + drm_object_attach_property(&connector->base,
>>>>>> + panel->refresh_mode_init_property,
>>>>>> + panel->refresh_mode_init + 1);
>>>>>> +
>>>>>> + /*
>>>>>> + * color_mode: only expose 3-color option on panels that
>>>>>> physically have
>>>>>> + * a red plane; on BW-only panels the property still exists for
>>>>>> + * consistency but userspace can only set "black-white".
>>>>>> + */
>>>>>> + panel->color_mode_property =
>>>>>> + drm_property_create_enum(drm, 0, "color_mode",
>>>>>> + ssd16xx_color_mode_enum,
>>>>>> + panel->panel_cfg->red_supported
>>>>>> + ? ARRAY_SIZE(ssd16xx_color_mode_enum)
>>>>>> + : 1);
>>>>>> + if (!panel->color_mode_property)
>>>>>> + return -ENOMEM;
>>>>>> + drm_object_attach_property(&connector->base,
>>>>>> + panel->color_mode_property,
>>>>>> + panel->color_mode);
>>>>>> +
>>>>>> + return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static int ssd16xx_connector_atomic_get_property(struct
>>>>>> drm_connector *connector,
>>>>>> + const struct drm_connector_state *state,
>>>>>> + struct drm_property *property,
>>>>>> + uint64_t *val)
>>>>>> +{
>>>>>> + struct ssd16xx_panel *panel = to_ssd16xx_panel(connector->dev);
>>>>>> +
>>>>>> + drm_dbg(&panel->drm, "get_property: %s\n", property->name);
>>>>>> +
>>>>>> + if (property == panel->rotation_property) {
>>>>>> + *val = panel->orientation;
>>>>>> + return 0;
>>>>>> + }
>>>>>> + if (property == panel->refresh_mode_property) {
>>>>>> + *val = panel->refresh_mode;
>>>>>> + return 0;
>>>>>> + }
>>>>>> + if (property == panel->border_waveform_init_property) {
>>>>>> + *val = panel->border_waveform_init_idx;
>>>>>> + return 0;
>>>>>> + }
>>>>>> + if (property == panel->border_waveform_update_property) {
>>>>>> + *val = panel->border_waveform_update_idx;
>>>>>> + return 0;
>>>>>> + }
>>>>>> + if (property == panel-
>>>>>> >border_refresh_on_every_update_property) {
>>>>>> + *val = panel->border_refresh_on_every_update;
>>>>>> + return 0;
>>>>>> + }
>>>>>> + if (property == panel->clear_on_init_property) {
>>>>>> + *val = panel->clear_on_init + 1; /* field -1/0/1/2 → val
>>>>>> 0/1/2/3 */
>>>>>> + return 0;
>>>>>> + }
>>>>>> + if (property == panel->clear_on_close_property) {
>>>>>> + *val = panel->clear_on_close + 1;
>>>>>> + return 0;
>>>>>> + }
>>>>>> + if (property == panel->clear_on_disable_property) {
>>>>>> + *val = panel->clear_on_disable + 1;
>>>>>> + return 0;
>>>>>> + }
>>>>>> + if (property == panel->refresh_mode_init_property) {
>>>>>> + *val = panel->refresh_mode_init + 1;
>>>>>> + return 0;
>>>>>> + }
>>>>>> + if (property == panel->color_mode_property) {
>>>>>> + *val = panel->color_mode;
>>>>>> + return 0;
>>>>>> + }
>>>>>> +
>>>>>> + return -EINVAL;
>>>>>> +}
>>>>>> +
>>>>>> +static int ssd16xx_connector_atomic_set_property(struct
>>>>>> drm_connector *connector,
>>>>>> + struct drm_connector_state *state,
>>>>>> + struct drm_property *property,
>>>>>> + uint64_t val)
>>>>>> +{
>>>>>> + struct ssd16xx_panel *panel = to_ssd16xx_panel(connector->dev);
>>>>>> +
>>>>>> + drm_dbg(&panel->drm, "set_property: %s = %llu\n", property-
>>>>>> >name, val);
>>>>>> +
>>>>>> + if (property == panel->rotation_property) {
>>>>>> + if (val != 0 && val != 90 && val != 180 && val != 270)
>>>>>> + return -EINVAL;
>>>>>> + panel->orientation = val;
>>>>>> + /*
>>>>>> + * Flag hardware re-init needed. crtc_atomic_flush will call
>>>>>> + * ssd16xx_hw_init() with the new orientation and redraw.
>>>>>> + */
>>>>>> + panel->reinit_pending = true;
>>>>>> + return 0;
>>>>>> + }
>>>>>> + if (property == panel->refresh_mode_property) {
>>>>>> + if (val > SSD16XX_REFRESH_FAST)
>>>>>> + return -EINVAL;
>>>>>> + /*
>>>>>> + * Fast refresh (0xC7) omits LOAD_LUT on every update and
>>>>>> relies
>>>>>> + * on the LUT being pre-loaded upfront. Arm the one-shot
>>>>>> flag
>>>>>> + * when switching into fast mode so the next
>>>>>> plane_atomic_update
>>>>>> + * loads the LUT before the first fast-refresh cycle.
>>>>>> Clear it
>>>>>> + * when switching away so a fresh pre-load happens if the
>>>>>> user
>>>>>> + * returns to fast mode later.
>>>>>> + */
>>>>>> + if (val == SSD16XX_REFRESH_FAST &&
>>>>>> + panel->refresh_mode != SSD16XX_REFRESH_FULL)
>>>>>> + panel->fast_lut_pending = true;
>>>>>> + else
>>>>>> + panel->fast_lut_pending = false;
>>>>>> + panel->refresh_mode = val;
>>>>>> + return 0;
>>>>>> + }
>>>>>> + if (property == panel->border_waveform_init_property) {
>>>>>> + if (val >= ARRAY_SIZE(ssd1683_border_waveform_table))
>>>>>> + return -EINVAL;
>>>>>> + panel->border_waveform_init_idx = val;
>>>>>> + return 0;
>>>>>> + }
>>>>>> + if (property == panel->border_waveform_update_property) {
>>>>>> + const u8 *bw_tbl = panel->controller_cfg-
>>>>>> >border_waveform_table;
>>>>>> + bool changed = (int)val != panel-
>>>>>> >border_waveform_update_idx;
>>>>>> +
>>>>>> + if (val >= ARRAY_SIZE(ssd1683_border_waveform_table))
>>>>>> + return -EINVAL;
>>>>>> + drm_dbg(&panel->drm,
>>>>>> + "set_property: border_waveform_update old=%d new=%llu
>>>>>> hw=0x%02x -> 0x%02x %s\n",
>>>>>> + panel->border_waveform_update_idx, val,
>>>>>> + bw_tbl[panel->border_waveform_update_idx],
>>>>>> + bw_tbl[val],
>>>>>> + changed ? "(arming pending)" : "(no change)");
>>>>>> + /* Arm one-shot flag so the new border value is sent on
>>>>>> the very
>>>>>> + * next display update, even if
>>>>>> border_refresh_on_every_update is
>>>>>> + * not set. Cleared in fb_dirty after the command is sent.
>>>>>> + */
>>>>>> + if ((int)val != panel->border_waveform_update_idx)
>>>>>> + panel->border_waveform_pending = true;
>>>>>> + panel->border_waveform_update_idx = val;
>>>>>> + return 0;
>>>>>> + }
>>>>>> + if (property == panel-
>>>>>> >border_refresh_on_every_update_property) {
>>>>>> + panel->border_refresh_on_every_update = !!val;
>>>>>> + return 0;
>>>>>> + }
>>>>>> + if (property == panel->clear_on_init_property) {
>>>>>> + if (val > 3)
>>>>>> + return -EINVAL;
>>>>>> + panel->clear_on_init = (int)val - 1; /* val 0/1/2/3 →
>>>>>> field -1/0/1/2 */
>>>>>> + panel->first_clear_done = false; /* allow re-fire on
>>>>>> next enable */
>>>>>> + return 0;
>>>>>> + }
>>>>>> + if (property == panel->clear_on_close_property) {
>>>>>> + if (val > 3)
>>>>>> + return -EINVAL;
>>>>>> + panel->clear_on_close = (int)val - 1;
>>>>>> + return 0;
>>>>>> + }
>>>>>> + if (property == panel->clear_on_disable_property) {
>>>>>> + if (val > 3)
>>>>>> + return -EINVAL;
>>>>>> + panel->clear_on_disable = (int)val - 1;
>>>>>> + return 0;
>>>>>> + }
>>>>>> + if (property == panel->refresh_mode_init_property) {
>>>>>> + if (val > 3)
>>>>>> + return -EINVAL;
>>>>>> + panel->refresh_mode_init = (int)val - 1;
>>>>>> + return 0;
>>>>>> + }
>>>>>> + if (property == panel->color_mode_property) {
>>>>>> + if (val > SSD16XX_COLOR_MODE_3COLOR)
>>>>>> + return -EINVAL;
>>>>>> + if (val == SSD16XX_COLOR_MODE_3COLOR && !panel-
>>>>>> >panel_cfg- >red_supported) {
>>>>>> + drm_dbg(&panel->drm,
>>>>>> + "set_property: 3-color mode not supported by this
>>>>>> panel\n");
>>>>>> + return -EINVAL;
>>>>>> + }
>>>>>> + panel->color_mode = val;
>>>>>> + return 0;
>>>>>> + }
>>>>>> +
>>>>>> + return -EINVAL;
>>>>>> +}
>>>>>> +
>>>>>> +static const struct drm_connector_funcs ssd16xx_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,
>>>>>> + .atomic_get_property = ssd16xx_connector_atomic_get_property,
>>>>>> + .atomic_set_property = ssd16xx_connector_atomic_set_property,
>>>>>> +};
>>>>>> +
>>>>>> +static const u32 ssd16xx_formats[] = {
>>>>>> + DRM_FORMAT_XRGB8888, /* 32-bit RGB with padding (preferred) */
>>>>>> + DRM_FORMAT_RGB888, /* 24-bit packed RGB */
>>>>>> + DRM_FORMAT_RGB565, /* 16-bit RGB (5:6:5) */
>>>>>> + DRM_FORMAT_R8, /* 8-bit grayscale */
>>>>>> + DRM_FORMAT_NV12, /* YUV 4:2:0 planar */
>>>>>> + DRM_FORMAT_NV16, /* YUV 4:2:2 planar */
>>>>>> + DRM_FORMAT_YUYV, /* Packed YUV 4:2:2 (Y0 U0 Y1 V0) */
>>>>>> + DRM_FORMAT_UYVY, /* Packed YUV 4:2:2 (U0 Y0 V0 Y1) */
>>>>>> + DRM_FORMAT_R1, /* 1-bit monochrome (native, 8 pixels/
>>>>>> byte) */
>>>>>> +};
>>>>>
>>>>> Why do you have all these formats?
>>>>>
>>>>> Only export the modes your panel can do natively; plus maybe
>>>>> XRGB8888 for compatibility.
>>>>>
>>>>
>>>> I wanted to keep YUV formats too since some apps such as camera apps
>>>> (in case we want to click a picture and display over on the e-paper
>>>> badge directly) support only YUV formats but yeah if it's too much I
>>>> can remove them from driver and instead have the conversion in the
>>>> app itself.
>>>>
>>>>>> +
>>>>>> +DEFINE_DRM_GEM_FOPS(ssd16xx_fops);
>>>>>> +
>>>>>> +/*
>>>>>> + * ssd16xx_drm_master_set - arm init refresh when a new master
>>>>>> takes control.
>>>>>> + */
>>>>>> +static void ssd16xx_drm_master_set(struct drm_device *drm,
>>>>>> + struct drm_file *file, bool from_open)
>>>>>> +{
>>>>>> + struct ssd16xx_panel *panel = to_ssd16xx_panel(drm);
>>>>>> +
>>>>>> + panel->display_cleared_on_deinit = false;
>>>>>> + panel->first_clear_done = false;
>>>>>> +
>>>>>> + if (panel->refresh_mode_init >= 0)
>>>>>> + panel->init_refresh_pending = true;
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * ssd16xx_drm_master_drop - clear display and disarm init
>>>>>> refresh when the
>>>>>> + * master client exits.
>>>>>> + */
>>>>>> +static void ssd16xx_drm_master_drop(struct drm_device *drm,
>>>>>> + struct drm_file *file)
>>>>>> +{
>>>>>> + struct ssd16xx_panel *panel = to_ssd16xx_panel(drm);
>>>>>> + int ret;
>>>>>> +
>>>>>> + panel->init_refresh_pending = false;
>>>>>> + panel->first_clear_done = false;
>>>>>> +
>>>>>> + if (panel->clear_on_close < 0 || panel-
>>>>>> >display_cleared_on_deinit)
>>>>>> + return;
>>>>>> +
>>>>>> + ret = ssd16xx_clear_display_on_exit(panel);
>>>>>> + if (ret)
>>>>>> + drm_err(drm, "master_drop: clear on close failed: %d\n",
>>>>>> ret);
>>>>>> +
>>>>>> + panel->display_cleared_on_deinit = true;
>>>>>> +}
>>>>>
>>>>> No, don't overload these. Just remove all this. Clearing should be
>>>>> left to the DRM client.
>>>>>
>>>>
>>>> Yes, the choice to clear or not to clear is left to drm client
>>>> depending on drm property setting done by drm client, the driver
>>>> clears the display. It would be difficult to update all different
>>>> apps to pass a blank white buffer to clear the screen and what if
>>>> the app gets closed abruptly (as master drop callback will get
>>>> triggered), then in that case the current driver logic ensures that
>>>> screen gets cleared. In normal LCD displays if app gets closed
>>>> abruptly, the display would have gone-off automatically as signals
>>>> would stop getting transmitted but in e-paper panel the last display
>>>> context would remain and I think it is driver responsibility to
>>>> clear that if that was the policy communicated by application to the
>>>> driver.
>>>>
>>>>>> +
>>>>>> +static struct drm_driver ssd16xx_drm_driver = {
>>>>>> + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
>>>>>> + .fops = &ssd16xx_fops,
>>>>>> + .name = "ssd16xx",
>>>>>> + .desc = "DRM driver for SSD16xx e-paper controller family",
>>>>>> + .major = 1,
>>>>>> + .minor = 0,
>>>>>> + .master_set = ssd16xx_drm_master_set,
>>>>>> + .master_drop = ssd16xx_drm_master_drop,
>>>>>> + DRM_GEM_DMA_DRIVER_OPS,
>>>>>> + DRM_FBDEV_DMA_DRIVER_OPS,
>>>>>> +};
>>>>>> +
>>>>>> +static const struct drm_mode_config_funcs
>>>>>> ssd16xx_mode_config_funcs = {
>>>>>> + .fb_create = drm_gem_fb_create_with_dirty,
>>>>>> + .atomic_check = drm_atomic_helper_check,
>>>>>> + .atomic_commit = drm_atomic_helper_commit,
>>>>>> +};
>>>>>> +
>>>>>> +/*
>>>>>> + * Use the RPM commit-tail variant so that
>>>>>> drm_atomic_helper_commit_modeset_enables
>>>>>> + * (which calls crtc_atomic_enable) runs before
>>>>>> drm_atomic_helper_commit_planes.
>>>>>> + * Without this, the standard commit_tail calls commit_planes before
>>>>>> + * modeset_enables, so plane_atomic_update would see initialized
>>>>>> == false on the
>>>>>> + * first commit and silently drop the frame.
>>>>>> + */
>>>>>> +static const struct drm_mode_config_helper_funcs
>>>>>> ssd16xx_mode_config_helper_funcs = {
>>>>>> + .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm,
>>>>>> +};
>>>>>> +
>>>>>> +static int ssd16xx_alloc_tx_bufs(struct ssd16xx_panel *panel)
>>>>>> +{
>>>>>> + struct device *dev = &panel->spi->dev;
>>>>>> + size_t frame_size = (panel->controller_cfg->max_width *
>>>>>> + panel->controller_cfg->max_height) / 8;
>>>>>> +
>>>>>> + panel->tx_buf = devm_kmalloc(dev, frame_size, GFP_KERNEL);
>>>>>
>>>>> drmm_kmalloc() here and for the other buffers.
>>>>>
>>>>
>>>> Understood, thanks for pointing will fix it in V2.
>>>>
>>>> Best Regards
>>>> Devarsh
>>>>
>>>>> Best regards
>>>>> Thomas
>>>>
>>>>>
>>>>>> + if (!panel->tx_buf)
>>>>>> + return -ENOMEM;
>>>>>> +
>>>>>> + if (panel->panel_cfg->red_supported) {
>>>>>> + panel->tx_red_buf = devm_kmalloc(dev, frame_size,
>>>>>> GFP_KERNEL);
>>>>>> + if (!panel->tx_red_buf)
>>>>>> + return -ENOMEM;
>>>>>> + }
>>>>>> +
>>>>>> + if (!panel->dc) {
>>>>>> + panel->tx_buf9 = devm_kmalloc_array(dev, frame_size,
>>>>>> + sizeof(u16), GFP_KERNEL);
>>>>>> + if (!panel->tx_buf9)
>>>>>> + return -ENOMEM;
>>>>>> + }
>>>>>> +
>>>>>> + return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static int ssd16xx_probe(struct spi_device *spi)
>>>>>> +{
>>>>>> + struct device *dev = &spi->dev;
>>>>>> + struct ssd16xx_panel *panel;
>>>>>> + struct drm_device *drm;
>>>>>> + const struct spi_device_id *spi_id;
>>>>>> + struct drm_display_mode *mode;
>>>>>> + const void *match;
>>>>>> + enum ssd16xx_model model;
>>>>>> + u32 dt_rotation = 0;
>>>>>> + int ret;
>>>>>> +
>>>>>> + match = device_get_match_data(dev);
>>>>>> + if (match) {
>>>>>> + model = (enum ssd16xx_model)(uintptr_t)match;
>>>>>> + } else {
>>>>>> + spi_id = spi_get_device_id(spi);
>>>>>> + model = (enum ssd16xx_model)spi_id->driver_data;
>>>>>> + }
>>>>>> +
>>>>>> + if (!dev->coherent_dma_mask) {
>>>>>> + ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(64));
>>>>>> + if (ret) {
>>>>>> + dev_warn(dev, "Failed to set DMA mask: %d\n", ret);
>>>>>> + return ret;
>>>>>> + }
>>>>>> + }
>>>>>> +
>>>>>> + panel = devm_drm_dev_alloc(dev, &ssd16xx_drm_driver,
>>>>>> + struct ssd16xx_panel, drm);
>>>>>> + if (IS_ERR(panel))
>>>>>> + return PTR_ERR(panel);
>>>>>> +
>>>>>> + drm = &panel->drm;
>>>>>> + panel->spi = spi;
>>>>>> + panel->model = model;
>>>>>> + spi_set_drvdata(spi, panel);
>>>>>> +
>>>>>> + spi->mode = SPI_MODE_0;
>>>>>> + spi->bits_per_word = SSD16XX_SPI_BITS_PER_WORD;
>>>>>> +
>>>>>> + if (!spi->max_speed_hz) {
>>>>>> + drm_warn(drm, "spi-max-frequency not specified, using %u
>>>>>> Hz\n",
>>>>>> + SSD16XX_SPI_SPEED_DEFAULT);
>>>>>> + spi->max_speed_hz = SSD16XX_SPI_SPEED_DEFAULT;
>>>>>> + }
>>>>>> +
>>>>>> + ret = spi_setup(spi);
>>>>>> + if (ret < 0) {
>>>>>> + drm_err(drm, "SPI setup failed: %d\n", ret);
>>>>>> + return ret;
>>>>>> + }
>>>>>> +
>>>>>> + switch (model) {
>>>>>> + case GDEY042T81:
>>>>>> + panel->controller = SSD1683;
>>>>>> + break;
>>>>>> + default:
>>>>>> + drm_err(drm, "Unknown panel model: %d\n", model);
>>>>>> + return -EINVAL;
>>>>>> + }
>>>>>> +
>>>>>> + if (panel->controller >=
>>>>>> ARRAY_SIZE(ssd16xx_controller_configs) ||
>>>>>> + !ssd16xx_controller_configs[panel->controller].max_width)
>>>>>> + return -EINVAL;
>>>>>> + panel->controller_cfg = &ssd16xx_controller_configs[panel-
>>>>>> >controller];
>>>>>> +
>>>>>> + if (model >= ARRAY_SIZE(ssd16xx_panel_configs))
>>>>>> + return -EINVAL;
>>>>>> + panel->panel_cfg = &ssd16xx_panel_configs[model];
>>>>>> +
>>>>>> + mode = devm_kmemdup(dev, panel->panel_cfg->mode,
>>>>>> + sizeof(*panel->panel_cfg->mode), GFP_KERNEL);
>>>>>> + if (!mode)
>>>>>> + return -ENOMEM;
>>>>>> +
>>>>>> + panel->refresh_mode = panel->panel_cfg->default_refresh_mode;
>>>>>> + /* Default color mode: 3-color for panels with red plane, BW
>>>>>> otherwise */
>>>>>> + panel->color_mode = panel->panel_cfg->red_supported
>>>>>> + ? SSD16XX_COLOR_MODE_3COLOR
>>>>>> + : SSD16XX_COLOR_MODE_BW;
>>>>>> + panel->border_waveform_init_idx = panel->panel_cfg-
>>>>>> >default_border_waveform_init;
>>>>>> + panel->border_waveform_update_idx = panel->panel_cfg-
>>>>>> >default_border_waveform_update;
>>>>>> + panel->border_refresh_on_every_update =
>>>>>> + panel->panel_cfg->default_border_refresh_on_every_update;
>>>>>> + panel->clear_on_init = panel->panel_cfg-
>>>>>> >default_clear_on_init;
>>>>>> + panel->clear_on_close = panel->panel_cfg-
>>>>>> >default_clear_on_close;
>>>>>> + panel->clear_on_disable = panel->panel_cfg-
>>>>>> >default_clear_on_disable;
>>>>>> + panel->refresh_mode_init = panel->panel_cfg-
>>>>>> >default_refresh_mode_init;
>>>>>> +
>>>>>> + /* Module parameter overrides for border/display control */
>>>>>> + if (border_waveform_init_lut >= 0 &&
>>>>>> + border_waveform_init_lut <
>>>>>> (int)ARRAY_SIZE(ssd1683_border_waveform_table))
>>>>>> + panel->border_waveform_init_idx = border_waveform_init_lut;
>>>>>> + if (border_waveform_lut >= 0 &&
>>>>>> + border_waveform_lut <
>>>>>> (int)ARRAY_SIZE(ssd1683_border_waveform_table))
>>>>>> + panel->border_waveform_update_idx = border_waveform_lut;
>>>>>> + if (border_refresh_on_every_update)
>>>>>> + panel->border_refresh_on_every_update = true;
>>>>>> + if (clear_on_init >= 0 && clear_on_init <= 2)
>>>>>> + panel->clear_on_init = clear_on_init;
>>>>>> + if (clear_on_close >= 0 && clear_on_close <= 2)
>>>>>> + panel->clear_on_close = clear_on_close;
>>>>>> + if (clear_on_disable >= 0 && clear_on_disable <= 2)
>>>>>> + panel->clear_on_disable = clear_on_disable;
>>>>>> + if (refresh_mode_init >= 0 && refresh_mode_init <= 2)
>>>>>> + panel->refresh_mode_init = refresh_mode_init;
>>>>>> +
>>>>>> + /* Module parameter overrides panel default refresh mode when
>>>>>> set */
>>>>>> + if (refresh_mode >= 0) {
>>>>>> + if (refresh_mode > SSD16XX_REFRESH_FAST)
>>>>>> + drm_warn(drm, "Invalid refresh_mode module param %d,
>>>>>> ignored\n",
>>>>>> + refresh_mode);
>>>>>> + else
>>>>>> + panel->refresh_mode = refresh_mode;
>>>>>> + }
>>>>>> +
>>>>>> + /* Module parameter overrides panel default color mode when
>>>>>> set */
>>>>>> + if (color_mode >= 0) {
>>>>>> + if (color_mode > SSD16XX_COLOR_MODE_3COLOR)
>>>>>> + drm_warn(drm, "Invalid color_mode module param %d,
>>>>>> ignored\n",
>>>>>> + color_mode);
>>>>>> + else if (color_mode == SSD16XX_COLOR_MODE_3COLOR &&
>>>>>> + !panel->panel_cfg->red_supported)
>>>>>> + drm_warn(drm,
>>>>>> + "color_mode=3-color requested but panel has no
>>>>>> red plane, ignored\n");
>>>>>> + else
>>>>>> + panel->color_mode = color_mode;
>>>>>> + }
>>>>>> +
>>>>>> + /* Parse "rotation" DT property; swap mode dimensions for
>>>>>> portrait. */
>>>>>> + device_property_read_u32(dev, "rotation", &dt_rotation);
>>>>>> + if (dt_rotation != 0 && dt_rotation != 90 && dt_rotation !=
>>>>>> 180 && dt_rotation != 270) {
>>>>>> + drm_warn(drm, "Invalid DT rotation %u, defaulting to 0°
>>>>>> \n", dt_rotation);
>>>>>> + dt_rotation = 0;
>>>>>> + }
>>>>>> + panel->orientation = dt_rotation;
>>>>>> +
>>>>>> + /* Module parameter overrides DT rotation when set */
>>>>>> + if (rotation >= 0) {
>>>>>> + if (rotation != 0 && rotation != 90 && rotation != 180 &&
>>>>>> rotation != 270)
>>>>>> + drm_warn(drm, "Invalid rotation module param %d,
>>>>>> ignored\n",
>>>>>> + rotation);
>>>>>> + else
>>>>>> + panel->orientation = rotation;
>>>>>> + }
>>>>>> +
>>>>>> + drm_dbg(drm, "Using %s orientation (%u°, %ux%u logical)\n",
>>>>>> + (panel->orientation == 90 || panel->orientation == 270) ?
>>>>>> "portrait" : "landscape",
>>>>>> + panel->orientation, mode->hdisplay, mode->vdisplay);
>>>>>> +
>>>>>> + /* Swap mode dimensions for portrait so clients see logical
>>>>>> size. */
>>>>>> + if (panel->orientation == 90 || panel->orientation == 270) {
>>>>>> + swap(mode->hdisplay, mode->vdisplay);
>>>>>> + swap(mode->hsync_start, mode->vsync_start);
>>>>>> + swap(mode->hsync_end, mode->vsync_end);
>>>>>> + swap(mode->htotal, mode->vtotal);
>>>>>> + swap(mode->width_mm, mode->height_mm);
>>>>>> + drm_dbg(drm, "Mode dimensions swapped for portrait:
>>>>>> %ux%u\n",
>>>>>> + mode->hdisplay, mode->vdisplay);
>>>>>> + } else {
>>>>>> + drm_dbg(drm, "Mode dimensions unchanged: %ux%u\n",
>>>>>> + mode->hdisplay, mode->vdisplay);
>>>>>> + }
>>>>>> + panel->mode = mode;
>>>>>> + panel->width = mode->hdisplay;
>>>>>> + panel->height = mode->vdisplay;
>>>>>> +
>>>>>> + /* Acquire GPIOs. */
>>>>>> + panel->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
>>>>>> + if (IS_ERR(panel->reset))
>>>>>> + return dev_err_probe(dev, PTR_ERR(panel->reset), "Failed
>>>>>> to get RESET GPIO\n");
>>>>>> +
>>>>>> + panel->busy = devm_gpiod_get(dev, "busy", GPIOD_IN);
>>>>>> + if (IS_ERR(panel->busy))
>>>>>> + return dev_err_probe(dev, PTR_ERR(panel->busy), "Failed
>>>>>> to get BUSY GPIO\n");
>>>>>> +
>>>>>> + panel->dc = devm_gpiod_get_optional(dev, "dc", GPIOD_OUT_LOW);
>>>>>> + if (IS_ERR(panel->dc))
>>>>>> + return dev_err_probe(dev, PTR_ERR(panel->dc), "Failed to
>>>>>> get DC GPIO\n");
>>>>>> + if (!panel->dc) {
>>>>>> + if (!spi_is_bpw_supported(spi, 9))
>>>>>> + return dev_err_probe(dev, -EINVAL,
>>>>>> + "3-wire SPI mode requires 9-bit word
>>>>>> support\n");
>>>>>> + drm_dbg(drm, "dc-gpios not specified, using 3-wire (9-
>>>>>> bit) SPI mode\n");
>>>>>> + }
>>>>>> +
>>>>>> + ret = ssd16xx_alloc_tx_bufs(panel);
>>>>>> + if (ret)
>>>>>> + return ret;
>>>>>> +
>>>>>> + ssd16xx_hw_reset(panel);
>>>>>> +
>>>>>> + ret = drmm_mode_config_init(drm);
>>>>>> + if (ret)
>>>>>> + return ret;
>>>>>> +
>>>>>> + drm->mode_config.funcs = &ssd16xx_mode_config_funcs;
>>>>>> + drm->mode_config.helper_private =
>>>>>> &ssd16xx_mode_config_helper_funcs;
>>>>>> + drm->mode_config.min_width = min(panel->width, panel->height);
>>>>>> + drm->mode_config.max_width = max(panel->width, panel->height);
>>>>>> + drm->mode_config.min_height = min(panel->width, panel->height);
>>>>>> + drm->mode_config.max_height = max(panel->width, panel->height);
>>>>>> +
>>>>>> + drm_connector_helper_add(&panel->connector,
>>>>>> &ssd16xx_connector_helper_funcs);
>>>>>> + ret = drm_connector_init(drm, &panel->connector,
>>>>>> &ssd16xx_connector_funcs,
>>>>>> + DRM_MODE_CONNECTOR_SPI);
>>>>>> + if (ret)
>>>>>> + return ret;
>>>>>> +
>>>>>> + ret = drm_universal_plane_init(drm, &panel->primary_plane, 0,
>>>>>> + &ssd16xx_plane_funcs,
>>>>>> + ssd16xx_formats, ARRAY_SIZE(ssd16xx_formats),
>>>>>> + NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
>>>>>> + if (ret)
>>>>>> + return ret;
>>>>>> + drm_plane_helper_add(&panel->primary_plane,
>>>>>> &ssd16xx_plane_helper_funcs);
>>>>>> + drm_plane_enable_fb_damage_clips(&panel->primary_plane);
>>>>>> +
>>>>>> + ret = drm_crtc_init_with_planes(drm, &panel->crtc, &panel-
>>>>>> >primary_plane,
>>>>>> + NULL, &ssd16xx_crtc_funcs, NULL);
>>>>>> + if (ret)
>>>>>> + return ret;
>>>>>> + drm_crtc_helper_add(&panel->crtc, &ssd16xx_crtc_helper_funcs);
>>>>>> +
>>>>>> + ret = drm_simple_encoder_init(drm, &panel->encoder,
>>>>>> DRM_MODE_ENCODER_NONE);
>>>>>> + if (ret)
>>>>>> + return ret;
>>>>>> + panel->encoder.possible_crtcs = drm_crtc_mask(&panel->crtc);
>>>>>> +
>>>>>> + ret = drm_connector_attach_encoder(&panel->connector, &panel-
>>>>>> >encoder);
>>>>>> + if (ret)
>>>>>> + return ret;
>>>>>> +
>>>>>> + ret = ssd16xx_connector_create_properties(panel);
>>>>>> + if (ret)
>>>>>> + return ret;
>>>>>> +
>>>>>> + drm_mode_config_reset(drm);
>>>>>> +
>>>>>> + ret = drm_dev_register(drm, 0);
>>>>>> + if (ret)
>>>>>> + return ret;
>>>>>> +
>>>>>> + drm_dbg(drm, "SSD16xx e-paper display initialized (%dx%d, %d°
>>>>>> rotation)\n",
>>>>>> + panel->width, panel->height, panel->orientation);
>>>>>> +
>>>>>> + drm_client_setup(drm, NULL);
>>>>>> +
>>>>>> + return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static void ssd16xx_remove(struct spi_device *spi)
>>>>>> +{
>>>>>> + struct ssd16xx_panel *panel = spi_get_drvdata(spi);
>>>>>> +
>>>>>> + drm_dev_unplug(&panel->drm);
>>>>>> + drm_atomic_helper_shutdown(&panel->drm);
>>>>>> +}
>>>>>> +
>>>>>> +static void ssd16xx_shutdown(struct spi_device *spi)
>>>>>> +{
>>>>>> + struct ssd16xx_panel *panel = spi_get_drvdata(spi);
>>>>>> +
>>>>>> + drm_atomic_helper_shutdown(&panel->drm);
>>>>>> +}
>>>>>> +
>>>>>> +static const struct of_device_id ssd16xx_of_match[] = {
>>>>>> + { .compatible = "gooddisplay,gdey042t81", .data = (void
>>>>>> *)GDEY042T81 },
>>>>>> + { }
>>>>>> +};
>>>>>> +MODULE_DEVICE_TABLE(of, ssd16xx_of_match);
>>>>>> +
>>>>>> +static const struct spi_device_id ssd16xx_id[] = {
>>>>>> + { "gdey042t81", GDEY042T81 },
>>>>>> + { }
>>>>>> +};
>>>>>> +MODULE_DEVICE_TABLE(spi, ssd16xx_id);
>>>>>> +
>>>>>> +static struct spi_driver ssd16xx_spi_driver = {
>>>>>> + .driver = {
>>>>>> + .name = "ssd16xx",
>>>>>> + .of_match_table = ssd16xx_of_match,
>>>>>> + },
>>>>>> + .probe = ssd16xx_probe,
>>>>>> + .remove = ssd16xx_remove,
>>>>>> + .shutdown = ssd16xx_shutdown,
>>>>>> + .id_table = ssd16xx_id,
>>>>>> +};
>>>>>> +module_spi_driver(ssd16xx_spi_driver);
>>>>>> +
>>>>>> +MODULE_AUTHOR("Devarsh Thakkar <devarsht@ti.com>");
>>>>>> +MODULE_DESCRIPTION("DRM driver for Solomon SSD16xx e-paper
>>>>>> display controller family");
>>>>>> +MODULE_LICENSE("GPL");
>>>>>
>>>>
>>>
>>
>
^ permalink raw reply
* Re: [PATCH] dt-bindings: clock: renesas: div6: Use ZT/ZTR trace clock in R-Mobile APE6 example
From: Rob Herring @ 2026-06-24 13:10 UTC (permalink / raw)
To: Marek Vasut
Cc: linux-arm-kernel, Brian Masney, Conor Dooley, Geert Uytterhoeven,
Krzysztof Kozlowski, Michael Turquette, Stephen Boyd, devicetree,
linux-clk, linux-kernel, linux-renesas-soc
In-Reply-To: <20260523192622.56605-1-marek.vasut+renesas@mailbox.org>
On Sat, May 23, 2026 at 09:25:50PM +0200, Marek Vasut wrote:
> Since commit 2abdc3dcf978 ("dt-bindings: clock: renesas,cpg-clocks:
> Document ZT/ZTR trace clock on R-Mobile APE6"), the APE6 clock node
> expects two additional "clock-output-names" entries, "zt" and "ztr".
> Update the example accordingly.
>
> Fixes: 2abdc3dcf978 ("dt-bindings: clock: renesas,cpg-clocks: Document ZT/ZTR trace clock on R-Mobile APE6")
> Signed-off-by: Marek Vasut <marek.vasut+renesas@mailbox.org>
Applied for rc1.
Rob
^ permalink raw reply
* Re: [PATCH v6 1/3] regulator: dt-bindings: Add Unisoc SC2730 PMIC
From: Rob Herring @ 2026-06-24 13:06 UTC (permalink / raw)
To: Otto Pflüger
Cc: Liam Girdwood, Mark Brown, Krzysztof Kozlowski, Conor Dooley,
Orson Zhai, Baolin Wang, Chunyan Zhang, Lee Jones, linux-kernel,
devicetree, Krzysztof Kozlowski
In-Reply-To: <20260620-sc2730-regulators-v6-1-bbd2db395231@abscue.de>
On Sat, Jun 20, 2026 at 10:54:00AM +0200, Otto Pflüger wrote:
> Add bindings for the regulators found in the Spreadtrum/Unisoc SC2730
> PMIC, used e.g. with the UMS512 and UMS9230 SoCs.
>
> Signed-off-by: Otto Pflüger <otto.pflueger@abscue.de>
> Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
> ---
> .../bindings/regulator/sprd,sc2730-regulator.yaml | 44 ++++++++++++++++++++++
> 1 file changed, 44 insertions(+)
Applied for rc1 to fix the warnings.
Rob
^ permalink raw reply
* Re: [PATCH v9 04/12] reset: realtek: Add RTD1625-ISO reset controller driver
From: Philipp Zabel @ 2026-06-24 13:03 UTC (permalink / raw)
To: Yu-Chun Lin, mturquette, sboyd, robh, krzk+dt, conor+dt, cylee12,
afaerber, jyanchou, bmasney
Cc: devicetree, linux-clk, linux-kernel, linux-arm-kernel,
linux-realtek-soc, james.tai, cy.huang, stanley_chang
In-Reply-To: <20260624112940.3475605-5-eleanor.lin@realtek.com>
On Mi, 2026-06-24 at 19:29 +0800, Yu-Chun Lin wrote:
> From: Cheng-Yu Lee <cylee12@realtek.com>
>
> Add support for the ISO (Isolation) domain reset controller on the Realtek
> RTD1625 SoC.
>
> The reset controller shares the same register space with the ISO clock
> controller. To handle this shared register space, the reset driver is
> implemented as an auxiliary driver. It will be instantiated and probed via
> the auxiliary bus by the RTD1625-ISO clock controller driver.
>
> Signed-off-by: Cheng-Yu Lee <cylee12@realtek.com>
> Co-developed-by: Yu-Chun Lin <eleanor.lin@realtek.com>
> Signed-off-by: Yu-Chun Lin <eleanor.lin@realtek.com>
> ---
> Changes in v9:
> - Extract reset-related code from the previous clock driver patch
> (formerly patch 9 in v8).
> ---
> drivers/reset/realtek/Makefile | 2 +-
> drivers/reset/realtek/reset-rtd1625-iso.c | 99 +++++++++++++++++++++++
> 2 files changed, 100 insertions(+), 1 deletion(-)
> create mode 100644 drivers/reset/realtek/reset-rtd1625-iso.c
>
> diff --git a/drivers/reset/realtek/Makefile b/drivers/reset/realtek/Makefile
> index c3f605ffb11c..9007c9d5683b 100644
> --- a/drivers/reset/realtek/Makefile
> +++ b/drivers/reset/realtek/Makefile
> @@ -1,3 +1,3 @@
> # SPDX-License-Identifier: GPL-2.0-only
> obj-$(CONFIG_RESET_RTK_COMMON) += reset-rtk-common.o
> -obj-$(CONFIG_RESET_RTD1625) += reset-rtd1625-crt.o
> +obj-$(CONFIG_RESET_RTD1625) += reset-rtd1625-crt.o reset-rtd1625-iso.o
Is there any benefit to these two being separate modules?
I suggest you merge them into one: reset-rtd1625.o
> diff --git a/drivers/reset/realtek/reset-rtd1625-iso.c b/drivers/reset/realtek/reset-rtd1625-iso.c
> new file mode 100644
> index 000000000000..78eaabb408f0
> --- /dev/null
> +++ b/drivers/reset/realtek/reset-rtd1625-iso.c
> @@ -0,0 +1,99 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2026 Realtek Semiconductor Corporation
> + */
> +
> +#include <dt-bindings/reset/realtek,rtd1625.h>
> +#include <linux/auxiliary_bus.h>
> +#include <linux/device.h>
> +#include <linux/errno.h>
> +#include <linux/of.h>
> +#include <linux/slab.h>
> +#include "reset-rtk-common.h"
> +
> +#define RTD1625_ISO_RSTN_MAX 29
> +#define RTD1625_ISO_S_RSTN_MAX 5
These are not necessary, just use ARRAY_SIZE() for nr_resets.
> +
> +static const struct rtk_reset_desc rtd1625_iso_reset_descs[] = {
> + [RTD1625_ISO_RSTN_VFD] = { .ofs = 0x88, .bit = 0 },
> + [RTD1625_ISO_RSTN_CEC0] = { .ofs = 0x88, .bit = 2 },
> + [RTD1625_ISO_RSTN_CEC1] = { .ofs = 0x88, .bit = 3 },
> + [RTD1625_ISO_RSTN_CBUSTX] = { .ofs = 0x88, .bit = 5 },
> + [RTD1625_ISO_RSTN_CBUSRX] = { .ofs = 0x88, .bit = 6 },
> + [RTD1625_ISO_RSTN_USB3_PHY2_XTAL_POW] = { .ofs = 0x88, .bit = 7 },
> + [RTD1625_ISO_RSTN_UR0] = { .ofs = 0x88, .bit = 8 },
> + [RTD1625_ISO_RSTN_GMAC] = { .ofs = 0x88, .bit = 9 },
> + [RTD1625_ISO_RSTN_GPHY] = { .ofs = 0x88, .bit = 10 },
> + [RTD1625_ISO_RSTN_I2C_0] = { .ofs = 0x88, .bit = 11 },
> + [RTD1625_ISO_RSTN_I2C_1] = { .ofs = 0x88, .bit = 12 },
> + [RTD1625_ISO_RSTN_CBUS] = { .ofs = 0x88, .bit = 13 },
> + [RTD1625_ISO_RSTN_USB_DRD] = { .ofs = 0x88, .bit = 14 },
> + [RTD1625_ISO_RSTN_USB_HOST] = { .ofs = 0x88, .bit = 15 },
> + [RTD1625_ISO_RSTN_USB_PHY_0] = { .ofs = 0x88, .bit = 16 },
> + [RTD1625_ISO_RSTN_USB_PHY_1] = { .ofs = 0x88, .bit = 17 },
> + [RTD1625_ISO_RSTN_USB_PHY_2] = { .ofs = 0x88, .bit = 18 },
> + [RTD1625_ISO_RSTN_USB] = { .ofs = 0x88, .bit = 19 },
> + [RTD1625_ISO_RSTN_TYPE_C] = { .ofs = 0x88, .bit = 20 },
> + [RTD1625_ISO_RSTN_USB_U3_HOST] = { .ofs = 0x88, .bit = 21 },
> + [RTD1625_ISO_RSTN_USB3_PHY0_POW] = { .ofs = 0x88, .bit = 22 },
> + [RTD1625_ISO_RSTN_USB3_P0_MDIO] = { .ofs = 0x88, .bit = 23 },
> + [RTD1625_ISO_RSTN_USB3_PHY1_POW] = { .ofs = 0x88, .bit = 24 },
> + [RTD1625_ISO_RSTN_USB3_P1_MDIO] = { .ofs = 0x88, .bit = 25 },
> + [RTD1625_ISO_RSTN_VTC] = { .ofs = 0x88, .bit = 26 },
> + [RTD1625_ISO_RSTN_USB3_PHY2_POW] = { .ofs = 0x88, .bit = 27 },
> + [RTD1625_ISO_RSTN_USB3_P2_MDIO] = { .ofs = 0x88, .bit = 28 },
> + [RTD1625_ISO_RSTN_USB_PHY_3] = { .ofs = 0x88, .bit = 29 },
> + [RTD1625_ISO_RSTN_USB_PHY_4] = { .ofs = 0x88, .bit = 30 },
> +};
> +
> +static const struct rtk_reset_desc rtd1625_iso_s_reset_descs[] = {
> + [RTD1625_ISO_S_RSTN_ISOM_MIS] = { .ofs = 0x310, .bit = 0, .write_en = 1 },
> + [RTD1625_ISO_S_RSTN_GPIOM] = { .ofs = 0x310, .bit = 2, .write_en = 1 },
> + [RTD1625_ISO_S_RSTN_TIMER7] = { .ofs = 0x310, .bit = 4, .write_en = 1 },
> + [RTD1625_ISO_S_RSTN_IRDA] = { .ofs = 0x310, .bit = 6, .write_en = 1 },
> + [RTD1625_ISO_S_RSTN_UR10] = { .ofs = 0x310, .bit = 8, .write_en = 1 },
> +};
> +
> +static int rtd1625_iso_reset_probe(struct auxiliary_device *adev,
> + const struct auxiliary_device_id *id)
> +{
> + struct device *dev = &adev->dev;
> + struct device *parent = dev->parent;
> + struct rtk_reset_data *data;
> +
> + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
> + if (!data)
> + return -ENOMEM;
> +
> + if (of_device_is_compatible(parent->of_node, "realtek,rtd1625-iso-s-clk")) {
> + data->descs = rtd1625_iso_s_reset_descs;
> + data->rcdev.nr_resets = RTD1625_ISO_S_RSTN_MAX;
> + } else {
> + data->descs = rtd1625_iso_reset_descs;
> + data->rcdev.nr_resets = RTD1625_ISO_RSTN_MAX;
> + }
No need to parse OF compatible again. Store these in a struct, point
auxiliary_device_id::driver_data to it, and use that here.
regards
Philipp
^ permalink raw reply
* Re: [PATCH v9 02/12] reset: Add Realtek basic reset support
From: Philipp Zabel @ 2026-06-24 13:02 UTC (permalink / raw)
To: Yu-Chun Lin, mturquette, sboyd, robh, krzk+dt, conor+dt, cylee12,
afaerber, jyanchou, bmasney
Cc: devicetree, linux-clk, linux-kernel, linux-arm-kernel,
linux-realtek-soc, james.tai, cy.huang, stanley_chang
In-Reply-To: <20260624112940.3475605-3-eleanor.lin@realtek.com>
On Mi, 2026-06-24 at 19:29 +0800, Yu-Chun Lin wrote:
> From: Cheng-Yu Lee <cylee12@realtek.com>
>
> Define the reset operations backed by a regmap-based register interface
> and prepare the reset controller to be registered through the reset
> framework.
>
> Since the reset controllers on Realtek SoCs often share the same register
> space with the clock controllers, this common framework is designed to
> extract the regmap and device tree node from the parent device
> (e.g., an auxiliary device parent).
>
> Signed-off-by: Cheng-Yu Lee <cylee12@realtek.com>
> Co-developed-by: Yu-Chun Lin <eleanor.lin@realtek.com>
> Signed-off-by: Yu-Chun Lin <eleanor.lin@realtek.com>
> ---
> Changes in v8:
> - Rename common.[ch] to reset-rtk-common.[ch].
> ---
> MAINTAINERS | 1 +
> drivers/reset/Kconfig | 1 +
> drivers/reset/Makefile | 1 +
> drivers/reset/realtek/Kconfig | 8 +++
> drivers/reset/realtek/Makefile | 2 +
> drivers/reset/realtek/reset-rtk-common.c | 90 ++++++++++++++++++++++++
> drivers/reset/realtek/reset-rtk-common.h | 29 ++++++++
> 7 files changed, 132 insertions(+)
> create mode 100644 drivers/reset/realtek/Kconfig
> create mode 100644 drivers/reset/realtek/Makefile
> create mode 100644 drivers/reset/realtek/reset-rtk-common.c
> create mode 100644 drivers/reset/realtek/reset-rtk-common.h
>
[...]
> diff --git a/drivers/reset/realtek/reset-rtk-common.c b/drivers/reset/realtek/reset-rtk-common.c
> new file mode 100644
> index 000000000000..75b27cb2a208
> --- /dev/null
> +++ b/drivers/reset/realtek/reset-rtk-common.c
> @@ -0,0 +1,90 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2019-2026 Realtek Semiconductor Corporation
> + */
> +
> +#include <linux/device.h>
> +#include <linux/export.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/regmap.h>
> +#include "reset-rtk-common.h"
> +
> +static inline struct rtk_reset_data *to_rtk_reset_controller(struct reset_controller_dev *r)
> +{
> + return container_of(r, struct rtk_reset_data, rcdev);
> +}
> +
> +static inline const struct rtk_reset_desc *rtk_reset_get_desc(struct rtk_reset_data *data,
> + unsigned long idx)
> +{
> + return &data->descs[idx];
> +}
> +
> +static int rtk_reset_assert(struct reset_controller_dev *rcdev,
> + unsigned long idx)
> +{
> + struct rtk_reset_data *data = to_rtk_reset_controller(rcdev);
> + const struct rtk_reset_desc *desc;
> + u32 mask, val;
> +
> + desc = rtk_reset_get_desc(data, idx);
> + mask = desc->write_en ? (0x3U << desc->bit) : BIT(desc->bit);
> + val = desc->write_en ? (0x2U << desc->bit) : 0;
> +
> + return regmap_update_bits(data->regmap, desc->ofs, mask, val);
> +}
> +
> +static int rtk_reset_deassert(struct reset_controller_dev *rcdev,
> + unsigned long idx)
> +{
> + struct rtk_reset_data *data = to_rtk_reset_controller(rcdev);
> + const struct rtk_reset_desc *desc;
> + u32 mask, val;
> +
> + desc = rtk_reset_get_desc(data, idx);
> + mask = desc->write_en ? (0x3U << desc->bit) : BIT(desc->bit);
> + val = mask;
> +
> + return regmap_update_bits(data->regmap, desc->ofs, mask, val);
You can use regmap_set_bits() here.
> +}
> +
> +static int rtk_reset_status(struct reset_controller_dev *rcdev,
> + unsigned long idx)
> +{
> + struct rtk_reset_data *data = to_rtk_reset_controller(rcdev);
> + const struct rtk_reset_desc *desc;
> + u32 val;
unsigned int val;
> + int ret;
> +
> + desc = rtk_reset_get_desc(data, idx);
> + ret = regmap_read(data->regmap, desc->ofs, &val);
> + if (ret)
> + return ret;
> +
> + return !((val >> desc->bit) & 1);
> +}
> +
> +static const struct reset_control_ops rtk_reset_ops = {
> + .assert = rtk_reset_assert,
> + .deassert = rtk_reset_deassert,
> + .status = rtk_reset_status,
> +};
> +
> +/* The caller must initialize data->descs, data->rcdev.nr_resets and
> + * data->rcdev.owner before calling rtk_reset_controller_add().
> + */
> +int rtk_reset_controller_add(struct device *dev,
> + struct rtk_reset_data *data)
> +{
> + data->regmap = dev_get_platdata(dev);
> + data->rcdev.ops = &rtk_reset_ops;
> + data->rcdev.dev = dev;
> + data->rcdev.of_node = dev->parent->of_node;
This split rcdev initialization is more hassle than it is worth.
Please just export rtk_reset_ops and duplicate the
regmap/ops/dev/of_node assignment in the probe functions.
Alternatively, consolidate the probe function and export it from here.
regards
Philipp
^ permalink raw reply
* [PATCH v5 3/3] arm64: dts: qcom: Add Vicharak Axon Mini
From: Ajit Singh @ 2026-06-24 12:54 UTC (permalink / raw)
To: Bjorn Andersson, Konrad Dybcio
Cc: Dmitry Baryshkov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
linux-arm-msm, devicetree, linux-kernel, Ajit Singh
In-Reply-To: <20260624125443.18729-1-blfizzyy@gmail.com>
Add DTS for the Vicharak Axon Mini board based on the Qualcomm
QCS6490 SoC.
This adds debug UART, eMMC, UFS, SDIO WLAN, USB 2.0 host, PCIe
support along with regulators.
The UFS ICE block is kept disabled because enabling it currently causes
an SError during qcom_ice_create() on this board. UFS works without ICE.
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Signed-off-by: Ajit Singh <blfizzyy@gmail.com>
---
arch/arm64/boot/dts/qcom/Makefile | 1 +
.../dts/qcom/qcs6490-vicharak-axon-mini.dts | 1014 +++++++++++++++++
2 files changed, 1015 insertions(+)
create mode 100644 arch/arm64/boot/dts/qcom/qcs6490-vicharak-axon-mini.dts
diff --git a/arch/arm64/boot/dts/qcom/Makefile b/arch/arm64/boot/dts/qcom/Makefile
index f80b5d9cf1e8..d8d04dc88e08 100644
--- a/arch/arm64/boot/dts/qcom/Makefile
+++ b/arch/arm64/boot/dts/qcom/Makefile
@@ -146,6 +146,7 @@ qcs6490-rb3gen2-industrial-mezzanine-dtbs := qcs6490-rb3gen2.dtb qcs6490-rb3gen2
dtb-$(CONFIG_ARCH_QCOM) += qcs6490-rb3gen2-industrial-mezzanine.dtb
dtb-$(CONFIG_ARCH_QCOM) += qcs6490-rb3gen2-vision-mezzanine.dtb
dtb-$(CONFIG_ARCH_QCOM) += qcs6490-thundercomm-rubikpi3.dtb
+dtb-$(CONFIG_ARCH_QCOM) += qcs6490-vicharak-axon-mini.dtb
dtb-$(CONFIG_ARCH_QCOM) += qcs8300-ride.dtb
dtb-$(CONFIG_ARCH_QCOM) += qcs8550-aim300-aiot.dtb
dtb-$(CONFIG_ARCH_QCOM) += qcs9100-ride.dtb
diff --git a/arch/arm64/boot/dts/qcom/qcs6490-vicharak-axon-mini.dts b/arch/arm64/boot/dts/qcom/qcs6490-vicharak-axon-mini.dts
new file mode 100644
index 000000000000..4f2e0109f49b
--- /dev/null
+++ b/arch/arm64/boot/dts/qcom/qcs6490-vicharak-axon-mini.dts
@@ -0,0 +1,1014 @@
+// SPDX-License-Identifier: BSD-3-Clause
+/*
+ * Copyright (c) 2026 Vicharak Computers Pvt. Ltd.
+ */
+
+/dts-v1/;
+
+/* PM7250B is configured to use SID8/9 */
+#define PM7250B_SID 8
+#define PM7250B_SID1 9
+
+#include <dt-bindings/iio/qcom,spmi-adc7-pmk8350.h>
+#include <dt-bindings/iio/qcom,spmi-adc7-pm7325.h>
+#include <dt-bindings/leds/common.h>
+#include <dt-bindings/pinctrl/qcom,pmic-gpio.h>
+#include <dt-bindings/regulator/qcom,rpmh-regulator.h>
+
+#include "kodiak.dtsi"
+#include "pm7250b.dtsi"
+#include "pm7325.dtsi"
+#include "pm8350c.dtsi"
+#include "pmk8350.dtsi"
+
+/delete-node/ &adsp_mem;
+/delete-node/ &cdsp_mem;
+/delete-node/ &ipa_fw_mem;
+/delete-node/ &mpss_mem;
+/delete-node/ &remoteproc_mpss;
+/delete-node/ &remoteproc_wpss;
+/delete-node/ &rmtfs_mem;
+/delete-node/ &video_mem;
+/delete-node/ &wifi;
+/delete-node/ &wlan_ce_mem;
+/delete-node/ &wlan_fw_mem;
+/delete-node/ &wpss_mem;
+/delete-node/ &xbl_mem;
+
+/ {
+ model = "Vicharak Axon Mini";
+ compatible = "vicharak,axon-mini", "qcom,qcm6490";
+ chassis-type = "embedded";
+
+ aliases {
+ serial0 = &uart5;
+ };
+
+ chosen {
+ stdout-path = "serial0:115200n8";
+ };
+
+ leds {
+ compatible = "gpio-leds";
+
+ pinctrl-0 = <&user_leds>;
+ pinctrl-names = "default";
+
+ user-led {
+ color = <LED_COLOR_ID_BLUE>;
+ function = LED_FUNCTION_INDICATOR;
+ gpios = <&tlmm 19 GPIO_ACTIVE_HIGH>;
+ linux,default-trigger = "default-on";
+ default-state = "on";
+ panic-indicator;
+ retain-state-suspended;
+ };
+
+ status-led {
+ color = <LED_COLOR_ID_BLUE>;
+ function = LED_FUNCTION_INDICATOR;
+ gpios = <&tlmm 55 GPIO_ACTIVE_HIGH>;
+ linux,default-trigger = "heartbeat";
+ default-state = "on";
+ retain-state-suspended;
+ };
+ };
+
+ reserved-memory {
+ xbl_mem: xbl@80700000 {
+ reg = <0x0 0x80700000 0x0 0x100000>;
+ no-map;
+ };
+
+ cdsp_secure_heap_mem: cdsp-secure-heap@81800000 {
+ reg = <0x0 0x81800000 0x0 0x1e00000>;
+ no-map;
+ };
+
+ camera_mem: camera@84300000 {
+ reg = <0x0 0x84300000 0x0 0x500000>;
+ no-map;
+ };
+
+ wpss_mem: wpss@84800000 {
+ reg = <0x0 0x84800000 0x0 0x1900000>;
+ no-map;
+ };
+
+ adsp_mem: adsp@86100000 {
+ reg = <0x0 0x86100000 0x0 0x2800000>;
+ no-map;
+ };
+
+ cdsp_mem: cdsp@88900000 {
+ reg = <0x0 0x88900000 0x0 0x1e00000>;
+ no-map;
+ };
+
+ video_mem: video@8a700000 {
+ reg = <0x0 0x8a700000 0x0 0x700000>;
+ no-map;
+ };
+
+ cvp_mem: cvp@8ae00000 {
+ reg = <0x0 0x8ae00000 0x0 0x500000>;
+ no-map;
+ };
+
+ ipa_fw_mem: ipa-fw@8b300000 {
+ reg = <0x0 0x8b300000 0x0 0x10000>;
+ no-map;
+ };
+
+ ipa_gsi_mem: ipa-gsi@8b310000 {
+ reg = <0x0 0x8b310000 0x0 0xa000>;
+ no-map;
+ };
+
+ gpu_microcode_mem: gpu-microcode@8b31a000 {
+ reg = <0x0 0x8b31a000 0x0 0x2000>;
+ no-map;
+ };
+
+ tz_stat_mem: tz-stat@c0000000 {
+ reg = <0x0 0xc0000000 0x0 0x100000>;
+ no-map;
+ };
+
+ tags_mem: tags@c0100000 {
+ reg = <0x0 0xc0100000 0x0 0x1200000>;
+ no-map;
+ };
+
+ qtee_mem: qtee@c1300000 {
+ reg = <0x0 0xc1300000 0x0 0x500000>;
+ no-map;
+ };
+
+ trusted_apps_mem: trusted-apps@c1800000 {
+ reg = <0x0 0xc1800000 0x0 0x1c00000>;
+ no-map;
+ };
+
+ debug_vm_mem: debug-vm@d0600000 {
+ reg = <0x0 0xd0600000 0x0 0x100000>;
+ no-map;
+ };
+ };
+
+ thermal-zones {
+ chg-skin-thermal {
+ polling-delay-passive = <0>;
+
+ thermal-sensors = <&pm7250b_adc_tm 0>;
+
+ trips {
+ active-config0 {
+ temperature = <125000>;
+ hysteresis = <1000>;
+ type = "passive";
+ };
+ };
+ };
+
+ conn-thermal {
+ polling-delay-passive = <0>;
+
+ thermal-sensors = <&pm7250b_adc_tm 1>;
+
+ trips {
+ active-config0 {
+ temperature = <125000>;
+ hysteresis = <1000>;
+ type = "passive";
+ };
+ };
+ };
+
+ quiet-thermal {
+ polling-delay-passive = <0>;
+
+ thermal-sensors = <&pmk8350_adc_tm 1>;
+
+ trips {
+ active-config0 {
+ temperature = <125000>;
+ hysteresis = <1000>;
+ type = "passive";
+ };
+ };
+ };
+
+ sdm-skin-thermal {
+ polling-delay-passive = <0>;
+
+ thermal-sensors = <&pmk8350_adc_tm 3>;
+
+ trips {
+ active-config0 {
+ temperature = <125000>;
+ hysteresis = <1000>;
+ type = "passive";
+ };
+ };
+ };
+
+ xo-thermal {
+ polling-delay-passive = <0>;
+
+ thermal-sensors = <&pmk8350_adc_tm 0>;
+
+ trips {
+ active-config0 {
+ temperature = <125000>;
+ hysteresis = <1000>;
+ type = "passive";
+ };
+ };
+ };
+ };
+
+ vcc_5v0: regulator-vcc-5v-peri {
+ compatible = "regulator-fixed";
+ regulator-name = "vcc_5v0";
+ regulator-min-microvolt = <5000000>;
+ regulator-max-microvolt = <5000000>;
+
+ vin-supply = <&vph_pwr>;
+
+ regulator-boot-on;
+ regulator-always-on;
+ };
+
+ vph_pwr: regulator-vph-pwr {
+ compatible = "regulator-fixed";
+ regulator-name = "vph_pwr";
+ regulator-min-microvolt = <3700000>;
+ regulator-max-microvolt = <3700000>;
+
+ regulator-boot-on;
+ regulator-always-on;
+ };
+
+ vcc_5v0_usb2_0: regulator-vcc-5v0-usb2-0 {
+ compatible = "regulator-fixed";
+ regulator-name = "vcc_5v0_usb2_0";
+ regulator-min-microvolt = <5000000>;
+ regulator-max-microvolt = <5000000>;
+
+ vin-supply = <&vcc_5v0>;
+
+ gpio = <&tlmm 117 GPIO_ACTIVE_HIGH>;
+ enable-active-high;
+
+ pinctrl-0 = <&vcc5v0_usb2_0_en>;
+ pinctrl-names = "default";
+
+ regulator-boot-on;
+ regulator-always-on;
+ };
+
+ vcc_pcie1_3v3: regulator-vcc-pcie1-3v3 {
+ compatible = "regulator-fixed";
+ regulator-name = "vcc_pcie1_3v3";
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+
+ vin-supply = <&vcc_5v0>;
+
+ gpio = <&tlmm 115 GPIO_ACTIVE_HIGH>;
+ enable-active-high;
+
+ pinctrl-0 = <&vcc_pcie1_3v3_en>;
+ pinctrl-names = "default";
+
+ regulator-boot-on;
+ regulator-always-on;
+ };
+
+ vcc_pcie0_dsi_3v3: regulator-vcc-pcie0-dsi-3v3 {
+ compatible = "regulator-fixed";
+ regulator-name = "vcc_pcie0_dsi_3v3";
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+
+ vin-supply = <&vcc_5v0>;
+
+ gpio = <&tlmm 114 GPIO_ACTIVE_HIGH>;
+ enable-active-high;
+
+ pinctrl-0 = <&vcc_pcie0_dsi_3v3_en>;
+ pinctrl-names = "default";
+
+ regulator-boot-on;
+ regulator-always-on;
+ };
+
+ wlan_pwrseq: wlan-pwrseq {
+ compatible = "mmc-pwrseq-simple";
+
+ pinctrl-0 = <&wl_enable_h>;
+ pinctrl-names = "default";
+
+ reset-gpios = <&tlmm 84 GPIO_ACTIVE_LOW>;
+ post-power-on-delay-ms = <200>;
+ power-off-delay-us = <20000>;
+ };
+};
+
+&apps_rsc {
+ regulators-0 {
+ compatible = "qcom,pm7325-rpmh-regulators";
+ qcom,pmic-id = "b";
+
+ vdd-s1-supply = <&vph_pwr>;
+ vdd-s2-supply = <&vph_pwr>;
+ vdd-s3-supply = <&vph_pwr>;
+ vdd-s4-supply = <&vph_pwr>;
+ vdd-s5-supply = <&vph_pwr>;
+ vdd-s6-supply = <&vph_pwr>;
+ vdd-s7-supply = <&vph_pwr>;
+ vdd-s8-supply = <&vph_pwr>;
+ vdd-l1-l4-l12-l15-supply = <&vreg_s7b_0p972>;
+ vdd-l2-l7-supply = <&vreg_bob_3p296>;
+ vdd-l6-l9-l10-supply = <&vreg_s8b_1p272>;
+ vdd-l8-supply = <&vreg_s7b_0p972>;
+ vdd-l11-l17-l18-l19-supply = <&vreg_s1b_1p872>;
+ vdd-l13-supply = <&vreg_s7b_0p972>;
+ vdd-l14-l16-supply = <&vreg_s8b_1p272>;
+
+ vreg_s1b_1p872: smps1 {
+ regulator-name = "vreg_s1b_1p872";
+ regulator-min-microvolt = <1840000>;
+ regulator-max-microvolt = <2040000>;
+ };
+
+ vreg_s7b_0p972: smps7 {
+ regulator-name = "vreg_s7b_0p972";
+ regulator-min-microvolt = <535000>;
+ regulator-max-microvolt = <1120000>;
+ };
+
+ vreg_s8b_1p272: smps8 {
+ regulator-name = "vreg_s8b_1p272";
+ regulator-min-microvolt = <1200000>;
+ regulator-max-microvolt = <1500000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_RET>;
+ };
+
+ vreg_l1b_0p912: ldo1 {
+ regulator-name = "vreg_l1b_0p912";
+ regulator-min-microvolt = <825000>;
+ regulator-max-microvolt = <925000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l2b_3p072: ldo2 {
+ regulator-name = "vreg_l2b_3p072";
+ regulator-min-microvolt = <2700000>;
+ regulator-max-microvolt = <3544000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l3b_0p504: ldo3 {
+ regulator-name = "vreg_l3b_0p504";
+ regulator-min-microvolt = <312000>;
+ regulator-max-microvolt = <650000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l6b_1p2: ldo6 {
+ regulator-name = "vreg_l6b_1p2";
+ regulator-min-microvolt = <1200000>;
+ regulator-max-microvolt = <1260000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l7b_2p96: ldo7 {
+ regulator-name = "vreg_l7b_2p96";
+ regulator-min-microvolt = <2960000>;
+ regulator-max-microvolt = <2960000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ regulator-allow-set-load;
+ regulator-allowed-modes = <RPMH_REGULATOR_MODE_LPM
+ RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l8b_0p904: ldo8 {
+ regulator-name = "vreg_l8b_0p904";
+ regulator-min-microvolt = <870000>;
+ regulator-max-microvolt = <970000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l9b_1p2: ldo9 {
+ regulator-name = "vreg_l9b_1p2";
+ regulator-min-microvolt = <1200000>;
+ regulator-max-microvolt = <1200000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ regulator-allow-set-load;
+ regulator-allowed-modes = <RPMH_REGULATOR_MODE_LPM
+ RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l11b_1p504: ldo11 {
+ regulator-name = "vreg_l11b_1p504";
+ regulator-min-microvolt = <1776000>;
+ regulator-max-microvolt = <2000000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l12b_0p751: ldo12 {
+ regulator-name = "vreg_l12b_0p751";
+ regulator-min-microvolt = <751000>;
+ regulator-max-microvolt = <824000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l13b_0p53: ldo13 {
+ regulator-name = "vreg_l13b_0p53";
+ regulator-min-microvolt = <530000>;
+ regulator-max-microvolt = <824000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l14b_1p08: ldo14 {
+ regulator-name = "vreg_l14b_1p08";
+ regulator-min-microvolt = <1200000>;
+ regulator-max-microvolt = <1304000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l15b_0p765: ldo15 {
+ regulator-name = "vreg_l15b_0p765";
+ regulator-min-microvolt = <765000>;
+ regulator-max-microvolt = <1020000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l16b_1p1: ldo16 {
+ regulator-name = "vreg_l16b_1p1";
+ regulator-min-microvolt = <1100000>;
+ regulator-max-microvolt = <1300000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l17b_1p7: ldo17 {
+ regulator-name = "vreg_l17b_1p7";
+ regulator-min-microvolt = <1700000>;
+ regulator-max-microvolt = <1900000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l18b_1p8: ldo18 {
+ regulator-name = "vreg_l18b_1p8";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <2000000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l19b_1p8: ldo19 {
+ regulator-name = "vreg_l19b_1p8";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <2000000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+ };
+
+ regulators-1 {
+ compatible = "qcom,pm8350c-rpmh-regulators";
+ qcom,pmic-id = "c";
+
+ vdd-s1-supply = <&vph_pwr>;
+ vdd-s2-supply = <&vph_pwr>;
+ vdd-s3-supply = <&vph_pwr>;
+ vdd-s4-supply = <&vph_pwr>;
+ vdd-s5-supply = <&vph_pwr>;
+ vdd-s6-supply = <&vph_pwr>;
+ vdd-s7-supply = <&vph_pwr>;
+ vdd-s8-supply = <&vph_pwr>;
+ vdd-s9-supply = <&vph_pwr>;
+ vdd-s10-supply = <&vph_pwr>;
+ vdd-l1-l12-supply = <&vreg_s1b_1p872>;
+ vdd-l2-l8-supply = <&vreg_s1b_1p872>;
+ vdd-l3-l4-l5-l7-l13-supply = <&vreg_bob_3p296>;
+ vdd-l6-l9-l11-supply = <&vreg_bob_3p296>;
+ vdd-l10-supply = <&vreg_s7b_0p972>;
+ vdd-bob-supply = <&vph_pwr>;
+
+ vreg_s1c_2p19: smps1 {
+ regulator-name = "vreg_s1c_2p19";
+ regulator-min-microvolt = <2200000>;
+ regulator-max-microvolt = <2208000>;
+ };
+
+ vreg_s9c_1p084: smps9 {
+ regulator-name = "vreg_s9c_1p084";
+ regulator-min-microvolt = <1010000>;
+ regulator-max-microvolt = <1170000>;
+ };
+
+ vreg_l1c_1p8: ldo1 {
+ regulator-name = "vreg_l1c_1p8";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1980000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l2c_1p62: ldo2 {
+ regulator-name = "vreg_l2c_1p62";
+ regulator-min-microvolt = <1620000>;
+ regulator-max-microvolt = <1976000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l3c_2p8: ldo3 {
+ regulator-name = "vreg_l3c_2p8";
+ regulator-min-microvolt = <2800000>;
+ regulator-max-microvolt = <3540000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l4c_1p62: ldo4 {
+ regulator-name = "vreg_l4c_1p62";
+ regulator-min-microvolt = <1620000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l5c_1p62: ldo5 {
+ regulator-name = "vreg_l5c_1p62";
+ regulator-min-microvolt = <1620000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l6c_2p96: ldo6 {
+ regulator-name = "vreg_l6c_2p96";
+ regulator-min-microvolt = <1650000>;
+ regulator-max-microvolt = <3544000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l7c_3p0: ldo7 {
+ regulator-name = "vreg_l7c_3p0";
+ regulator-min-microvolt = <3000000>;
+ regulator-max-microvolt = <3544000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l8c_1p62: ldo8 {
+ regulator-name = "vreg_l8c_1p62";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <2000000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l9c_2p96: ldo9 {
+ regulator-name = "vreg_l9c_2p96";
+ regulator-min-microvolt = <2700000>;
+ regulator-max-microvolt = <3544000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l10c_0p88: ldo10 {
+ regulator-name = "vreg_l10c_0p88";
+ regulator-min-microvolt = <720000>;
+ regulator-max-microvolt = <1050000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l11c_2p8: ldo11 {
+ regulator-name = "vreg_l11c_2p8";
+ regulator-min-microvolt = <2800000>;
+ regulator-max-microvolt = <3544000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_l12c_1p8: ldo12 {
+ regulator-name = "vreg_l12c_1p8";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <2000000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+
+ /*
+ * VREG_L12C_1P8 supplies the Ampak WLAN/BT module
+ * VDDIO and the external 32.768 kHz oscillator.
+ */
+ regulator-always-on;
+ regulator-boot-on;
+ };
+
+ vreg_l13c_2p7: ldo13 {
+ regulator-name = "vreg_l13c_2p7";
+ regulator-min-microvolt = <2700000>;
+ regulator-max-microvolt = <3544000>;
+ regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
+ };
+
+ vreg_bob_3p296: bob {
+ regulator-name = "vreg_bob_3p296";
+ regulator-min-microvolt = <3008000>;
+ regulator-max-microvolt = <3300000>;
+ };
+ };
+};
+
+&gcc {
+ protected-clocks = <GCC_CFG_NOC_LPASS_CLK>,
+ <GCC_MSS_CFG_AHB_CLK>,
+ <GCC_MSS_GPLL0_MAIN_DIV_CLK_SRC>,
+ <GCC_MSS_OFFLINE_AXI_CLK>,
+ <GCC_MSS_Q6SS_BOOT_CLK_SRC>,
+ <GCC_MSS_Q6_MEMNOC_AXI_CLK>,
+ <GCC_MSS_SNOC_AXI_CLK>,
+ <GCC_QSPI_CNOC_PERIPH_AHB_CLK>,
+ <GCC_QSPI_CORE_CLK>,
+ <GCC_QSPI_CORE_CLK_SRC>,
+ <GCC_SEC_CTRL_CLK_SRC>,
+ <GCC_WPSS_AHB_BDG_MST_CLK>,
+ <GCC_WPSS_AHB_CLK>,
+ <GCC_WPSS_RSCP_CLK>;
+};
+
+&gpi_dma0 {
+ status = "okay";
+};
+
+&gpi_dma1 {
+ status = "okay";
+};
+
+&gpu {
+ status = "okay";
+};
+
+&gpu_zap_shader {
+ firmware-name = "qcom/qcs6490/a660_zap.mbn";
+};
+
+&ice {
+ status = "disabled";
+};
+
+&pcie0 {
+ perst-gpios = <&tlmm 87 GPIO_ACTIVE_LOW>;
+ wake-gpios = <&tlmm 89 GPIO_ACTIVE_HIGH>;
+
+ pinctrl-0 = <&pcie0_clkreq_n>, <&pcie0_reset_n>, <&pcie0_wake_n>;
+ pinctrl-names = "default";
+
+ status = "okay";
+};
+
+&pcie0_phy {
+ vdda-phy-supply = <&vreg_l10c_0p88>;
+ vdda-pll-supply = <&vreg_l6b_1p2>;
+
+ status = "okay";
+};
+
+&pcie1 {
+ perst-gpios = <&tlmm 2 GPIO_ACTIVE_LOW>;
+ wake-gpios = <&tlmm 3 GPIO_ACTIVE_HIGH>;
+
+ pinctrl-0 = <&pcie1_reset_n>, <&pcie1_wake_n>, <&pcie1_clkreq_n>;
+ pinctrl-names = "default";
+
+ status = "okay";
+};
+
+&pcie1_phy {
+ vdda-phy-supply = <&vreg_l10c_0p88>;
+ vdda-pll-supply = <&vreg_l6b_1p2>;
+
+ status = "okay";
+};
+
+&pm7250b_adc {
+ channel@4d {
+ reg = <ADC5_AMUX_THM1_100K_PU>;
+ qcom,ratiometric;
+ qcom,hw-settle-time = <200>;
+ qcom,pre-scaling = <1 1>;
+ label = "charger_skin_therm";
+ };
+
+ channel@4f {
+ reg = <ADC5_AMUX_THM3_100K_PU>;
+ qcom,ratiometric;
+ qcom,hw-settle-time = <200>;
+ qcom,pre-scaling = <1 1>;
+ label = "conn_therm";
+ };
+};
+
+&pm7250b_adc_tm {
+ status = "okay";
+
+ charger-skin-therm@0 {
+ reg = <0>;
+ io-channels = <&pm7250b_adc ADC5_AMUX_THM1_100K_PU>;
+ qcom,ratiometric;
+ qcom,hw-settle-time-us = <200>;
+ };
+
+ conn-therm@1 {
+ reg = <1>;
+ io-channels = <&pm7250b_adc ADC5_AMUX_THM3_100K_PU>;
+ qcom,ratiometric;
+ qcom,hw-settle-time-us = <200>;
+ };
+};
+
+&pm7325_temp_alarm {
+ io-channels = <&pmk8350_vadc PM7325_ADC7_DIE_TEMP>;
+ io-channel-names = "thermal";
+};
+
+&pmk8350_adc_tm {
+ status = "okay";
+
+ xo-therm@0 {
+ reg = <0>;
+ io-channels = <&pmk8350_vadc PMK8350_ADC7_AMUX_THM1_100K_PU>;
+ qcom,ratiometric;
+ qcom,hw-settle-time-us = <200>;
+ };
+
+ quiet-therm@1 {
+ reg = <1>;
+ io-channels = <&pmk8350_vadc PM7325_ADC7_AMUX_THM1_100K_PU>;
+ qcom,ratiometric;
+ qcom,hw-settle-time-us = <200>;
+ };
+
+ sdm-skin-therm@3 {
+ reg = <3>;
+ io-channels = <&pmk8350_vadc PM7325_ADC7_AMUX_THM3_100K_PU>;
+ qcom,ratiometric;
+ qcom,hw-settle-time-us = <200>;
+ };
+};
+
+&pmk8350_rtc {
+ status = "okay";
+};
+
+&pmk8350_vadc {
+ status = "okay";
+
+ channel@3 {
+ reg = <PMK8350_ADC7_DIE_TEMP>;
+ qcom,pre-scaling = <1 1>;
+ label = "pmk8350_die_therm";
+ };
+
+ channel@44 {
+ reg = <PMK8350_ADC7_AMUX_THM1_100K_PU>;
+ qcom,ratiometric;
+ qcom,hw-settle-time = <200>;
+ qcom,pre-scaling = <1 1>;
+ label = "pmk8350_xo_therm";
+ };
+
+ channel@103 {
+ reg = <PM7325_ADC7_DIE_TEMP>;
+ qcom,pre-scaling = <1 1>;
+ label = "pm7325_die_therm";
+ };
+
+ channel@144 {
+ reg = <PM7325_ADC7_AMUX_THM1_100K_PU>;
+ qcom,ratiometric;
+ qcom,hw-settle-time = <200>;
+ qcom,pre-scaling = <1 1>;
+ label = "pm7325_quiet_therm";
+ };
+
+ channel@146 {
+ reg = <PM7325_ADC7_AMUX_THM3_100K_PU>;
+ qcom,ratiometric;
+ qcom,hw-settle-time = <200>;
+ qcom,pre-scaling = <1 1>;
+ label = "pm7325_sdm_skin_therm";
+ };
+};
+
+&pon_pwrkey {
+ status = "okay";
+};
+
+&pon_resin {
+ linux,code = <KEY_RESTART>;
+
+ status = "okay";
+};
+
+&qupv3_id_0 {
+ firmware-name = "qcom/qcs6490/qupv3fw.elf";
+
+ status = "okay";
+};
+
+&qupv3_id_1 {
+ firmware-name = "qcom/qcs6490/qupv3fw.elf";
+
+ status = "okay";
+};
+
+&remoteproc_adsp {
+ firmware-name = "qcom/qcs6490/adsp.mbn";
+
+ status = "okay";
+};
+
+&remoteproc_cdsp {
+ firmware-name = "qcom/qcs6490/cdsp.mbn";
+
+ status = "okay";
+};
+
+&sdhc_1 {
+ vqmmc-supply = <&vreg_l19b_1p8>;
+ vmmc-supply = <&vreg_bob_3p296>;
+
+ non-removable;
+ no-sd;
+ no-sdio;
+
+ status = "okay";
+};
+
+&sdhc_2 {
+ vqmmc-supply = <&vreg_l2c_1p62>;
+ vmmc-supply = <&vreg_l6c_2p96>;
+
+ mmc-pwrseq = <&wlan_pwrseq>;
+
+ bus-width = <4>;
+ non-removable;
+ no-sd;
+ no-mmc;
+ cap-sdio-irq;
+ keep-power-in-suspend;
+ max-frequency = <100000000>;
+
+ status = "okay";
+};
+
+&tlmm {
+ gpio-reserved-ranges = <48 4>; /* NFC */
+
+ pcie0_reset_n: pcie0-reset-n-state {
+ pins = "gpio87";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-disable;
+ };
+
+ pcie0_wake_n: pcie0-wake-n-state {
+ pins = "gpio89";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-pull-up;
+ };
+
+ pcie1_reset_n: pcie1-reset-n-state {
+ pins = "gpio2";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-disable;
+ };
+
+ pcie1_wake_n: pcie1-wake-n-state {
+ pins = "gpio3";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-pull-up;
+ };
+
+ user_leds: user-led-state {
+ pins = "gpio19", "gpio55";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-disable;
+ };
+
+ vcc5v0_usb2_0_en: vcc-5v0-usb2-0-en-state {
+ pins = "gpio117";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-disable;
+ };
+
+ vcc_pcie0_dsi_3v3_en: vcc-pcie0-dsi-3v3-en-state {
+ pins = "gpio114";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-disable;
+ };
+
+ vcc_pcie1_3v3_en: vcc-pcie1-3v3-en-state {
+ pins = "gpio115";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-disable;
+ };
+
+ wl_enable_h: wl-enable-h-state {
+ pins = "gpio84";
+ function = "gpio";
+ drive-strength = <8>;
+ bias-disable;
+ };
+};
+
+&uart5 {
+ status = "okay";
+};
+
+&ufs_mem_hc {
+ /delete-property/ qcom,ice;
+
+ reset-gpios = <&tlmm 175 GPIO_ACTIVE_LOW>;
+ vcc-supply = <&vreg_l7b_2p96>;
+ vcc-max-microamp = <800000>;
+ vccq-supply = <&vreg_l9b_1p2>;
+ vccq-max-microamp = <900000>;
+ vccq2-supply = <&vreg_l9b_1p2>;
+ vccq2-max-microamp = <900000>;
+
+ status = "okay";
+};
+
+&ufs_mem_phy {
+ vdda-phy-supply = <&vreg_l10c_0p88>;
+ vdda-pll-supply = <&vreg_l6b_1p2>;
+
+ status = "okay";
+};
+
+&usb_2 {
+ /* Routed to an onboard USB hub for two USB-A host ports. */
+ dr_mode = "host";
+
+ status = "okay";
+};
+
+&usb_2_hsphy {
+ vdda-pll-supply = <&vreg_l10c_0p88>;
+ vdda33-supply = <&vreg_l2b_3p072>;
+ vdda18-supply = <&vreg_l1c_1p8>;
+
+ status = "okay";
+};
+
+&venus {
+ status = "okay";
+};
+
+/* pinctrl */
+&pcie0_clkreq_n {
+ bias-pull-up;
+ drive-strength = <2>;
+};
+
+&pcie1_clkreq_n {
+ bias-pull-up;
+ drive-strength = <2>;
+};
+
+&sdc1_clk {
+ bias-disable;
+ drive-strength = <16>;
+};
+
+&sdc1_cmd {
+ bias-pull-up;
+ drive-strength = <10>;
+};
+
+&sdc1_data {
+ bias-pull-up;
+ drive-strength = <10>;
+};
+
+&sdc1_rclk {
+ bias-pull-down;
+};
+
+&sdc2_clk {
+ bias-disable;
+ drive-strength = <16>;
+};
+
+&sdc2_cmd {
+ bias-pull-up;
+ drive-strength = <10>;
+};
+
+&sdc2_data {
+ bias-pull-up;
+ drive-strength = <10>;
+};
--
2.50.1 (Apple Git-155)
^ permalink raw reply related
* [PATCH v5 2/3] dt-bindings: arm: qcom: Add Vicharak Axon Mini
From: Ajit Singh @ 2026-06-24 12:54 UTC (permalink / raw)
To: Bjorn Andersson, Konrad Dybcio
Cc: Dmitry Baryshkov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
linux-arm-msm, devicetree, linux-kernel, Ajit Singh,
Krzysztof Kozlowski
In-Reply-To: <20260624125443.18729-1-blfizzyy@gmail.com>
The Vicharak Axon Mini is a single-board computer based on the
Qualcomm QCM6490 platform.
Add the top-level compatible string for this board.
Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Signed-off-by: Ajit Singh <blfizzyy@gmail.com>
---
Documentation/devicetree/bindings/arm/qcom.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/arm/qcom.yaml b/Documentation/devicetree/bindings/arm/qcom.yaml
index d48c625d3fc4..6924bfe7b949 100644
--- a/Documentation/devicetree/bindings/arm/qcom.yaml
+++ b/Documentation/devicetree/bindings/arm/qcom.yaml
@@ -361,6 +361,7 @@ properties:
- radxa,dragon-q6a
- shift,otter
- thundercomm,rubikpi3
+ - vicharak,axon-mini
- const: qcom,qcm6490
- description: Qualcomm Technologies, Inc. Distributed Unit 1000 platform
--
2.50.1 (Apple Git-155)
^ permalink raw reply related
* [PATCH v5 1/3] dt-bindings: vendor-prefixes: Add prefix for Vicharak
From: Ajit Singh @ 2026-06-24 12:54 UTC (permalink / raw)
To: Bjorn Andersson, Konrad Dybcio
Cc: Dmitry Baryshkov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
linux-arm-msm, devicetree, linux-kernel, Ajit Singh,
Krzysztof Kozlowski
In-Reply-To: <20260624125443.18729-1-blfizzyy@gmail.com>
Vicharak develops computing platforms and manufactures single-board
computers, including FPGA-integrated SBCs. Add a vendor prefix for
them.
Link: https://vicharak.in/
Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Signed-off-by: Ajit Singh <blfizzyy@gmail.com>
---
Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index ee7fd3cfe203..1948356337b9 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -1767,6 +1767,8 @@ patternProperties:
description: VIA Technologies, Inc.
"^vialab,.*":
description: VIA Labs, Inc.
+ "^vicharak,.*":
+ description: Vicharak Computers Pvt. Ltd.
"^vicor,.*":
description: Vicor Corporation
"^videostrong,.*":
--
2.50.1 (Apple Git-155)
^ permalink raw reply related
* [PATCH v5 0/3] arm64: dts: qcom: Add Vicharak Axon Mini
From: Ajit Singh @ 2026-06-24 12:54 UTC (permalink / raw)
To: Bjorn Andersson, Konrad Dybcio
Cc: Dmitry Baryshkov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
linux-arm-msm, devicetree, linux-kernel, Ajit Singh
Add initial support for the Vicharak Axon Mini, a QCS6490-based
single-board computer.
This series adds the Vicharak vendor prefix, documents the board
compatible, and adds the initial board DTS.
Tested:
- debug UART
- eMMC
- UFS
- SDIO WLAN
- USB 2.0 host
- PCIe
---
v4: https://lore.kernel.org/all/20260607113658.25117-1-blfizzyy@gmail.com/
Changes in v5:
- Drop regulator-always-on/regulator-boot-on from vreg_l16b_1p1
and vreg_l17b_1p7.
- Remove the unused vcc_3v3_en pinctrl state.
- Pick up Dmitry's Reviewed-by tag.
Changes in v4:
- Move pinctrl-related changes under a /* pinctrl */ section.
- Explain why UFS ICE is kept disabled in commit msg.
- Add a comment describing the USB 2.0 host-only board routing.
Changes in v3:
- Dropped unused regulators.
- Pick up Acked-by tags for the binding patches.
Changes in v2:
- Drop unused Type-C VBUS regulator.
- Drop invalid camera thermal zone.
- Drop incorrect PM8350C thermal alarm override.
- Fix PCIe1 3.3 V regulator name.
- Drop redundant EUD disable override.
- Keep ICE disabled due to fatal SError during qcom_ice_create().
- Fix pinctrl property ordering.
- Sort top-level label references.
- Add blank lines before status properties.
Ajit Singh (3):
dt-bindings: vendor-prefixes: Add prefix for Vicharak
dt-bindings: arm: qcom: Add Vicharak Axon Mini
arm64: dts: qcom: Add Vicharak Axon Mini
.../devicetree/bindings/arm/qcom.yaml | 1 +
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
arch/arm64/boot/dts/qcom/Makefile | 1 +
.../dts/qcom/qcs6490-vicharak-axon-mini.dts | 1014 +++++++++++++++++
4 files changed, 1018 insertions(+)
create mode 100644 arch/arm64/boot/dts/qcom/qcs6490-vicharak-axon-mini.dts
--
2.50.1 (Apple Git-155)
^ permalink raw reply
* Re: [PATCH 1/2] dt-bindings: thermal: amlogic: Fix missing header in the example
From: Rob Herring @ 2026-06-24 12:53 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Guillaume La Roque, Rafael J. Wysocki, Daniel Lezcano, Zhang Rui,
Lukasz Luba, Krzysztof Kozlowski, Conor Dooley, Ronald Claveau,
linux-pm, linux-amlogic, devicetree, linux-kernel
In-Reply-To: <20260622100231.438435-3-krzysztof.kozlowski@oss.qualcomm.com>
On Mon, Jun 22, 2026 at 12:02:32PM +0200, Krzysztof Kozlowski wrote:
> Usage of defines from headers requires including relevant header,
> otherwise dt_binding_check fails:
>
> Lexical error: Documentation/devicetree/bindings/thermal/amlogic,thermal.example.dts:59.27-34 Unexpected 'GIC_SPI'
> Lexical error: Documentation/devicetree/bindings/thermal/amlogic,thermal.example.dts:59.38-57 Unexpected 'IRQ_TYPE_LEVEL_HIGH'
> Lexical error: Documentation/devicetree/bindings/thermal/amlogic,thermal.example.dts:60.37-45 Unexpected 'CLKID_TS'
>
> Fixes: b1c8ccdbd4e9 ("dt-bindings: thermal: amlogic: Add support for T7")
> Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
>
> ---
>
> Fix for current RC - commit already pulled in merge window.
>
> This should be applied fast to fix current RC, thus maybe Rob?
Both applied.
Rob
^ permalink raw reply
* Re: [PATCH v3 1/7] dt-bindings: serial: 8250: aspeed: add compatible string for ast2600
From: Grégoire Layet @ 2026-06-24 12:48 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: joel, andrew, lkundrak, devicetree, gregkh, jirislaby, robh,
krzk+dt, conor+dt, andrew, jacky_chou, yh_chung, ninad,
anirudhsriniv, linux-serial, linux-aspeed, linux-arm-kernel,
linux-kernel
In-Reply-To: <20260624-realistic-spiked-parrot-db1d9c@quoll>
Hi Krzysztof,
> Do not attach (thread) your patchsets to some other threads (unrelated
> or older versions). This buries them deep in the mailbox and might
> interfere with applying entire sets. See also:
> https://elixir.bootlin.com/linux/v6.16-rc2/source/Documentation/process/submitting-patches.rst#L830
Oh okay sorry I missed this information. Thank's for letting me know !
> This should be oneOf (by convention and actually more accurate meaning).
Acknowledged
> More important, where is documenting of the actual compatible?
Yes, you are right, I missed it. Will be added in v4.
Best regards,
Grégoire
^ permalink raw reply
* Re: [PATCH v3 2/7] dt-bindings: serial: 8250: aspeed: add aspeed,vuart-over-pci bool prop
From: Grégoire Layet @ 2026-06-24 12:48 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: joel, andrew, lkundrak, devicetree, gregkh, jirislaby, robh,
krzk+dt, conor+dt, andrew, jacky_chou, yh_chung, ninad,
anirudhsriniv, linux-serial, linux-aspeed, linux-arm-kernel,
linux-kernel
In-Reply-To: <20260624-original-vigorous-mayfly-dfceac@quoll>
Hi Krzysztof,
> What does that mean? How UART can be accessible over PCI bus?
It's a Virtual UART. Internally, it's two FIFOs accessible via
8250-compatible register sets on both ends.
There is 4 Virtuals UARTs on the LPC bus of the AST2600 and 2 of them
are bridged over the PCI bus.
So, from the host, you can access the 8250 register set on the PCI bus.
> > + aspeed,vuart-over-pci:
> > + type: boolean
> > + default: false
>
> There is no such syntax. Please do not introduce own style. Instead,
> look at other files how this is done.
Ack. I will remove 'default: false' for the v4.
> > + description: |
>
> Do not need '|' unless you need to preserve formatting.
Acknowledged
Best regards,
Grégoire
^ permalink raw reply
* [PATCH v2] arm64: dts: qcom: glymur: Add label properties to CoreSight devices
From: Jie Gan @ 2026-06-24 12:38 UTC (permalink / raw)
To: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Tingwei Zhang
Cc: linux-arm-msm, devicetree, linux-kernel, Jie Gan
Add label properties to TPDM and CTI nodes in the glymur device tree to
provide human-readable identifiers for each CoreSight device. These
labels allow userspace tools and the CoreSight framework to identify
devices by name rather than by base address.
Signed-off-by: Jie Gan <jie.gan@oss.qualcomm.com>
---
Changes in v2:
- fix typo in commit message: "hamoa" -> "glymur"
- Link to v1: https://lore.kernel.org/r/20260624-add-label-node-for-glymur-v1-1-87576107b999@oss.qualcomm.com
---
arch/arm64/boot/dts/qcom/glymur.dtsi | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/glymur.dtsi b/arch/arm64/boot/dts/qcom/glymur.dtsi
index 20b49af7298e..27cc30de940e 100644
--- a/arch/arm64/boot/dts/qcom/glymur.dtsi
+++ b/arch/arm64/boot/dts/qcom/glymur.dtsi
@@ -5770,6 +5770,7 @@ tpdm@1000f000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ label = "tpdm_spdm";
qcom,cmb-element-bits = <32>;
qcom,cmb-msrs-num = <32>;
@@ -5834,6 +5835,7 @@ tpdm@1102c000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ label = "tpdm_gcc";
qcom,dsb-msrs-num = <32>;
@@ -5852,6 +5854,7 @@ tpdm@11180000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ label = "tpdm_cdsp";
qcom,dsb-element-bits = <32>;
qcom,dsb-msrs-num = <32>;
@@ -5871,6 +5874,7 @@ tpdm@11185000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ label = "tpdm_cdsp_dpm_1";
qcom,cmb-element-bits = <64>;
qcom,cmb-msrs-num = <32>;
@@ -5890,6 +5894,7 @@ tpdm@11186000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ label = "tpdm_cdsp_dpm_2";
qcom,cmb-element-bits = <64>;
qcom,cmb-msrs-num = <32>;
@@ -6010,6 +6015,7 @@ cti@11193000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ label = "cti_cdsp";
};
cti_wpss: cti@111ab000 {
@@ -6018,6 +6024,7 @@ cti_wpss: cti@111ab000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ label = "cti_wpss";
};
tpdm@111d0000 {
@@ -6026,6 +6033,7 @@ tpdm@111d0000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ label = "tpdm_qm";
qcom,dsb-msrs-num = <32>;
@@ -6201,6 +6209,7 @@ tpdm@11207000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ label = "tpdm_mm_dsb";
qcom,dsb-msrs-num = <32>;
@@ -6219,6 +6228,7 @@ tpdm@1120b000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ label = "tpdm_east_dsb";
qcom,dsb-msrs-num = <32>;
@@ -6237,6 +6247,7 @@ tpdm@11213000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ label = "tpdm_west_dsb";
qcom,dsb-msrs-num = <32>;
@@ -6255,6 +6266,7 @@ tpdm@11219000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ label = "tpdm_center_dsb";
qcom,dsb-msrs-num = <32>;
@@ -6273,6 +6285,7 @@ tpdm@1121a000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ label = "tpdm_ipcc";
qcom,cmb-msrs-num = <32>;
@@ -6291,6 +6304,7 @@ tpdm@1121b000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ label = "tpdm_qrng";
qcom,cmb-msrs-num = <32>;
@@ -6309,6 +6323,7 @@ tpdm@1121c000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ label = "tpdm_pmu";
qcom,dsb-msrs-num = <32>;
@@ -6327,6 +6342,7 @@ tpdm@1121d000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ label = "tpdm_rdpm_cx";
qcom,cmb-msrs-num = <32>;
@@ -6345,6 +6361,7 @@ tpdm@1121e000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ label = "tpdm_rdpm_mxc";
qcom,cmb-msrs-num = <32>;
@@ -6363,6 +6380,7 @@ tpdm@1121f000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ label = "tpdm_rdpm_mxa";
qcom,cmb-msrs-num = <32>;
@@ -6381,6 +6399,7 @@ tpdm@11220000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ label = "tpdm_center_dsb_1";
qcom,dsb-msrs-num = <32>;
@@ -6399,6 +6418,7 @@ tpdm@11224000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ label = "tpdm_south2_dsb";
qcom,dsb-msrs-num = <32>;
@@ -6417,6 +6437,7 @@ tpdm@11228000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ label = "tpdm_south_dsb";
qcom,dsb-msrs-num = <32>;
@@ -6435,6 +6456,7 @@ tpdm@11470000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ label = "tpdm_pcie_rscc";
qcom,cmb-element-bits = <32>;
qcom,cmb-msrs-num = <32>;
@@ -6478,6 +6500,7 @@ tpdm@11c03000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ label = "tpdm_swao_prio_4";
qcom,cmb-element-bits = <64>;
qcom,cmb-msrs-num = <32>;
@@ -6656,6 +6679,7 @@ tpdm@11c09000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ label = "tpdm_swao_prio_0";
qcom,cmb-element-bits = <64>;
qcom,cmb-msrs-num = <32>;
@@ -6675,6 +6699,7 @@ tpdm@11c0a000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ label = "tpdm_swao_prio_1";
qcom,cmb-element-bits = <64>;
qcom,cmb-msrs-num = <32>;
@@ -6694,6 +6719,7 @@ tpdm@11c0b000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ label = "tpdm_swao_prio_2";
qcom,cmb-element-bits = <64>;
qcom,cmb-msrs-num = <32>;
@@ -6713,6 +6739,7 @@ tpdm@11c0c000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ label = "tpdm_swao_prio_3";
qcom,cmb-element-bits = <64>;
qcom,cmb-msrs-num = <32>;
@@ -6732,6 +6759,7 @@ tpdm@11c0d000 {
clocks = <&aoss_qmp>;
clock-names = "apb_pclk";
+ label = "tpdm_swao";
qcom,dsb-element-bits = <32>;
qcom,dsb-msrs-num = <32>;
---
base-commit: 4e5dfb7c84012007c3c7061126491bbc92d71bf1
change-id: 20260624-add-label-node-for-glymur-1ba59f479870
Best regards,
--
Jie Gan <jie.gan@oss.qualcomm.com>
^ permalink raw reply related
* Re: [PATCH v6 02/10] arm64: tegra: Remove fallback compatible for GPCDMA
From: Thierry Reding @ 2026-06-24 12:35 UTC (permalink / raw)
To: Rob Herring
Cc: Akhil R, Thierry Reding, Vinod Koul, Frank Li,
Krzysztof Kozlowski, Conor Dooley, Jonathan Hunter,
Laxman Dewangan, Philipp Zabel, dmaengine, devicetree,
linux-tegra, linux-kernel
In-Reply-To: <ajuv2CVQ-b978cn6@orome>
[-- Attachment #1: Type: text/plain, Size: 967 bytes --]
On Wed, Jun 24, 2026 at 12:22:38PM +0200, Thierry Reding wrote:
> On Tue, Jun 23, 2026 at 09:02:39AM -0500, Rob Herring wrote:
> > On Tue, Mar 31, 2026 at 5:24 AM Akhil R <akhilrajeev@nvidia.com> wrote:
> > >
> > > Remove the fallback compatible string "nvidia,tegra186-gpcdma" for GPCDMA
> > > in Tegra264. Tegra186 compatible cannot work on Tegra264 because of the
> > > register offset changes and absence of the reset property.
> > >
> > > Fixes: 65ef237e4810 ("arm64: tegra: Add Tegra264 support")
> > > Signed-off-by: Akhil R <akhilrajeev@nvidia.com>
> > > ---
> > > arch/arm64/boot/dts/nvidia/tegra264.dtsi | 2 +-
> > > 1 file changed, 1 insertion(+), 1 deletion(-)
> >
> > Thierry, Are you going to apply this? The binding change has been
> > picked up and now there's a warning.
>
> Yes, I have this in my tree of fixes for 7.1 and plan to send it out
> towards the end of this week.
Sorry, fixes for 7.2-rc1, that is.
Thierry
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply
* Re: [PATCH v7 3/4] PCI: tegra: Add Tegra264 support
From: Thierry Reding @ 2026-06-24 12:35 UTC (permalink / raw)
To: Manivannan Sadhasivam
Cc: Bjorn Helgaas, Lorenzo Pieralisi, Krzysztof Wilczyński,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
Jonathan Hunter, Karthikeyan Mitran, Hou Zhiqiang,
Thomas Petazzoni, Pali Rohár, Michal Simek, Kevin Xie,
Aksh Garg, linux-pci, devicetree, linux-tegra, linux-kernel,
linux-arm-kernel, Thierry Reding, Manikanta Maddireddy
In-Reply-To: <slfaxyt6p5mwsqmxvmriy6npilpjhjxv5ruegj4hnivj6zufkl@o6adjy5jmzfy>
[-- Attachment #1: Type: text/plain, Size: 8181 bytes --]
On Thu, Jun 18, 2026 at 09:26:33AM +0200, Manivannan Sadhasivam wrote:
> On Wed, Jun 17, 2026 at 06:01:30PM +0200, Thierry Reding wrote:
[...]
> > +static int tegra264_pcie_parse_dt(struct tegra264_pcie *pcie)
> > +{
> > + struct device *dev = pcie->dev;
> > + int err;
> > +
> > + pcie->wake_gpio = devm_gpiod_get_optional(dev, "wake", GPIOD_IN);
> > + if (IS_ERR(pcie->wake_gpio))
> > + return PTR_ERR(pcie->wake_gpio);
> > +
> > + if (!pcie->wake_gpio)
> > + return 0;
> > +
> > + err = gpiod_to_irq(pcie->wake_gpio);
> > + if (err < 0)
> > + return dev_err_probe(dev, err, "failed to get wake IRQ\n");
> > +
> > + pcie->wake_irq = (unsigned int)err;
> > +
> > + err = devm_device_init_wakeup(dev);
> > + if (err < 0)
> > + return dev_err_probe(dev, err, "failed to initialize wakeup\n");
> > +
> > + err = devm_pm_set_wake_irq(dev, pcie->wake_irq);
> > + if (err < 0)
> > + return dev_err_probe(dev, err, "failed to set wakeup IRQ\n");
> > +
>
> I'd really like to get rid of custom WAKE# handling in the controller drivers.
> Krishna is trying to add generic WAKE# handling in the PCI core and I'd suggest
> you to take a look at the patches:
> https://lore.kernel.org/linux-pci/20260511-wakeirq_support-v10-2-c10af9c9eb8c@oss.qualcomm.com/
>
> But this also means that you need to use switch to Root Port binding to move the
> Root Port properties out of the controller node. This is something we are
> mandating for the new controllers. Not a big change though...
>
> Reference:
>
> Documentation/devicetree/bindings/pci/spacemit,k1-pcie-host.yaml#n80
Okay, let me look into it.
[...]
> > + err = devm_pm_runtime_set_active_enabled(dev);
>
> I belive this has to come after pm_runtime_get_sync() because at this point, the
> controller is not enabled.
Okay, I'll change that.
> > + if (err < 0) {
> > + dev_err_probe(dev, err, "failed to enable runtime PM\n");
> > + goto put_bpmp;
> > + }
> > +
> > + err = pm_runtime_get_sync(dev);
> > + if (err < 0) {
> > + dev_err_probe(dev, err, "failed to power on device\n");
> > + goto put_bpmp;
> > + }
> > +
> > + /* sanity check that programmed ranges match what's in DT */
> > + if (!tegra264_pcie_check_ranges(pdev)) {
> > + err = -EINVAL;
> > + goto put_pm;
> > + }
> > +
> > + pcie->cfg = pci_ecam_create(dev, res, bus->res, &pci_generic_ecam_ops);
> > + if (IS_ERR(pcie->cfg)) {
> > + err = dev_err_probe(dev, PTR_ERR(pcie->cfg),
> > + "failed to create ECAM\n");
> > + goto put_pm;
> > + }
> > +
> > + bridge->ops = (struct pci_ops *)&pci_generic_ecam_ops.pci_ops;
> > + bridge->sysdata = pcie->cfg;
> > + pcie->ecam = pcie->cfg->win;
> > +
> > + tegra264_pcie_init(pcie);
> > +
> > + if (!pcie->link_up)
> > + return 0;
>
> So not hotplug support? Also, you do not want the driver to error out? I'm
> wondering what's the use then?
Hotplug is supported via pciehp. We skip probing the host bridge if no
link was detected because there's simply nothing attached to the port,
otherwise the link would've come up.
pcie->link_up is slightly misleading because it actually means something
along the lines of "link could be up at some point", either during probe
or after some hotplug event later on. It is only ever false if there's
no link during probe and hotplug isn't supported at all.
> > +
> > + err = pci_host_probe(bridge);
> > + if (err < 0) {
> > + dev_err_probe(dev, err, "failed to register host\n");
> > + goto free_ecam;
> > + }
> > +
> > + return 0;
> > +
> > +free_ecam:
>
> Nit: Prefix 'err' for the labels.
I don't see any benefit of adding a prefix. Seems pretty redundant, but
I also don't feel too strongly about it, so I can add it.
> > + pci_ecam_free(pcie->cfg);
> > +put_pm:
> > + pm_runtime_put_sync(dev);
> > +put_bpmp:
> > + tegra_bpmp_put(pcie->bpmp);
> > +
> > + return err;
> > +}
> > +
> > +static void tegra264_pcie_remove(struct platform_device *pdev)
> > +{
> > + struct tegra264_pcie *pcie = platform_get_drvdata(pdev);
> > +
> > + /*
> > + * If we undo tegra264_pcie_init() then link goes down and need
> > + * controller reset to bring up the link again. Remove intention is
> > + * to clean up the root bridge and re-enumerate during bind.
>
> But the controller will be consuming power even if PCIe is not used. Do you
> really want that? Can't tegra264_pcie_init() handle the initialization? I'm
> wondering how tegra264_pcie_deinit() in tegra264_pcie_suspend() works then.
I had to clarify this with the PCI team and they indicated that
tegra264_pcie_deinit() is actually useless and maybe even harmful. The
reason is that there's a processor on these boards (BPMP) that takes
care of power sequencing and it will automatically take the PCI links
to L2 on suspend and assert PERST#.
Another reason why we don't want to reset the entire controller is that
it is already set up during early boot by UEFI and the kernel driver
does not redo the entire initialization.
So yes, I think a little bit of power consumption is the compromise that
we will have to live with. In the bigger picture it's probably not going
to be noticeable in most cases, and given that these are embedded
platforms we'll likely see fixed configurations most of the time and the
case where we remove the PCIe host controller will not be common.
> > + */
> > + pci_lock_rescan_remove();
> > + pci_stop_root_bus(pcie->bridge->bus);
> > + pci_remove_root_bus(pcie->bridge->bus);
> > + pci_unlock_rescan_remove();
> > +
> > + pm_runtime_put_sync(&pdev->dev);
> > + tegra_bpmp_put(pcie->bpmp);
> > + pci_ecam_free(pcie->cfg);
> > +}
> > +
> > +static int tegra264_pcie_suspend(struct device *dev)
> > +{
> > + struct tegra264_pcie *pcie = dev_get_drvdata(dev);
> > + int err;
> > +
> > + tegra264_pcie_deinit(pcie);
> > +
> > + if (pcie->wake_gpio && device_may_wakeup(dev)) {
> > + err = enable_irq_wake(pcie->wake_irq);
> > + if (err < 0)
> > + dev_err(dev, "failed to enable wake IRQ: %pe\n",
> > + ERR_PTR(err));
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int tegra264_pcie_resume(struct device *dev)
> > +{
> > + struct tegra264_pcie *pcie = dev_get_drvdata(dev);
> > + int err;
> > +
> > + err = pinctrl_pm_select_default_state(dev);
> > + if (err < 0)
> > + dev_err(dev, "failed to configure sideband pins: %pe\n",
> > + ERR_PTR(err));
>
> Please remind me if you justified this manual pinctrl handling before.
This is just regular pinctrl PM boilerplate. There's plenty of other
drivers where we do this, too. We want this because some of the pins
get configured to non-default states on boot/resume, so doing this
here ensures they are muxed correctly.
> > +
> > + if (pcie->wake_gpio && device_may_wakeup(dev)) {
> > + err = disable_irq_wake(pcie->wake_irq);
> > + if (err < 0)
> > + dev_err(dev, "failed to disable wake IRQ: %pe\n",
> > + ERR_PTR(err));
> > + }
> > +
> > + if (pcie->link_up == false)
> > + return 0;
>
> How is this possible? If 'pcie->link_up' was 'false' during probe(), then it is
> going to stay until tegra264_pcie_init() is called below.
Yes, this keeps confusing me, too. The purpose of this is to skip
initialization if we've already determined during probe that there is
never going to be a link. link_up will be false if and only if there was
no link during probe and we don't expect there ever will be a link
because there is no hotplug support.
Maybe a different name for link_up could help here? maybe_link_up
perhaps? I don't know if that's any clearer, but I also couldn't come up
with a better name.
Or maybe we should split this into two booleans, since we're essentially
trying to use one boolean to track a tristate. What we want to know is
if a link is truly up and if the controller should be kept powered for
the case where hotplug is supported.
I suppose we could do:
bool link_up; /* track the link state */
bool supports_hotplug; /* track whether port is hotpluggable */
That would make the code a bit cleaner and easier to follow.
Thierry
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply
* Re: [PATCH v4 5/5] clk: rockchip: rk3588: add GATE_GRF clocks for I2S MCLK output to IO
From: Daniele Briguglio @ 2026-06-24 12:20 UTC (permalink / raw)
To: Heiko Stuebner, Diederik de Haas, Michael Turquette, Stephen Boyd,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Nicolas Frattaroli, linux-clk, devicetree, linux-arm-kernel,
linux-rockchip, linux-kernel, Ricardo Pardini
In-Reply-To: <2008560.6tgchFWduM@diego>
Hi Heiko,
> Care to send a patch for that change? :-)
Will do. I was only waiting for your call on the approach before sending,
so I'll get it out shortly.
Best regards,
Daniele
^ permalink raw reply
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