Devicetree
 help / color / mirror / Atom feed
* Re: [PATCH 1/2] dt-bindings: thermal: Add Qualcomm MBG thermal monitor support
From: Sachin Gupta @ 2026-06-23 10:14 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba,
	Stephen Boyd, Jishnu Prakash, Kamal Wadhwa, Amit Kucheria,
	Thara Gopinath, linux-arm-msm, devicetree, linux-kernel, linux-pm,
	Satya Priya Kakitapalli, Ajit Pandey, Imran Shaik, Taniya Das,
	Jagadeesh Kona
In-Reply-To: <97a16bfa-2315-47f7-ac5e-b130e7fdcf26@kernel.org>



On 6/19/2026 6:36 PM, Krzysztof Kozlowski wrote:
> On 19/06/2026 08:44, Sachin Gupta wrote:
>>>> index 000000000000..a0ecc9f35cf6
>>>> --- /dev/null
>>>> +++ b/Documentation/devicetree/bindings/thermal/qcom-spmi-mbg-tm.yaml
>>>
>>> Filename must match compatible.
>>>
>>
>> Thanks for the review. I can rename this to match the compatible naming,
>> but wanted your preference on scope:
>>
>> Should I use a generic naming scheme (qcom,spmi-mbg-tm.yaml with
>> matching compatible), or make it PMIC-specific (qcom,pm8775-mbg-tm.yaml).
> 
> The second one, please.
> 
> 
> 
> Best regards,
> Krzysztof

Sure, will update in next patch-series.

Thanks,
Sachin

^ permalink raw reply

* Re: [PATCH 2/2] thermal: qcom: Add support for Qualcomm MBG thermal monitoring
From: Sachin Gupta @ 2026-06-23 10:14 UTC (permalink / raw)
  To: Konrad Dybcio, Lee Jones, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Rafael J. Wysocki, Daniel Lezcano, Zhang Rui,
	Lukasz Luba, Stephen Boyd, Jishnu Prakash, Kamal Wadhwa,
	Amit Kucheria, Thara Gopinath
  Cc: linux-arm-msm, devicetree, linux-kernel, linux-pm,
	Satya Priya Kakitapalli, Ajit Pandey, Imran Shaik, Taniya Das,
	Jagadeesh Kona
In-Reply-To: <5ad33cf2-3168-44f2-89fe-a7fe1420f8f4@oss.qualcomm.com>



On 6/19/2026 5:44 PM, Konrad Dybcio wrote:
> On 6/19/26 8:45 AM, Sachin Gupta wrote:
>>
>>
>> On 6/16/2026 3:40 PM, Konrad Dybcio wrote:
>>> On 6/1/26 1:01 PM, Sachin Gupta wrote:
>>>> From: Satya Priya Kakitapalli <quic_skakitap@quicinc.com>
>>>>
>>>> Add driver for the Qualcomm MBG thermal monitoring device. It monitors
>>>> the die temperature, and when there is a level 1 upper threshold
>>>> violation, it receives an interrupt over spmi. The driver reads
>>>> the fault status register and notifies thermal accordingly.
>>>>
>>>> Signed-off-by: Satya Priya Kakitapalli <quic_skakitap@quicinc.com>
>>>> Co-developed-by: Sachin Gupta <sachin.gupta@oss.qualcomm.com>
>>>> Signed-off-by: Sachin Gupta <sachin.gupta@oss.qualcomm.com>
>>>> ---
> 
> [...]
> 
>>>> +    /*
>>>> +     * Configure the last_temp one degree higher, to ensure the
>>>> +     * violated temp is returned to thermal framework when it reads
>>>> +     * temperature for the first time after the violation happens.
>>>> +     * This is needed to account for the inaccuracy in the conversion
>>>> +     * formula used which leads to the thermal framework setting back
>>>> +     * the same thresholds in case the temperature it reads does not
>>>> +     * show violation.
>>>> +     */
>>>> +    chip->last_temp = temp + MBG_TEMP_CONSTANT;
>>>
>>> Will this work fine if the user tries to set the max temp supported
>>> by the hardware (i.e. is there headroom for max+1)?
>>>
>>
>> In the current implementation, temp == MBG_MAX_SUPPORTED_TEMP is not accepted (temp < MBG_MAX_SUPPORTED_TEMP), so the last_temp = temp + MBG_TEMP_CONSTANT path is never taken at absolute max. For accepted trips (strictly below max), there is headroom for the +1C adjustment.
> 
> You check for `temp < MBG_MAX_SUPPORTED_TEMP` and there's:
> 
> #define MBG_MAX_SUPPORTED_TEMP 160000,
> 
> so passing temp=159999 is "valid" and after the addition it becomes 160999,
> which in my understanding is outside the range
> 
> Konrad

chip->last_temp is only a software cache used in one place, 
mbg_tm_get_temp(), to return a synthetic “trip violated” reading once 
after the IRQ. It is not programmed into any hardware register. So temp 
+ MBG_TEMP_CONSTANT exceeding MBG_MAX_SUPPORTED_TEMP does not cause a 
hardware out-of-range condition.

Do you see this as an issue?

Thanks,
Sachin

^ permalink raw reply

* Re: [PATCH v3 1/2] dt-bindings: iio: dac: Add AD5529R
From: Janani Sunil @ 2026-06-23 10:07 UTC (permalink / raw)
  To: David Lechner, Nuno Sá, Rodrigo Alencar
  Cc: Jonathan Cameron, Conor Dooley, Janani Sunil, Lars-Peter Clausen,
	Michael Hennerich, Nuno Sá, Andy Shevchenko, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Philipp Zabel, Jonathan Corbet,
	Shuah Khan, linux-iio, devicetree, linux-kernel, linux-doc,
	Mark Brown
In-Reply-To: <c72fb508-05a4-429a-9ca7-86e42a115fa8@baylibre.com>


On 6/22/26 17:36, David Lechner wrote:
> On 6/22/26 7:20 AM, Nuno Sá wrote:
>> On Mon, Jun 22, 2026 at 12:51:20PM +0100, Rodrigo Alencar wrote:
>>> On 22/06/26 11:29, Nuno Sá wrote:
>>>> On Mon, Jun 22, 2026 at 10:24:05AM +0100, Rodrigo Alencar wrote:
>>>>> On 21/06/26 15:33, Jonathan Cameron wrote:
>>>>>> On Fri, 19 Jun 2026 16:54:11 +0100
>>>>>> Nuno Sá <noname.nuno@gmail.com> wrote:
>>>>>>
>>>>>>> On Fri, Jun 19, 2026 at 03:12:07PM +0100, Conor Dooley wrote:
>>>>>>>> On Fri, Jun 19, 2026 at 02:01:08PM +0100, Nuno Sá wrote:
>>>>>>>>> On Fri, Jun 19, 2026 at 12:40:54PM +0100, Conor Dooley wrote:
>>>>>>>>>> On Fri, Jun 19, 2026 at 12:36:55PM +0100, Conor Dooley wrote:
>>>>>>>>>>> On Fri, Jun 19, 2026 at 12:33:11PM +0200, Janani Sunil wrote:
>>>>>>>>>>>> On 6/14/26 21:44, Jonathan Cameron wrote:
>>>>>>>>>>>>> On Tue, 9 Jun 2026 16:47:23 +0200
>>>>>>>>>>>>> Janani Sunil <jan.sun97@gmail.com> wrote:
>>>>>>>>>>>>>    
>>>>>>>>>>>>>> On 5/26/26 15:11, Rodrigo Alencar wrote:
>>>>>>>>>>>>>>> On 26/05/19 05:42PM, Janani Sunil wrote:
>>>>>>>>>>>>>>>> Devicetree bindings for AD5529R 16 channel 12/16 bit high voltage,
>>>>>>>>>>>>>>>> buffered voltage output digital-to-analog converter (DAC) with an
>>>>>>>>>>>>>>>> integrated precision reference.
>>>>>>>>>>>>>>> ...
>>>>>>>>>>>>>>> Probably others may comment on that, but...
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> This parent node may support device addressing for multi-device support through
>>>>>>>>>>>>>>> those ID pins. I suppose that each device may have its own power supplies or
>>>>>>>>>>>>>>> other resources like the toggle pins or reset and enable.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> That way I suppose that an example would look like...
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +patternProperties:
>>>>>>>>>>>>>>>> +  "^channel@([0-9]|1[0-5])$":
>>>>>>>>>>>>>>>> +    type: object
>>>>>>>>>>>>>>>> +    description: Child nodes for individual channel configuration
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +    properties:
>>>>>>>>>>>>>>>> +      reg:
>>>>>>>>>>>>>>>> +        description: Channel number.
>>>>>>>>>>>>>>>> +        minimum: 0
>>>>>>>>>>>>>>>> +        maximum: 15
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +      adi,output-range-microvolt:
>>>>>>>>>>>>>>>> +        description: |
>>>>>>>>>>>>>>>> +          Output voltage range for this channel as [min, max] in microvolts.
>>>>>>>>>>>>>>>> +          If not specified, defaults to 0V to 5V range.
>>>>>>>>>>>>>>>> +        oneOf:
>>>>>>>>>>>>>>>> +          - items:
>>>>>>>>>>>>>>>> +              - const: 0
>>>>>>>>>>>>>>>> +              - enum: [5000000, 10000000, 20000000, 40000000]
>>>>>>>>>>>>>>>> +          - items:
>>>>>>>>>>>>>>>> +              - const: -5000000
>>>>>>>>>>>>>>>> +              - const: 5000000
>>>>>>>>>>>>>>>> +          - items:
>>>>>>>>>>>>>>>> +              - const: -10000000
>>>>>>>>>>>>>>>> +              - const: 10000000
>>>>>>>>>>>>>>>> +          - items:
>>>>>>>>>>>>>>>> +              - const: -15000000
>>>>>>>>>>>>>>>> +              - const: 15000000
>>>>>>>>>>>>>>>> +          - items:
>>>>>>>>>>>>>>>> +              - const: -20000000
>>>>>>>>>>>>>>>> +              - const: 20000000
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +    required:
>>>>>>>>>>>>>>>> +      - reg
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +    additionalProperties: false
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +required:
>>>>>>>>>>>>>>>> +  - compatible
>>>>>>>>>>>>>>>> +  - reg
>>>>>>>>>>>>>>>> +  - vdd-supply
>>>>>>>>>>>>>>>> +  - avdd-supply
>>>>>>>>>>>>>>>> +  - hvdd-supply
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +dependencies:
>>>>>>>>>>>>>>>> +  spi-cpha: [ spi-cpol ]
>>>>>>>>>>>>>>>> +  spi-cpol: [ spi-cpha ]
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +allOf:
>>>>>>>>>>>>>>>> +  - $ref: /schemas/spi/spi-peripheral-props.yaml#
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +unevaluatedProperties: false
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +examples:
>>>>>>>>>>>>>>>> +  - |
>>>>>>>>>>>>>>>> +    #include <dt-bindings/gpio/gpio.h>
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +    spi {
>>>>>>>>>>>>>>>> +        #address-cells = <1>;
>>>>>>>>>>>>>>>> +        #size-cells = <0>;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +        dac@0 {
>>>>>>>>>>>>>>>> +            compatible = "adi,ad5529r-16";
>>>>>>>>>>>>>>>> +            reg = <0>;
>>>>>>>>>>>>>>>> +            spi-max-frequency = <25000000>;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +            vdd-supply = <&vdd_regulator>;
>>>>>>>>>>>>>>>> +            avdd-supply = <&avdd_regulator>;
>>>>>>>>>>>>>>>> +            hvdd-supply = <&hvdd_regulator>;
>>>>>>>>>>>>>>>> +            hvss-supply = <&hvss_regulator>;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +            reset-gpios = <&gpio0 87 GPIO_ACTIVE_LOW>;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +            #address-cells = <1>;
>>>>>>>>>>>>>>>> +            #size-cells = <0>;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +            channel@0 {
>>>>>>>>>>>>>>>> +                reg = <0>;
>>>>>>>>>>>>>>>> +                adi,output-range-microvolt = <0 5000000>;
>>>>>>>>>>>>>>>> +            };
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +            channel@1 {
>>>>>>>>>>>>>>>> +                reg = <1>;
>>>>>>>>>>>>>>>> +                adi,output-range-microvolt = <(-10000000) 10000000>;
>>>>>>>>>>>>>>>> +            };
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +            channel@2 {
>>>>>>>>>>>>>>>> +                reg = <2>;
>>>>>>>>>>>>>>>> +                adi,output-range-microvolt = <0 40000000>;
>>>>>>>>>>>>>>>> +            };
>>>>>>>>>>>>>>>> +        };
>>>>>>>>>>>>>>>> +    };
>>>>>>>>>>>>>>> ...
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> 	spi {
>>>>>>>>>>>>>>> 		#address-cells = <1>;
>>>>>>>>>>>>>>> 		#size-cells = <0>;
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> 		multi-dac@0 {
>>>>>>>>>>>>>>> 			compatible = "adi,ad5529r-16";
>>>>>>>>>>>>>>> 			reg = <0>;
>>>>>>>>>>>>>>> 			spi-max-frequency = <25000000>;
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> 			#address-cells = <1>;
>>>>>>>>>>>>>>> 			#size-cells = <0>;
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> 			dac@0 {
>>>>>>>>>>>>>>> 				reg = <0>;
>>>>>>>>>>>>>>> 				vdd-supply = <&vdd_regulator>;
>>>>>>>>>>>>>>> 				avdd-supply = <&avdd_regulator>;
>>>>>>>>>>>>>>> 				hvdd-supply = <&hvdd_regulator>;
>>>>>>>>>>>>>>> 				hvss-supply = <&hvss_regulator>;
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> 				reset-gpios = <&gpio0 87 GPIO_ACTIVE_LOW>;
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> 				#address-cells = <1>;
>>>>>>>>>>>>>>> 				#size-cells = <0>;
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> 				channel@0 {
>>>>>>>>>>>>>>> 					reg = <0>;
>>>>>>>>>>>>>>> 					adi,output-range-microvolt = <0 5000000>;
>>>>>>>>>>>>>>> 				};
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> 				channel@1 {
>>>>>>>>>>>>>>> 					reg = <1>;
>>>>>>>>>>>>>>> 					adi,output-range-microvolt = <(-10000000) 10000000>;
>>>>>>>>>>>>>>> 				};
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> 				channel@2 {
>>>>>>>>>>>>>>> 					reg = <2>;
>>>>>>>>>>>>>>> 					adi,output-range-microvolt = <0 40000000>;
>>>>>>>>>>>>>>> 				};
>>>>>>>>>>>>>>> 			}
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> 			dac@1 {
>>>>>>>>>>>>>>> 				reg = <1>;
>>>>>>>>>>>>>>> 				vdd-supply = <&vdd_regulator>;
>>>>>>>>>>>>>>> 				avdd-supply = <&avdd_regulator>;
>>>>>>>>>>>>>>> 				hvdd-supply = <&hvdd_regulator>;
>>>>>>>>>>>>>>> 				hvss-supply = <&hvss_regulator>;
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> 				reset-gpios = <&gpio0 88 GPIO_ACTIVE_LOW>;
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> 				#address-cells = <1>;
>>>>>>>>>>>>>>> 				#size-cells = <0>;
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> 				channel@0 {
>>>>>>>>>>>>>>> 					reg = <0>;
>>>>>>>>>>>>>>> 					adi,output-range-microvolt = <0 5000000>;
>>>>>>>>>>>>>>> 				};
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> 				channel@1 {
>>>>>>>>>>>>>>> 					reg = <1>;
>>>>>>>>>>>>>>> 					adi,output-range-microvolt = <(-10000000) 10000000>;
>>>>>>>>>>>>>>> 				};
>>>>>>>>>>>>>>> 			}
>>>>>>>>>>>>>>> 		};
>>>>>>>>>>>>>>> 	};
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> then you might need something like:
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> 	patternProperties:
>>>>>>>>>>>>>>> 		"^dac@[0-3]$":
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> and put most of the things under this node pattern.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> So the main driver that you're putting together might need to handle up to four instances.
>>>>>>>>>>>>>>> Even if your current driver cannot handle this, the dt-bindings might need cover that.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Need to double check if each dac node needs a separate compatible, so you would maybe populate
>>>>>>>>>>>>>>> a platform data to be shared with the child nodes, which would be a separate driver.
>>>>>>>>>>>>>>> (not sure if it would make sense to mix and match ad5529r-16 and ad5529r-12).
>>>>>>>>>>>>>> Hi Rodrigo,
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Thank you for looking at this.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> For now, I would prefer to keep the binding scoped to a single AD5529R device instance. The current
>>>>>>>>>>>>>> hardware/use case we have only needs one device node and the driver is written around that model as well.
>>>>>>>>>>>>>> While the device addressing pins could allow multi-device topology, we do not have an actual platform using
>>>>>>>>>>>>>> that configuration at the moment, so I would prefer not to introduce an extra parent/child binding structure
>>>>>>>>>>>>>> speculatively without a validating use case.
>>>>>>>>>>>>> Interesting feature - kind of similar to address control on a typical i2c bus device, or
>>>>>>>>>>>>> looking at it another way a kind of distributed SPI mux.
>>>>>>>>>>>>>
>>>>>>>>>>>>> Challenge of a binding is we need to anticipate the future.  So I think we do need something
>>>>>>>>>>>>> like Rodrigo is suggesting even if we only (for now) support a single instance in the driver.
>>>>>>>>>>>>> That would leave the path open to supporting the addressing at a later date.
>>>>>>>>>>>>> An alternative might be to look at it like a chained device setup. In those we pretend there
>>>>>>>>>>>>> is just one device with a lot of channels etc.  The snag is that here things are more loosely
>>>>>>>>>>>>> coupled whereas for those devices it tends to be you have to read / write the same register
>>>>>>>>>>>>> in all devices in the chain as one big SPI message.
>>>>>>>>>>>>>
>>>>>>>>>>>>> +CC Mark Brown as he may know of some precedence for this feature. For his reference..
>>>>>>>>>>>>> - Each of these device has 2 ID pins.  The SPI transfers have to contain the 2 bit
>>>>>>>>>>>>> value that matches that or they are ignored.  Thus a single bus + 1 chip select can
>>>>>>>>>>>>> be used to talk to 4 devices.  Question is what that looks like in device tree + I guess
>>>>>>>>>>>>> longer term how to support it cleanly in SPI.
>>>>>>>>>>> I'd swear I have seen this before, from some Microchip devices. Let me
>>>>>>>>>>> see if I can find what I am thinking of...
>>>>>>>>>>
>>>>>>>>>> microchip,mcp3911 and microchip,mcp3564 both seem to do this with
>>>>>>>>>> slightly different properties.
>>>>>>>>>>
>>>>>>>>>>    microchip,device-addr:
>>>>>>>>>>      description: Device address when multiple MCP3911 chips are present on the same SPI bus.
>>>>>>>>>>      $ref: /schemas/types.yaml#/definitions/uint32
>>>>>>>>>>      enum: [0, 1, 2, 3]
>>>>>>>>>>      default: 0
>>>>>>>>>>
>>>>>>>>>> and
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>    microchip,hw-device-address:
>>>>>>>>>>      $ref: /schemas/types.yaml#/definitions/uint32
>>>>>>>>>>      minimum: 0
>>>>>>>>>>      maximum: 3
>>>>>>>>>>      description:
>>>>>>>>>>        The address is set on a per-device basis by fuses in the factory,
>>>>>>>>>>        configured on request. If not requested, the fuses are set for 0x1.
>>>>>>>>>>        The device address is part of the device markings to avoid
>>>>>>>>>>        potential confusion. This address is coded on two bits, so four possible
>>>>>>>>>>        addresses are available when multiple devices are present on the same
>>>>>>>>>>        SPI bus with only one Chip Select line for all devices.
>>>>>>>>>>        Each device communication starts by a CS falling edge, followed by the
>>>>>>>>>>        clocking of the device address (BITS[7:6] - top two bits of COMMAND BYTE
>>>>>>>>>>        which is first one on the wire).
>>>>>>>>>>
>>>>>>>>>> This sounds exactly like the sort of feature that you're dealing with
>>>>>>>>>> here?
>>>>>>>>>>    
>>>>>>>>> The core idea yes but for this chip, things are a bit more annoying (but
>>>>>>>>> Janani can correct me if I'm wrong). Here, each device can, in theory,
>>>>>>>>> have it's own supplies, pins and at the very least, channels with maybe
>>>>>>>>> different scales. That is why Janani is proposing dac nodes. Given I
>>>>>>>>> honestly don't like much of that "adi,ad5529r-bus" compatible I wondered
>>>>>>>>> about solving this at the spi level.
>>>>>>>>>
>>>>>>>>> Ah and to make it more annoying, we can also mix 12 and 16 bits variants
>>>>>>>>> together in the same bus.
>>>>>>>> I'm definitely missing something, because that property for the
>>>>>>>> microchip devices is not impacted what else is on the bus. AFAICT, you
>>>>>>>> could have an mcp3911 and an mcp3564 on the same bus even though both
>>>>>>>> are completely different devices with different drivers. They have
>>>>>>>> individual device nodes and their own supplies etc etc. These aren't
>>>>>>>> per-channel properties on an adc or dac, they're per child device on a
>>>>>>>> spi bus.
>>>>>>> Maybe I'm the one missing something :). IIRC, spi would not allow two
>>>>>>> devices on the same CS right? Because for this chip we would need
>>>>>>> something like:
>>>>>>>
>>>>>>> spi {
>>>>>>> 	dac@0 {
>>>>>>> 		reg = <0>;
>>>>>>> 		adi,pin-id = <0>;
>>>>>>> 	};
>>>>>>>
>>>>>>> 	dac@1 {
>>>>>>> 		reg = <0>; // which seems already problematic?
>>>>>>> 		adi,pin-id <1>;
>>>>>>> 	};
>>>>>>>
>>>>>>> 	...
>>>>>>>
>>>>>>> 	//up to 4
>>>>>>> };
>>>>>> Yeah. It's not clear to me how that works for the microchip devices
>>>>>> (I suspect it doesn't!)
>>>>>>
>>>>>> Just thinking as I type, but could we do something a bit nasty with
>>>>>> a gpio mux that doesn't actually switch but represents the GPIO being
>>>>>> shared?  Given this is all tied to the spi bus that should all happen
>>>>>> under serializing locks.
>>>>>>
>>>>>> Agreed though that this would be nicer as an SPI thing that let
>>>>>> us specify that a single CS is share by multiple devices and their
>>>>>> is some other signal acting to select which one we are talking to.
>>>>>>
>>>>> If the device-addressing on the same chip-select is to be handled
>>>>> by the spi framework, wouldn't we lose device-specific features?
>>>>>
>>>>> I understand that this multi-device feature is there mostly to extend the
>>>>> channel count from 16 to 32, 48 or 64. I suppose the command:
>>>>>
>>>>> 	"MULTI DEVICE SW LDAC MODE"
>>>>>
>>>>> exists so that software can update channel values accross multiple devices.
>>>> Right! You do have a point! I agree the main driver for a feature like
>>>> this is likely to extend the channel count and effectively "aggregate"
>>>> devices.
>>>>
>>>> But I would say that even with the spi solution the MULTI DEVICE stuff
>>>> should be doable (as we still need a sort of adi,pin-id property).
>>> I don't think we can have something like an IIO buffer shared by multiple
>>> devices. Synchronizing separate devices would be doable with proper hardware
>>> support for this (probably involving an FGPA).
>> True!
>>
>>>   
>>>> But yes, I do feel that the whole feature is for aggregation so seeing
>>>> one device with 32 channels is the expectation here? Rather than seeing
>>>> two devices with 16 channels.
>>> Yes, I think aggregation is the whole point there... so that the IIO driver
>>> is multi-device-aware.
>> Which makes me feel that different pins per device might be possible
>> from an HW point of view but does not make much sense. For example, for
>> the buffer example I would expect LDAC to be shared between all the
>> devices.
>>
>> - Nuno Sá
> I think I mentioned this on a previous revision, but I still think the
> simplest way to go about it would be to assume that all chips treated
> as an aggregate device have everything wired in parallel and just add
> support for per-chip wiring on an as-needed basis. This is how we have
> handled daisy-chained devices so far.

Hi David,

One thing about this approach is that is does not cover a combination  of 12 and 16 bit parts in the chain,
since the compatible string would be at the top level and apply to all chips. To handle this without per chip child nodes or per-chip compatible,
I propose an "adi, resolution" property as an integer array, indexed by the device position:


dac@0 {
     compatible = "adi,ad5529r";
     reg = <0>;
     adi,device-addrs = <0 1>;
     adi,resolution   = <16 12>;   /* per-chip, indexed by position */
     reset-gpios = <&gpio0 87 GPIO_ACTIVE_LOW>;
     vdd-supply  = <&vdd_reg>;
     hvdd-supply = <&hvdd_reg>;

     channel@0  { reg = <0>;  adi,output-range-microvolt = <0 5000000>; };
     channel@16 { reg = <16>; adi,output-range-microvolt = <0 40000000>; };
};


1) This follows the daisy-chain/aggregated model as you suggested, exposing N*16 channels as a single IIO device.
2) Keeps the binding flat- no phantom compatible at a parent bus node, no per-chip child nodes.
3) Enables a 12 bit + 16 bit device combination in the chain, without needing a per-chip compatible.
4) adi, device-addrs specifies the HW address, allowing the driver to encode it into the SPI frame.
5) Supplies and GPIOs remain simple- assuming parallel wiring across all chips.

Jonathan, you had earlier suggested using separate compatibles
  (adi,ad5529r-16 and adi,ad5529r-12) to handle the
  resolution difference.
However, with the aggregated flat binding model,
  separate per-chip compatibles would require child nodes which brings
back the phantom compatible problem at the parent level. The
  adi,resolution array is intended as an alternative that achieves the
same goal-expressing per-chip resolution, without needing a per-chip
  compatible or child node structure.

Does this look reasonable?

Best Regards,
Janani Sunil


^ permalink raw reply

* [PATCH v7 6/6] remoteproc: qcom_q6v5_pas: Add SoCCP node on Kaanapali
From: Jingyi Wang @ 2026-06-23 10:05 UTC (permalink / raw)
  To: Bjorn Andersson, Mathieu Poirier, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Manivannan Sadhasivam,
	Luca Weiss, Bartosz Golaszewski, Kumar Patro, Komal Bajaj,
	Konrad Dybcio
  Cc: aiqun.yu, tingwei.zhang, trilok.soni, yijie.yang, linux-arm-msm,
	linux-remoteproc, devicetree, linux-kernel, Jingyi Wang,
	Dmitry Baryshkov, Bartosz Golaszewski
In-Reply-To: <20260623-knp-soccp-v7-0-1ec7bb5c9fec@oss.qualcomm.com>

The SoC Control Processor (SoCCP) is small RISC-V MCU that controls
USB Type-C, battery charging and various other functions on Qualcomm SoCs.
It provides a solution for control-plane processing, reducing per-subsystem
microcontroller reinvention. Add support for SoCCP PAS loader on Kaanapali
platform.

Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Reviewed-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
Signed-off-by: Jingyi Wang <jingyi.wang@oss.qualcomm.com>
---
 drivers/remoteproc/qcom_q6v5_pas.c | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c
index 8a0bb4b2e71c..60a4337d9e51 100644
--- a/drivers/remoteproc/qcom_q6v5_pas.c
+++ b/drivers/remoteproc/qcom_q6v5_pas.c
@@ -1640,8 +1640,26 @@ static const struct qcom_pas_data sm8750_mpss_resource = {
 	.region_assign_vmid = QCOM_SCM_VMID_MSS_MSA,
 };
 
+static const struct qcom_pas_data kaanapali_soccp_resource = {
+	.crash_reason_smem = 656,
+	.firmware_name = "soccp.mbn",
+	.dtb_firmware_name = "soccp_dtb.mbn",
+	.pas_id = 51,
+	.dtb_pas_id = 0x41,
+	.proxy_pd_names = (char*[]){
+		"cx",
+		"mx",
+		NULL
+	},
+	.ssr_name = "soccp",
+	.sysmon_name = "soccp",
+	.auto_boot = true,
+	.early_boot = true,
+};
+
 static const struct of_device_id qcom_pas_of_match[] = {
 	{ .compatible = "qcom,eliza-adsp-pas", .data = &sm8550_adsp_resource },
+	{ .compatible = "qcom,kaanapali-soccp-pas", .data = &kaanapali_soccp_resource },
 	{ .compatible = "qcom,milos-adsp-pas", .data = &sm8550_adsp_resource },
 	{ .compatible = "qcom,milos-cdsp-pas", .data = &milos_cdsp_resource },
 	{ .compatible = "qcom,milos-mpss-pas", .data = &sm8450_mpss_resource },

-- 
2.34.1


^ permalink raw reply related

* [PATCH v7 5/6] remoteproc: qcom: pas: Add late attach support for subsystems
From: Jingyi Wang @ 2026-06-23 10:05 UTC (permalink / raw)
  To: Bjorn Andersson, Mathieu Poirier, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Manivannan Sadhasivam,
	Luca Weiss, Bartosz Golaszewski, Kumar Patro, Komal Bajaj,
	Konrad Dybcio
  Cc: aiqun.yu, tingwei.zhang, trilok.soni, yijie.yang, linux-arm-msm,
	linux-remoteproc, devicetree, linux-kernel, Jingyi Wang,
	Gokul Krishna Krishnakumar, Shawn Guo
In-Reply-To: <20260623-knp-soccp-v7-0-1ec7bb5c9fec@oss.qualcomm.com>

Subsystems can be brought out of reset by entities such as bootloaders.
As the irq enablement could be later than subsystem bring up, the state
of subsystem should be checked by reading SMP2P bits.

A new qcom_pas_attach() function is introduced. if crash state is detected
for the subsystem, rproc_report_crash() is called. If the ready state is
detected meanwhile stop state is not detected, it will be marked as
"attached", otherwise it could be the early boot feature is not supported
by other entities or it has already been stopped. In above cases, the
state will be marked as RPROC_OFFLINE so that the PAS driver can load the
firmware and start the remoteproc.

Co-developed-by: Gokul Krishna Krishnakumar <gokul.krishnakumar@oss.qualcomm.com>
Signed-off-by: Gokul Krishna Krishnakumar <gokul.krishnakumar@oss.qualcomm.com>
Tested-by: Shawn Guo <shengchao.guo@oss.qualcomm.com>
Signed-off-by: Jingyi Wang <jingyi.wang@oss.qualcomm.com>
---
 drivers/remoteproc/qcom_common.h   |  6 ++++
 drivers/remoteproc/qcom_q6v5.c     |  3 +-
 drivers/remoteproc/qcom_q6v5_pas.c | 68 ++++++++++++++++++++++++++++++++++++++
 drivers/remoteproc/qcom_sysmon.c   | 19 +++++++++++
 4 files changed, 95 insertions(+), 1 deletion(-)

diff --git a/drivers/remoteproc/qcom_common.h b/drivers/remoteproc/qcom_common.h
index b07fbaa091a0..b0e7e336d363 100644
--- a/drivers/remoteproc/qcom_common.h
+++ b/drivers/remoteproc/qcom_common.h
@@ -68,6 +68,7 @@ struct qcom_sysmon *qcom_add_sysmon_subdev(struct rproc *rproc,
 					   int ssctl_instance);
 void qcom_remove_sysmon_subdev(struct qcom_sysmon *sysmon);
 bool qcom_sysmon_shutdown_acked(struct qcom_sysmon *sysmon);
+bool qcom_sysmon_shutdown_irq_state(struct qcom_sysmon *sysmon);
 #else
 static inline struct qcom_sysmon *qcom_add_sysmon_subdev(struct rproc *rproc,
 							 const char *name,
@@ -84,6 +85,11 @@ static inline bool qcom_sysmon_shutdown_acked(struct qcom_sysmon *sysmon)
 {
 	return false;
 }
+
+static inline bool qcom_sysmon_shutdown_irq_state(struct qcom_sysmon *sysmon)
+{
+	return false;
+}
 #endif
 
 #endif
diff --git a/drivers/remoteproc/qcom_q6v5.c b/drivers/remoteproc/qcom_q6v5.c
index 58d5b85e58cd..a11d8ace554b 100644
--- a/drivers/remoteproc/qcom_q6v5.c
+++ b/drivers/remoteproc/qcom_q6v5.c
@@ -202,7 +202,8 @@ int qcom_q6v5_request_stop(struct qcom_q6v5 *q6v5, struct qcom_sysmon *sysmon)
 	q6v5->running = false;
 
 	/* Don't perform SMP2P dance if remote isn't running */
-	if (q6v5->rproc->state != RPROC_RUNNING || qcom_sysmon_shutdown_acked(sysmon))
+	if ((q6v5->rproc->state != RPROC_RUNNING && q6v5->rproc->state != RPROC_ATTACHED) ||
+	    qcom_sysmon_shutdown_acked(sysmon))
 		return 0;
 
 	qcom_smem_state_update_bits(q6v5->state,
diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c
index 808e9609988d..8a0bb4b2e71c 100644
--- a/drivers/remoteproc/qcom_q6v5_pas.c
+++ b/drivers/remoteproc/qcom_q6v5_pas.c
@@ -60,6 +60,7 @@ struct qcom_pas_data {
 	int region_assign_count;
 	bool region_assign_shared;
 	int region_assign_vmid;
+	bool early_boot;
 };
 
 struct qcom_pas {
@@ -507,6 +508,67 @@ static unsigned long qcom_pas_panic(struct rproc *rproc)
 	return qcom_q6v5_panic(&pas->q6v5);
 }
 
+static int qcom_pas_attach(struct rproc *rproc)
+{
+	struct qcom_pas *pas = rproc->priv;
+	bool ready_state;
+	bool crash_state;
+	bool stop_state;
+	int ret;
+
+	pas->q6v5.handover_issued = true;
+	enable_irq(pas->q6v5.handover_irq);
+
+	pas->q6v5.running = true;
+	ret = irq_get_irqchip_state(pas->q6v5.fatal_irq,
+				    IRQCHIP_STATE_LINE_LEVEL, &crash_state);
+	if (ret)
+		goto disable_running;
+
+	if (crash_state) {
+		dev_err(pas->dev, "Subsystem has crashed before driver probe\n");
+		rproc_report_crash(rproc, RPROC_FATAL_ERROR);
+		ret = -EINVAL;
+		goto disable_running;
+	}
+
+	ret = irq_get_irqchip_state(pas->q6v5.stop_irq,
+				    IRQCHIP_STATE_LINE_LEVEL, &stop_state);
+	if (ret)
+		goto disable_running;
+
+	if (stop_state || qcom_sysmon_shutdown_irq_state(pas->sysmon)) {
+		dev_info(pas->dev, "Subsystem found stop state set. Falling back to start.\n");
+		goto unroll_attach;
+	}
+
+	ret = irq_get_irqchip_state(pas->q6v5.ready_irq,
+				    IRQCHIP_STATE_LINE_LEVEL, &ready_state);
+	if (ret)
+		goto disable_running;
+
+	if (unlikely(!ready_state)) {
+		/*
+		 * The bootloader may not support early boot, mark the state as
+		 * RPROC_OFFLINE so that the PAS driver can load the firmware and
+		 * start the remoteproc.
+		 */
+		dev_err(pas->dev, "Failed to get subsystem ready interrupt\n");
+		goto unroll_attach;
+	}
+
+	return 0;
+
+unroll_attach:
+	pas->rproc->state = RPROC_OFFLINE;
+	ret = -EINVAL;
+disable_running:
+	disable_irq(pas->q6v5.handover_irq);
+	pas->q6v5.running = false;
+
+	return ret;
+}
+
 static const struct rproc_ops qcom_pas_ops = {
 	.unprepare = qcom_pas_unprepare,
 	.start = qcom_pas_start,
@@ -515,6 +577,7 @@ static const struct rproc_ops qcom_pas_ops = {
 	.parse_fw = qcom_pas_parse_firmware,
 	.load = qcom_pas_load,
 	.panic = qcom_pas_panic,
+	.attach = qcom_pas_attach,
 };
 
 static const struct rproc_ops qcom_pas_minidump_ops = {
@@ -526,6 +589,7 @@ static const struct rproc_ops qcom_pas_minidump_ops = {
 	.load = qcom_pas_load,
 	.panic = qcom_pas_panic,
 	.coredump = qcom_pas_minidump,
+	.attach = qcom_pas_attach,
 };
 
 static int qcom_pas_init_clock(struct qcom_pas *pas)
@@ -852,6 +916,10 @@ static int qcom_pas_probe(struct platform_device *pdev)
 
 	pas->pas_ctx->use_tzmem = rproc->has_iommu;
 	pas->dtb_pas_ctx->use_tzmem = rproc->has_iommu;
+
+	if (desc->early_boot)
+		pas->rproc->state = RPROC_DETACHED;
+
 	ret = rproc_add(rproc);
 	if (ret)
 		goto remove_ssr_sysmon;
diff --git a/drivers/remoteproc/qcom_sysmon.c b/drivers/remoteproc/qcom_sysmon.c
index 913e3b750a86..a0830a48b1f4 100644
--- a/drivers/remoteproc/qcom_sysmon.c
+++ b/drivers/remoteproc/qcom_sysmon.c
@@ -736,6 +736,25 @@ bool qcom_sysmon_shutdown_acked(struct qcom_sysmon *sysmon)
 }
 EXPORT_SYMBOL_GPL(qcom_sysmon_shutdown_acked);
 
+bool qcom_sysmon_shutdown_irq_state(struct qcom_sysmon *sysmon)
+{
+	bool shutdown_state;
+	int ret;
+
+	if (!sysmon)
+		return false;
+
+	ret = irq_get_irqchip_state(sysmon->shutdown_irq,
+				    IRQCHIP_STATE_LINE_LEVEL, &shutdown_state);
+	if (ret) {
+		dev_warn(sysmon->dev, "failed to get shutdown_state: %d\n", ret);
+		return false;
+	}
+
+	return shutdown_state;
+}
+EXPORT_SYMBOL_GPL(qcom_sysmon_shutdown_irq_state);
+
 /**
  * sysmon_probe() - probe sys_mon channel
  * @rpdev:	rpmsg device handle

-- 
2.34.1


^ permalink raw reply related

* [PATCH v7 3/6] dt-bindings: remoteproc: qcom: Document pas for SoCCP on Kaanapali and Glymur platforms
From: Jingyi Wang @ 2026-06-23 10:05 UTC (permalink / raw)
  To: Bjorn Andersson, Mathieu Poirier, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Manivannan Sadhasivam,
	Luca Weiss, Bartosz Golaszewski, Kumar Patro, Komal Bajaj,
	Konrad Dybcio
  Cc: aiqun.yu, tingwei.zhang, trilok.soni, yijie.yang, linux-arm-msm,
	linux-remoteproc, devicetree, linux-kernel, Jingyi Wang
In-Reply-To: <20260623-knp-soccp-v7-0-1ec7bb5c9fec@oss.qualcomm.com>

Document the component used to boot SoCCP on Kaanapali SoC and add
compatible for Glymur SoCCP which could fallback to Kaanapali. Extend
the "qcom,smem-states", "qcom,smem-state-names" in the pas-common
and add maxItems constraints for SMEM properties in the documents
that reference to pas-common.

Signed-off-by: Jingyi Wang <jingyi.wang@oss.qualcomm.com>
---
 .../devicetree/bindings/remoteproc/qcom,adsp.yaml  |   8 ++
 .../remoteproc/qcom,kaanapali-soccp-pas.yaml       | 154 +++++++++++++++++++++
 .../bindings/remoteproc/qcom,milos-pas.yaml        |   8 ++
 .../bindings/remoteproc/qcom,pas-common.yaml       |  12 +-
 .../bindings/remoteproc/qcom,qcs404-pas.yaml       |   8 ++
 .../bindings/remoteproc/qcom,sa8775p-pas.yaml      |   8 ++
 .../bindings/remoteproc/qcom,sc7180-pas.yaml       |   8 ++
 .../bindings/remoteproc/qcom,sc8280xp-pas.yaml     |   8 ++
 .../bindings/remoteproc/qcom,sdx55-pas.yaml        |   8 ++
 .../bindings/remoteproc/qcom,sm6115-pas.yaml       |   8 ++
 .../bindings/remoteproc/qcom,sm6350-pas.yaml       |   8 ++
 .../bindings/remoteproc/qcom,sm6375-pas.yaml       |   8 ++
 .../bindings/remoteproc/qcom,sm8150-pas.yaml       |   8 ++
 .../bindings/remoteproc/qcom,sm8350-pas.yaml       |   8 ++
 .../bindings/remoteproc/qcom,sm8550-pas.yaml       |   8 ++
 15 files changed, 269 insertions(+), 1 deletion(-)

diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,adsp.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,adsp.yaml
index 16c35e15ee1b..7e8ecae8e6cb 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,adsp.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,adsp.yaml
@@ -73,6 +73,14 @@ properties:
       - const: handover
       - const: stop-ack
 
+  qcom,smem-states:
+    maxItems: 1
+    description: States used by the AP to signal the Hexagon core
+
+  qcom,smem-state-names:
+    maxItems: 1
+    description: The names of the state bits used for SMP2P output
+
 required:
   - compatible
   - memory-region
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,kaanapali-soccp-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,kaanapali-soccp-pas.yaml
new file mode 100644
index 000000000000..ce18460a949f
--- /dev/null
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,kaanapali-soccp-pas.yaml
@@ -0,0 +1,154 @@
+# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/remoteproc/qcom,kaanapali-soccp-pas.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm Kaanapali SoCCP Peripheral Authentication Service
+
+maintainers:
+  - Jingyi Wang <jingyi.wang@oss.qualcomm.com>
+
+description:
+  The SoC Control Processor (SoCCP) is a small RISC-V MCU that controls USB
+  Type-C, battery charging and various other functions on Qualcomm SoCs, somewhat
+  analogous to traditional PC Embedded Controllers. This document describes
+  the Peripheral Authentication Service that loads and boots firmware for SoCCP.
+
+properties:
+  compatible:
+    oneOf:
+      - items:
+          - enum:
+              - qcom,glymur-soccp-pas
+          - const: qcom,kaanapali-soccp-pas
+      - enum:
+          - qcom,kaanapali-soccp-pas
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    items:
+      - description: XO clock
+
+  clock-names:
+    items:
+      - const: xo
+
+  power-domains:
+    items:
+      - description: CX power domain
+      - description: MX power domain
+
+  power-domain-names:
+    items:
+      - const: cx
+      - const: mx
+
+  firmware-name:
+    items:
+      - description: Firmware name of the SoC Control Processor
+      - description: Firmware name of the SoCCP Devicetree
+
+  memory-region:
+    items:
+      - description: Memory region for main Firmware authentication
+      - description: Memory region for Devicetree Firmware authentication
+
+  interrupts:
+    items:
+      - description: Watchdog interrupt
+      - description: Fatal interrupt
+      - description: Ready interrupt
+      - description: Handover interrupt
+      - description: Stop acknowledge interrupt
+      - description: Pong interrupt
+
+  interrupt-names:
+    items:
+      - const: wdog
+      - const: fatal
+      - const: ready
+      - const: handover
+      - const: stop-ack
+      - const: pong
+
+  qcom,smem-states:
+    minItems: 2
+    description: States used by the AP to signal the SoC Control Processor
+
+  qcom,smem-state-names:
+    minItems: 2
+    description: The names of the state bits used for SMP2P output
+
+required:
+  - compatible
+  - reg
+  - memory-region
+  - power-domains
+  - power-domain-names
+
+allOf:
+  - $ref: /schemas/remoteproc/qcom,pas-common.yaml#
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/qcom,rpmh.h>
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+    #include <dt-bindings/mailbox/qcom-ipcc.h>
+    #include <dt-bindings/power/qcom-rpmpd.h>
+    #define IPCC_MPROC_SOCCP
+
+    remoteproc@d00000 {
+        compatible = "qcom,kaanapali-soccp-pas";
+        reg = <0x00d00000 0x200000>;
+
+        clocks = <&rpmhcc RPMH_CXO_CLK>;
+        clock-names = "xo";
+
+        interrupts-extended = <&intc GIC_SPI 167 IRQ_TYPE_EDGE_RISING>,
+                              <&soccp_smp2p_in 0 IRQ_TYPE_EDGE_RISING>,
+                              <&soccp_smp2p_in 1 IRQ_TYPE_EDGE_RISING>,
+                              <&soccp_smp2p_in 2 IRQ_TYPE_EDGE_RISING>,
+                              <&soccp_smp2p_in 3 IRQ_TYPE_EDGE_RISING>,
+                              <&soccp_smp2p_in 9 IRQ_TYPE_EDGE_RISING>;
+        interrupt-names = "wdog",
+                          "fatal",
+                          "ready",
+                          "handover",
+                          "stop-ack",
+                          "pong";
+
+        memory-region = <&soccp_mem>,
+                        <&soccp_dtb_mem_mem>;
+
+        firmware-name = "qcom/kaanapali/soccp.mbn",
+                        "qcom/kaanapali/soccp_dtb.mbn";
+
+        power-domains = <&rpmhpd RPMHPD_CX>,
+                        <&rpmhpd RPMHPD_MX>;
+        power-domain-names = "cx",
+                             "mx";
+
+        qcom,smem-states = <&soccp_smp2p_out 0>,
+                           <&soccp_smp2p_out 8>;
+        qcom,smem-state-names = "stop",
+                                "ping";
+
+        glink-edge {
+            interrupts-extended = <&ipcc IPCC_MPROC_SOCCP
+                                         IPCC_MPROC_SIGNAL_GLINK_QMP
+                                         IRQ_TYPE_EDGE_RISING>;
+            mboxes = <&ipcc IPCC_MPROC_SOCCP
+                            IPCC_MPROC_SIGNAL_GLINK_QMP>;
+
+            label = "soccp";
+            qcom,remote-pid = <19>;
+
+            /* ... */
+        };
+    };
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,milos-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,milos-pas.yaml
index d22d50c1e1ea..99d7337e58ec 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,milos-pas.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,milos-pas.yaml
@@ -69,6 +69,14 @@ properties:
       - description: Memory region for core Firmware authentication
       - description: Memory region for Devicetree Firmware authentication
 
+  qcom,smem-states:
+    maxItems: 1
+    description: States used by the AP to signal the Hexagon core
+
+  qcom,smem-state-names:
+    maxItems: 1
+    description: The names of the state bits used for SMP2P output
+
 required:
   - compatible
   - reg
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,pas-common.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,pas-common.yaml
index 3847aadfa980..11faf655f530 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,pas-common.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,pas-common.yaml
@@ -46,8 +46,16 @@ properties:
   qcom,smem-states:
     $ref: /schemas/types.yaml#/definitions/phandle-array
     description: States used by the AP to signal the Hexagon core
+    minItems: 1
     items:
-      - description: Stop the modem
+      - description: Stop the remoteproc
+        items:
+          - description: Phandle to the Shared Memory Point 2 Point device
+              handling the communication with a remote processor
+          - description: Single bit index to toggle in the value sent to
+              the remote processor
+            maximum: 32
+      - description: Ping the remoteproc
         items:
           - description: Phandle to the Shared Memory Point 2 Point device
               handling the communication with a remote processor
@@ -57,8 +65,10 @@ properties:
 
   qcom,smem-state-names:
     description: The names of the state bits used for SMP2P output
+    minItems: 1
     items:
       - const: stop
+      - const: ping
 
   smd-edge:
     $ref: /schemas/remoteproc/qcom,smd-edge.yaml#
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,qcs404-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,qcs404-pas.yaml
index 5854b3d2041d..bf9bf1af9ff1 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,qcs404-pas.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,qcs404-pas.yaml
@@ -59,6 +59,14 @@ properties:
     maxItems: 1
     description: Firmware name for the Hexagon core
 
+  qcom,smem-states:
+    maxItems: 1
+    description: States used by the AP to signal the Hexagon core
+
+  qcom,smem-state-names:
+    maxItems: 1
+    description: The names of the state bits used for SMP2P output
+
 required:
   - compatible
   - reg
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,sa8775p-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,sa8775p-pas.yaml
index 7f287e55896e..dda2d144b720 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,sa8775p-pas.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,sa8775p-pas.yaml
@@ -74,6 +74,14 @@ properties:
       - const: handover
       - const: stop-ack
 
+  qcom,smem-states:
+    maxItems: 1
+    description: States used by the AP to signal the Hexagon core
+
+  qcom,smem-state-names:
+    maxItems: 1
+    description: The names of the state bits used for SMP2P output
+
 required:
   - compatible
   - reg
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,sc7180-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,sc7180-pas.yaml
index cb0a61fc301d..b20780e5e26b 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,sc7180-pas.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,sc7180-pas.yaml
@@ -68,6 +68,14 @@ properties:
       - const: stop-ack
       - const: shutdown-ack
 
+  qcom,smem-states:
+    maxItems: 1
+    description: States used by the AP to signal the Hexagon core
+
+  qcom,smem-state-names:
+    maxItems: 1
+    description: The names of the state bits used for SMP2P output
+
 required:
   - compatible
   - reg
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,sc8280xp-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,sc8280xp-pas.yaml
index fef9d7c39f3c..4bbe4a986c7c 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,sc8280xp-pas.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,sc8280xp-pas.yaml
@@ -65,6 +65,14 @@ properties:
       - const: stop-ack
       - const: shutdown-ack
 
+  qcom,smem-states:
+    maxItems: 1
+    description: States used by the AP to signal the Hexagon core
+
+  qcom,smem-state-names:
+    maxItems: 1
+    description: The names of the state bits used for SMP2P output
+
 required:
   - compatible
   - reg
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,sdx55-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,sdx55-pas.yaml
index 2bbd427c6ea4..8c16b01c53e4 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,sdx55-pas.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,sdx55-pas.yaml
@@ -71,6 +71,14 @@ properties:
     maxItems: 1
     description: Firmware name for the Hexagon core
 
+  qcom,smem-states:
+    maxItems: 1
+    description: States used by the AP to signal the Hexagon core
+
+  qcom,smem-state-names:
+    maxItems: 1
+    description: The names of the state bits used for SMP2P output
+
 required:
   - compatible
   - reg
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,sm6115-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,sm6115-pas.yaml
index 987fac433fae..454ba82bd6f1 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,sm6115-pas.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,sm6115-pas.yaml
@@ -71,6 +71,14 @@ properties:
       - const: stop-ack
       - const: shutdown-ack
 
+  qcom,smem-states:
+    maxItems: 1
+    description: States used by the AP to signal the Hexagon core
+
+  qcom,smem-state-names:
+    maxItems: 1
+    description: The names of the state bits used for SMP2P output
+
 required:
   - compatible
   - reg
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,sm6350-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,sm6350-pas.yaml
index 53ffb1ccd199..42e02c64347a 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,sm6350-pas.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,sm6350-pas.yaml
@@ -65,6 +65,14 @@ properties:
       - const: stop-ack
       - const: shutdown-ack
 
+  qcom,smem-states:
+    maxItems: 1
+    description: States used by the AP to signal the Hexagon core
+
+  qcom,smem-state-names:
+    maxItems: 1
+    description: The names of the state bits used for SMP2P output
+
 required:
   - compatible
   - reg
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,sm6375-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,sm6375-pas.yaml
index 6823a2a8d74e..274f87880e2e 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,sm6375-pas.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,sm6375-pas.yaml
@@ -61,6 +61,14 @@ properties:
 
   smd-edge: false
 
+  qcom,smem-states:
+    maxItems: 1
+    description: States used by the AP to signal the Hexagon core
+
+  qcom,smem-state-names:
+    maxItems: 1
+    description: The names of the state bits used for SMP2P output
+
 required:
   - compatible
   - reg
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,sm8150-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,sm8150-pas.yaml
index 8a1fae095a3b..5a7c5f8c92d1 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,sm8150-pas.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,sm8150-pas.yaml
@@ -81,6 +81,14 @@ properties:
       - const: stop-ack
       - const: shutdown-ack
 
+  qcom,smem-states:
+    maxItems: 1
+    description: States used by the AP to signal the Hexagon core
+
+  qcom,smem-state-names:
+    maxItems: 1
+    description: The names of the state bits used for SMP2P output
+
 required:
   - compatible
   - reg
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,sm8350-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,sm8350-pas.yaml
index 4ea7518db537..72d0db5698c5 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,sm8350-pas.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,sm8350-pas.yaml
@@ -75,6 +75,14 @@ properties:
       - const: stop-ack
       - const: shutdown-ack
 
+  qcom,smem-states:
+    maxItems: 1
+    description: States used by the AP to signal the Hexagon core
+
+  qcom,smem-state-names:
+    maxItems: 1
+    description: The names of the state bits used for SMP2P output
+
 required:
   - compatible
   - reg
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,sm8550-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,sm8550-pas.yaml
index 4721c04ce09b..faf7b2890de8 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,sm8550-pas.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,sm8550-pas.yaml
@@ -95,6 +95,14 @@ properties:
       - const: stop-ack
       - const: shutdown-ack
 
+  qcom,smem-states:
+    maxItems: 1
+    description: States used by the AP to signal the Hexagon core
+
+  qcom,smem-state-names:
+    maxItems: 1
+    description: The names of the state bits used for SMP2P output
+
 required:
   - compatible
   - reg

-- 
2.34.1


^ permalink raw reply related

* [PATCH v7 4/6] dt-bindings: remoteproc: qcom: Document pas for SoCCP on Hawi and Maili SoC
From: Jingyi Wang @ 2026-06-23 10:05 UTC (permalink / raw)
  To: Bjorn Andersson, Mathieu Poirier, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Manivannan Sadhasivam,
	Luca Weiss, Bartosz Golaszewski, Kumar Patro, Komal Bajaj,
	Konrad Dybcio
  Cc: aiqun.yu, tingwei.zhang, trilok.soni, yijie.yang, linux-arm-msm,
	linux-remoteproc, devicetree, linux-kernel, Jingyi Wang,
	Mukesh Ojha
In-Reply-To: <20260623-knp-soccp-v7-0-1ec7bb5c9fec@oss.qualcomm.com>

From: Mukesh Ojha <mukesh.ojha@oss.qualcomm.com>

Document SoCCP remote processor used on Qualcomm Hawi and Maili SoC which
is fully compatible with Kaanapali.

Co-developed-by: Yijie Yang <yijie.yang@oss.qualcomm.com>
Signed-off-by: Yijie Yang <yijie.yang@oss.qualcomm.com>
Signed-off-by: Mukesh Ojha <mukesh.ojha@oss.qualcomm.com>
Signed-off-by: Jingyi Wang <jingyi.wang@oss.qualcomm.com>
---
 .../devicetree/bindings/remoteproc/qcom,kaanapali-soccp-pas.yaml        | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,kaanapali-soccp-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,kaanapali-soccp-pas.yaml
index ce18460a949f..8fd6913e414d 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,kaanapali-soccp-pas.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,kaanapali-soccp-pas.yaml
@@ -21,6 +21,8 @@ properties:
       - items:
           - enum:
               - qcom,glymur-soccp-pas
+              - qcom,hawi-soccp-pas
+              - qcom,maili-soccp-pas
           - const: qcom,kaanapali-soccp-pas
       - enum:
           - qcom,kaanapali-soccp-pas

-- 
2.34.1


^ permalink raw reply related

* [PATCH v7 2/6] dt-bindings: remoteproc: qcom: move interrupts and interrupt-names list out of pas-common
From: Jingyi Wang @ 2026-06-23 10:05 UTC (permalink / raw)
  To: Bjorn Andersson, Mathieu Poirier, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Manivannan Sadhasivam,
	Luca Weiss, Bartosz Golaszewski, Kumar Patro, Komal Bajaj,
	Konrad Dybcio
  Cc: aiqun.yu, tingwei.zhang, trilok.soni, yijie.yang, linux-arm-msm,
	linux-remoteproc, devicetree, linux-kernel, Jingyi Wang
In-Reply-To: <20260623-knp-soccp-v7-0-1ec7bb5c9fec@oss.qualcomm.com>

Move interrupts and interrupt-names list out of pas-common since they
will be redefined differently for Kaanapali SoCCP.

Signed-off-by: Jingyi Wang <jingyi.wang@oss.qualcomm.com>
---
 .../devicetree/bindings/remoteproc/qcom,adsp.yaml    | 14 ++++++++++++--
 .../bindings/remoteproc/qcom,milos-pas.yaml          | 18 ++++++++++++++----
 .../bindings/remoteproc/qcom,pas-common.yaml         | 16 ++--------------
 .../bindings/remoteproc/qcom,qcs404-pas.yaml         | 14 ++++++++++++--
 .../bindings/remoteproc/qcom,sa8775p-pas.yaml        | 14 ++++++++++++--
 .../bindings/remoteproc/qcom,sc7180-pas.yaml         | 20 ++++++++++++++++++++
 .../bindings/remoteproc/qcom,sc8280xp-pas.yaml       | 20 ++++++++++++++++++++
 .../bindings/remoteproc/qcom,sdx55-pas.yaml          | 16 ++++++++++++++--
 .../bindings/remoteproc/qcom,shikra-pas.yaml         | 20 ++++++++++++++++++++
 .../bindings/remoteproc/qcom,sm6115-pas.yaml         | 20 ++++++++++++++++++++
 .../bindings/remoteproc/qcom,sm6350-pas.yaml         | 20 ++++++++++++++++++++
 .../bindings/remoteproc/qcom,sm6375-pas.yaml         | 20 ++++++++++++++++++++
 .../bindings/remoteproc/qcom,sm8150-pas.yaml         | 20 ++++++++++++++++++++
 .../bindings/remoteproc/qcom,sm8350-pas.yaml         | 20 ++++++++++++++++++++
 .../bindings/remoteproc/qcom,sm8550-pas.yaml         | 20 ++++++++++++++++++++
 15 files changed, 246 insertions(+), 26 deletions(-)

diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,adsp.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,adsp.yaml
index a270834605da..16c35e15ee1b 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,adsp.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,adsp.yaml
@@ -58,10 +58,20 @@ properties:
     description: Firmware name for the Hexagon core
 
   interrupts:
-    maxItems: 5
+    items:
+      - description: Watchdog interrupt
+      - description: Fatal interrupt
+      - description: Ready interrupt
+      - description: Handover interrupt
+      - description: Stop acknowledge interrupt
 
   interrupt-names:
-    maxItems: 5
+    items:
+      - const: wdog
+      - const: fatal
+      - const: ready
+      - const: handover
+      - const: stop-ack
 
 required:
   - compatible
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,milos-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,milos-pas.yaml
index e5cce0d05fc6..d22d50c1e1ea 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,milos-pas.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,milos-pas.yaml
@@ -34,12 +34,22 @@ properties:
       - const: xo
 
   interrupts:
-    minItems: 6
-    maxItems: 6
+    items:
+      - description: Watchdog interrupt
+      - description: Fatal interrupt
+      - description: Ready interrupt
+      - description: Handover interrupt
+      - description: Stop acknowledge interrupt
+      - description: Shutdown acknowledge interrupt
 
   interrupt-names:
-    minItems: 6
-    maxItems: 6
+    items:
+      - const: wdog
+      - const: fatal
+      - const: ready
+      - const: handover
+      - const: stop-ack
+      - const: shutdown-ack
 
   qcom,qmp:
     $ref: /schemas/types.yaml#/definitions/phandle
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,pas-common.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,pas-common.yaml
index 4607b459131b..3847aadfa980 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,pas-common.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,pas-common.yaml
@@ -26,23 +26,11 @@ properties:
 
   interrupts:
     minItems: 5
-    items:
-      - description: Watchdog interrupt
-      - description: Fatal interrupt
-      - description: Ready interrupt
-      - description: Handover interrupt
-      - description: Stop acknowledge interrupt
-      - description: Shutdown acknowledge interrupt
+    maxItems: 6
 
   interrupt-names:
     minItems: 5
-    items:
-      - const: wdog
-      - const: fatal
-      - const: ready
-      - const: handover
-      - const: stop-ack
-      - const: shutdown-ack
+    maxItems: 6
 
   iommus:
     maxItems: 1
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,qcs404-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,qcs404-pas.yaml
index ad45fd00ae34..5854b3d2041d 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,qcs404-pas.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,qcs404-pas.yaml
@@ -32,10 +32,20 @@ properties:
       - const: xo
 
   interrupts:
-    maxItems: 5
+    items:
+      - description: Watchdog interrupt
+      - description: Fatal interrupt
+      - description: Ready interrupt
+      - description: Handover interrupt
+      - description: Stop acknowledge interrupt
 
   interrupt-names:
-    maxItems: 5
+    items:
+      - const: wdog
+      - const: fatal
+      - const: ready
+      - const: handover
+      - const: stop-ack
 
   power-domains: false
   power-domain-names: false
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,sa8775p-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,sa8775p-pas.yaml
index bcd2bcf96e24..7f287e55896e 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,sa8775p-pas.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,sa8775p-pas.yaml
@@ -59,10 +59,20 @@ properties:
       - description: Memory region for main Firmware authentication
 
   interrupts:
-    maxItems: 5
+    items:
+      - description: Watchdog interrupt
+      - description: Fatal interrupt
+      - description: Ready interrupt
+      - description: Handover interrupt
+      - description: Stop acknowledge interrupt
 
   interrupt-names:
-    maxItems: 5
+    items:
+      - const: wdog
+      - const: fatal
+      - const: ready
+      - const: handover
+      - const: stop-ack
 
 required:
   - compatible
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,sc7180-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,sc7180-pas.yaml
index 66b455d0a8e3..cb0a61fc301d 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,sc7180-pas.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,sc7180-pas.yaml
@@ -48,6 +48,26 @@ properties:
     maxItems: 1
     description: Firmware name for the Hexagon core
 
+  interrupts:
+    minItems: 5
+    items:
+      - description: Watchdog interrupt
+      - description: Fatal interrupt
+      - description: Ready interrupt
+      - description: Handover interrupt
+      - description: Stop acknowledge interrupt
+      - description: Shutdown acknowledge interrupt
+
+  interrupt-names:
+    minItems: 5
+    items:
+      - const: wdog
+      - const: fatal
+      - const: ready
+      - const: handover
+      - const: stop-ack
+      - const: shutdown-ack
+
 required:
   - compatible
   - reg
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,sc8280xp-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,sc8280xp-pas.yaml
index 8227527c1d77..fef9d7c39f3c 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,sc8280xp-pas.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,sc8280xp-pas.yaml
@@ -45,6 +45,26 @@ properties:
     maxItems: 1
     description: Firmware name for the Hexagon core
 
+  interrupts:
+    minItems: 5
+    items:
+      - description: Watchdog interrupt
+      - description: Fatal interrupt
+      - description: Ready interrupt
+      - description: Handover interrupt
+      - description: Stop acknowledge interrupt
+      - description: Shutdown acknowledge interrupt
+
+  interrupt-names:
+    minItems: 5
+    items:
+      - const: wdog
+      - const: fatal
+      - const: ready
+      - const: handover
+      - const: stop-ack
+      - const: shutdown-ack
+
 required:
   - compatible
   - reg
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,sdx55-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,sdx55-pas.yaml
index 8c4abde74915..2bbd427c6ea4 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,sdx55-pas.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,sdx55-pas.yaml
@@ -30,10 +30,22 @@ properties:
       - const: xo
 
   interrupts:
-    minItems: 6
+    items:
+      - description: Watchdog interrupt
+      - description: Fatal interrupt
+      - description: Ready interrupt
+      - description: Handover interrupt
+      - description: Stop acknowledge interrupt
+      - description: Shutdown acknowledge interrupt
 
   interrupt-names:
-    minItems: 6
+    items:
+      - const: wdog
+      - const: fatal
+      - const: ready
+      - const: handover
+      - const: stop-ack
+      - const: shutdown-ack
 
   power-domains:
     items:
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,shikra-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,shikra-pas.yaml
index 253b14eb2b59..34a2f15e9361 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,shikra-pas.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,shikra-pas.yaml
@@ -51,6 +51,26 @@ properties:
       and devices related to the remoteproc core.
     unevaluatedProperties: false
 
+  interrupts:
+    minItems: 5
+    items:
+      - description: Watchdog interrupt
+      - description: Fatal interrupt
+      - description: Ready interrupt
+      - description: Handover interrupt
+      - description: Stop acknowledge interrupt
+      - description: Shutdown acknowledge interrupt
+
+  interrupt-names:
+    minItems: 5
+    items:
+      - const: wdog
+      - const: fatal
+      - const: ready
+      - const: handover
+      - const: stop-ack
+      - const: shutdown-ack
+
   qcom,smem-states:
     $ref: /schemas/types.yaml#/definitions/phandle-array
     description: States used by the AP to signal the Hexagon core
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,sm6115-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,sm6115-pas.yaml
index eeb6a8aafeb9..987fac433fae 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,sm6115-pas.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,sm6115-pas.yaml
@@ -51,6 +51,26 @@ properties:
     maxItems: 1
     description: Firmware name for the Hexagon core
 
+  interrupts:
+    minItems: 5
+    items:
+      - description: Watchdog interrupt
+      - description: Fatal interrupt
+      - description: Ready interrupt
+      - description: Handover interrupt
+      - description: Stop acknowledge interrupt
+      - description: Shutdown acknowledge interrupt
+
+  interrupt-names:
+    minItems: 5
+    items:
+      - const: wdog
+      - const: fatal
+      - const: ready
+      - const: handover
+      - const: stop-ack
+      - const: shutdown-ack
+
 required:
   - compatible
   - reg
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,sm6350-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,sm6350-pas.yaml
index c1a3cc308bdb..53ffb1ccd199 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,sm6350-pas.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,sm6350-pas.yaml
@@ -45,6 +45,26 @@ properties:
     maxItems: 1
     description: Firmware name for the Hexagon core
 
+  interrupts:
+    minItems: 5
+    items:
+      - description: Watchdog interrupt
+      - description: Fatal interrupt
+      - description: Ready interrupt
+      - description: Handover interrupt
+      - description: Stop acknowledge interrupt
+      - description: Shutdown acknowledge interrupt
+
+  interrupt-names:
+    minItems: 5
+    items:
+      - const: wdog
+      - const: fatal
+      - const: ready
+      - const: handover
+      - const: stop-ack
+      - const: shutdown-ack
+
 required:
   - compatible
   - reg
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,sm6375-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,sm6375-pas.yaml
index 7286b2baa19f..6823a2a8d74e 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,sm6375-pas.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,sm6375-pas.yaml
@@ -39,6 +39,26 @@ properties:
     maxItems: 1
     description: Firmware name for the Hexagon core
 
+  interrupts:
+    minItems: 5
+    items:
+      - description: Watchdog interrupt
+      - description: Fatal interrupt
+      - description: Ready interrupt
+      - description: Handover interrupt
+      - description: Stop acknowledge interrupt
+      - description: Shutdown acknowledge interrupt
+
+  interrupt-names:
+    minItems: 5
+    items:
+      - const: wdog
+      - const: fatal
+      - const: ready
+      - const: handover
+      - const: stop-ack
+      - const: shutdown-ack
+
   smd-edge: false
 
 required:
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,sm8150-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,sm8150-pas.yaml
index a8cddf7e2fe1..8a1fae095a3b 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,sm8150-pas.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,sm8150-pas.yaml
@@ -61,6 +61,26 @@ properties:
     maxItems: 1
     description: Firmware name for the Hexagon core
 
+  interrupts:
+    minItems: 5
+    items:
+      - description: Watchdog interrupt
+      - description: Fatal interrupt
+      - description: Ready interrupt
+      - description: Handover interrupt
+      - description: Stop acknowledge interrupt
+      - description: Shutdown acknowledge interrupt
+
+  interrupt-names:
+    minItems: 5
+    items:
+      - const: wdog
+      - const: fatal
+      - const: ready
+      - const: handover
+      - const: stop-ack
+      - const: shutdown-ack
+
 required:
   - compatible
   - reg
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,sm8350-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,sm8350-pas.yaml
index 6d09823153fc..4ea7518db537 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,sm8350-pas.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,sm8350-pas.yaml
@@ -55,6 +55,26 @@ properties:
     maxItems: 1
     description: Firmware name for the Hexagon core
 
+  interrupts:
+    minItems: 5
+    items:
+      - description: Watchdog interrupt
+      - description: Fatal interrupt
+      - description: Ready interrupt
+      - description: Handover interrupt
+      - description: Stop acknowledge interrupt
+      - description: Shutdown acknowledge interrupt
+
+  interrupt-names:
+    minItems: 5
+    items:
+      - const: wdog
+      - const: fatal
+      - const: ready
+      - const: handover
+      - const: stop-ack
+      - const: shutdown-ack
+
 required:
   - compatible
   - reg
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,sm8550-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,sm8550-pas.yaml
index 9f30a38152a3..4721c04ce09b 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,sm8550-pas.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,sm8550-pas.yaml
@@ -75,6 +75,26 @@ properties:
       - description: DSM Memory region 2
       - description: Memory region for Qlink Logging
 
+  interrupts:
+    minItems: 5
+    items:
+      - description: Watchdog interrupt
+      - description: Fatal interrupt
+      - description: Ready interrupt
+      - description: Handover interrupt
+      - description: Stop acknowledge interrupt
+      - description: Shutdown acknowledge interrupt
+
+  interrupt-names:
+    minItems: 5
+    items:
+      - const: wdog
+      - const: fatal
+      - const: ready
+      - const: handover
+      - const: stop-ack
+      - const: shutdown-ack
+
 required:
   - compatible
   - reg

-- 
2.34.1


^ permalink raw reply related

* [PATCH v7 1/6] dt-bindings: remoteproc: qcom: cleanup qcom,adsp.yaml
From: Jingyi Wang @ 2026-06-23 10:05 UTC (permalink / raw)
  To: Bjorn Andersson, Mathieu Poirier, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Manivannan Sadhasivam,
	Luca Weiss, Bartosz Golaszewski, Kumar Patro, Komal Bajaj,
	Konrad Dybcio
  Cc: aiqun.yu, tingwei.zhang, trilok.soni, yijie.yang, linux-arm-msm,
	linux-remoteproc, devicetree, linux-kernel, Jingyi Wang,
	Krzysztof Kozlowski
In-Reply-To: <20260623-knp-soccp-v7-0-1ec7bb5c9fec@oss.qualcomm.com>

Items in qcom,adsp.yaml has common clock and interrupt properties, move
these out of the allOf section to avoid list the compatible repeatly.

Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Signed-off-by: Jingyi Wang <jingyi.wang@oss.qualcomm.com>
---
 .../devicetree/bindings/remoteproc/qcom,adsp.yaml  | 64 +++++-----------------
 1 file changed, 14 insertions(+), 50 deletions(-)

diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,adsp.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,adsp.yaml
index 16a245fe2738..a270834605da 100644
--- a/Documentation/devicetree/bindings/remoteproc/qcom,adsp.yaml
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,adsp.yaml
@@ -32,6 +32,14 @@ properties:
   reg:
     maxItems: 1
 
+  clocks:
+    items:
+      - description: XO clock
+
+  clock-names:
+    items:
+      - const: xo
+
   cx-supply: true
 
   px-supply:
@@ -49,6 +57,12 @@ properties:
     maxItems: 1
     description: Firmware name for the Hexagon core
 
+  interrupts:
+    maxItems: 5
+
+  interrupt-names:
+    maxItems: 5
+
 required:
   - compatible
   - memory-region
@@ -57,56 +71,6 @@ unevaluatedProperties: false
 
 allOf:
   - $ref: /schemas/remoteproc/qcom,pas-common.yaml#
-  - if:
-      properties:
-        compatible:
-          contains:
-            enum:
-              - qcom,msm8226-adsp-pil
-              - qcom,msm8953-adsp-pil
-              - qcom,msm8974-adsp-pil
-              - qcom,msm8996-adsp-pil
-              - qcom,msm8996-slpi-pil
-              - qcom,msm8998-adsp-pas
-              - qcom,msm8998-slpi-pas
-              - qcom,sdm660-adsp-pas
-              - qcom,sdm660-cdsp-pas
-              - qcom,sdm845-adsp-pas
-              - qcom,sdm845-cdsp-pas
-              - qcom,sdm845-slpi-pas
-    then:
-      properties:
-        clocks:
-          items:
-            - description: XO clock
-        clock-names:
-          items:
-            - const: xo
-
-  - if:
-      properties:
-        compatible:
-          contains:
-            enum:
-              - qcom,msm8226-adsp-pil
-              - qcom,msm8953-adsp-pil
-              - qcom,msm8974-adsp-pil
-              - qcom,msm8996-adsp-pil
-              - qcom,msm8996-slpi-pil
-              - qcom,msm8998-adsp-pas
-              - qcom,msm8998-slpi-pas
-              - qcom,sdm660-adsp-pas
-              - qcom,sdm660-cdsp-pas
-              - qcom,sdm845-adsp-pas
-              - qcom,sdm845-cdsp-pas
-              - qcom,sdm845-slpi-pas
-    then:
-      properties:
-        interrupts:
-          maxItems: 5
-        interrupt-names:
-          maxItems: 5
-
   - if:
       properties:
         compatible:

-- 
2.34.1


^ permalink raw reply related

* [PATCH v7 0/6] Add binding and driver for Kaanapali SoCCP
From: Jingyi Wang @ 2026-06-23 10:05 UTC (permalink / raw)
  To: Bjorn Andersson, Mathieu Poirier, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Manivannan Sadhasivam,
	Luca Weiss, Bartosz Golaszewski, Kumar Patro, Komal Bajaj,
	Konrad Dybcio
  Cc: aiqun.yu, tingwei.zhang, trilok.soni, yijie.yang, linux-arm-msm,
	linux-remoteproc, devicetree, linux-kernel, Jingyi Wang,
	Krzysztof Kozlowski, Mukesh Ojha, Gokul Krishna Krishnakumar,
	Shawn Guo, Dmitry Baryshkov, Bartosz Golaszewski

Add initial support for SoCCP on Qualcomm Kaanapali platform. SoC Control
Processor (SoCCP) is loaded by bootloader on Kaanapali. PAS loader will
check the state of the subsystem, and set the status "attached". As the
interrupts are redefined differently for Kaanapali SoCCP, list for the
interrupt properties are moved out of pas-common.

When we return fail in the rproc attach op, current remoteproc core cannot
handle it correctly for further recovery/firmware loading, which should be
generic problem shared across all remoteproc drivers that do attach and
not mandatory for normal bring up, a separate series is used for resolving
this:
https://lore.kernel.org/all/20260623-rproc-attach-issue-v3-0-8e24310707ce@oss.qualcomm.com/

Signed-off-by: Jingyi Wang <jingyi.wang@oss.qualcomm.com>
---
Changes in v7:
- minor coding style fix - Shawn
- check stop-ack and shutdown-ack when doing attach - Stephan
- update "qcom,smem-states" to fix phandle-array check fail - Rob
- Add interrupt list to qcom,shikra-pas.yaml
- pick binding for Maili SoCCP
- Link to v1: https://lore.kernel.org/r/20260519-knp-soccp-v6-0-cf5d0e194b5f@oss.qualcomm.com

Changes in v6:
- remove ping-pong in the attach workflow - Bjorn
- fix handover_irq imbalance - Stephan
- add attaach callback to minidump ops table - Jie
- pick binding for Hawi SoCCP
- Link to v5: https://lore.kernel.org/r/20260409-knp-soccp-v5-0-805a492124da@oss.qualcomm.com

Changes in v5:
- squash "qcom,smem-states" patch with the change changing pas-common
- drop the patch that set recovery_disabled
- remove the 5 seconds timeout in qcom_pas_attach and related logic
- patch rebase and add reviewed-by tag
- Link to v4: https://lore.kernel.org/all/20260310-knp-soccp-v4-0-0a91575e0e7e@oss.qualcomm.com/

Changes in v4:
- drop adsp/cdsp binding that have been applied
- move interrupt list out of pas-common yaml
- add constraint for smem-states in each file
- "wake-ack" interrupt and "wakeup"/"sleep" smem state have been deprecated in design, drop these
- coding style fixup
- add a patch to disable recovery during rproc_add to make sure rproc_report_crash can be called correctly during qcom_pas_attach
- update the handling for irq_get_irqchip_state -ENODEV in attach path
- skip qcom_q6v5_unprepare if the state is RPROC_ATTACHED
- Link to v3: https://lore.kernel.org/all/20251223-knp-remoteproc-v3-0-5b09885c55a5@oss.qualcomm.com

Changes in v3:
- Drop Glymur ADSP/CDSP binding 
- Extend the "interrupts" and "interrupt-names" properties in the pas-common
- add missing IPCC_MPROC_SOCCP definition
- fix complie err caused by qcom_q6v5_wcss.c
- code clean up for late attach feature
- call rproc_report_crash() instead of set RPROC_CRASHED state
- fix q6v5.running and q6v5.handover_issued state handling
- if wait_for_completion_timeout return 0, set RPROC_OFFLINE for PAS loader
- Only ping the subsystem if ready_state is set
- Link to v2: https://lore.kernel.org/r/20251029-knp-remoteproc-v2-0-6c81993b52ea@oss.qualcomm.com

Changes in v2:
- Drop MPSS change
- pick Glymur changes from https://lore.kernel.org/linux-arm-msm/20250924183726.509202-1-sibi.sankar@oss.qualcomm.com
- Drop redundant adsp bindings - Dmitry
- Clarify Kaanapali CDSP compatible in commit msg - Krzysztof
- include pas-common.yaml in soccp yaml and extend the common part - Krzysztof
- Clear early_boot flag in the adsp stop callback - Dmitry
- Use .mbn in soccp driver node - Konrad
- Link to v1: https://lore.kernel.org/r/20250924-knp-remoteproc-v1-0-611bf7be8329@oss.qualcomm.com

---
Jingyi Wang (5):
      dt-bindings: remoteproc: qcom: cleanup qcom,adsp.yaml
      dt-bindings: remoteproc: qcom: move interrupts and interrupt-names list out of pas-common
      dt-bindings: remoteproc: qcom: Document pas for SoCCP on Kaanapali and Glymur platforms
      remoteproc: qcom: pas: Add late attach support for subsystems
      remoteproc: qcom_q6v5_pas: Add SoCCP node on Kaanapali

Mukesh Ojha (1):
      dt-bindings: remoteproc: qcom: Document pas for SoCCP on Hawi and Maili SoC

 .../devicetree/bindings/remoteproc/qcom,adsp.yaml  |  82 +++++------
 .../remoteproc/qcom,kaanapali-soccp-pas.yaml       | 156 +++++++++++++++++++++
 .../bindings/remoteproc/qcom,milos-pas.yaml        |  26 +++-
 .../bindings/remoteproc/qcom,pas-common.yaml       |  28 ++--
 .../bindings/remoteproc/qcom,qcs404-pas.yaml       |  22 ++-
 .../bindings/remoteproc/qcom,sa8775p-pas.yaml      |  22 ++-
 .../bindings/remoteproc/qcom,sc7180-pas.yaml       |  28 ++++
 .../bindings/remoteproc/qcom,sc8280xp-pas.yaml     |  28 ++++
 .../bindings/remoteproc/qcom,sdx55-pas.yaml        |  24 +++-
 .../bindings/remoteproc/qcom,shikra-pas.yaml       |  20 +++
 .../bindings/remoteproc/qcom,sm6115-pas.yaml       |  28 ++++
 .../bindings/remoteproc/qcom,sm6350-pas.yaml       |  28 ++++
 .../bindings/remoteproc/qcom,sm6375-pas.yaml       |  28 ++++
 .../bindings/remoteproc/qcom,sm8150-pas.yaml       |  28 ++++
 .../bindings/remoteproc/qcom,sm8350-pas.yaml       |  28 ++++
 .../bindings/remoteproc/qcom,sm8550-pas.yaml       |  28 ++++
 drivers/remoteproc/qcom_common.h                   |   6 +
 drivers/remoteproc/qcom_q6v5.c                     |   3 +-
 drivers/remoteproc/qcom_q6v5_pas.c                 |  86 ++++++++++++
 drivers/remoteproc/qcom_sysmon.c                   |  19 +++
 20 files changed, 642 insertions(+), 76 deletions(-)
---
base-commit: 4fa3f5fabb30bf00d7475d5a33459ea83d639bf9
change-id: 20260623-knp-soccp-d7c3f790f0c1

Best regards,
-- 
Jingyi Wang <jingyi.wang@oss.qualcomm.com>


^ permalink raw reply

* Re: [PATCH net] net: ethernet: qualcomm: ppe: Demote from supported and fix maintainer addresses
From: Krzysztof Kozlowski @ 2026-06-23  9:55 UTC (permalink / raw)
  To: Jie Luo, Andrew Lunn
  Cc: Bjorn Andersson, Michael Turquette, Stephen Boyd, Brian Masney,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Andrew Lunn,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Lei Wei, Suruchi Agarwal, Pavithra R, linux-kernel, linux-arm-msm,
	linux-clk, devicetree, netdev
In-Reply-To: <8b0560ae-af5c-4d54-be02-d186be1d799c@oss.qualcomm.com>

On 23/06/2026 11:42, Jie Luo wrote:
> 
> 
> On 6/23/2026 4:10 PM, Andrew Lunn wrote:
>>> Driver is not supported - in terms of how netdev understands supported
>>> commitment - if maintainer does not care to receive the patches for its
>>> code, so demote it to "maintained" to reflect true status.
>>
>> Maybe "Orphan" would be better, if the listed Maintainer is not doing
>> any Maintainer work?
>>
>> 	   Andrew	   
> 
> Hello Andrew, Krzysztof,
> I will continue to maintain the listed drivers, so their status can
> remain Supported.

Do you understand the commitment/meaning of supported in networking
subsystem? Do you commit to the time frames netdev is asking, including
running the tests and reporting results TWICE per day (minimum frequency
is ever 12 hours)?

If address did not work for half a year, I really doubt that you commit
to above.

Best regards,
Krzysztof

^ permalink raw reply

* Re: [PATCH v2 1/3] dt-bindings: power: limits: Describe Qualcomm SPEL hardware
From: Manaf Meethalavalappu Pallikunhi @ 2026-06-23  9:47 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Rafael J. Wysocki,
	Bjorn Andersson, Konrad Dybcio, Daniel Lezcano, Gaurav Kohli,
	linux-arm-msm, devicetree, linux-kernel, linux-pm
In-Reply-To: <20260622-armored-witty-tarantula-84a28a@quoll>

Hi Krzysztof,


On 6/22/2026 5:58 PM, Krzysztof Kozlowski wrote:
> On Sat, Jun 20, 2026 at 02:09:08AM +0530, Manaf Meethalavalappu Pallikunhi wrote:
>> The Qualcomm SoC Power and Electrical Limits (SPEL) provides hardware
>> based power monitoring and limiting capabilities for various domains.
>>
>> Add a DeviceTree binding to describe the SPEL block on Qualcomm's SoC.
>>
>> Signed-off-by: Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com>
>> ---
>>   .../bindings/power/limits/qcom,spel.yaml           | 47 ++++++++++++++++++++++
>>   MAINTAINERS                                        |  6 +++
>>   2 files changed, 53 insertions(+)
>>
>> diff --git a/Documentation/devicetree/bindings/power/limits/qcom,spel.yaml b/Documentation/devicetree/bindings/power/limits/qcom,spel.yaml
> 
> What is "limits" directory for? What sort of class of devices fit there?

Added for devices that integrate with the powercap framework (exposed 
via sys/class/powercap). These devices are responsible for enforcing and 
monitoring power consumption limits across different domains, such as 
the system, SoC, or specific subsystems. Any other better directory ?

> 
>> new file mode 100644
>> index 000000000000..4c6e6cbfbfe4
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/power/limits/qcom,spel.yaml
> 
> Filename should match the compatible, so qcom,glymur-spel.yaml

ACK

> 
>> @@ -0,0 +1,47 @@
>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/power/limits/qcom,spel.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Qualcomm SoC Power and Electrical Limits (SPEL)
>> +
>> +maintainers:
>> +  - Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com>
>> +
>> +description:
>> +  The Qualcomm SPEL (SoC Power and Electrical Limits) provides hardware-based
>> +  power monitoring and limiting capabilities for various power domains in
>> +  Qualcomm SoCs.
> 
> Please describe here more what is this limiting capabilities.

ACK

Thanks,
Manaf

> 
>> +
>> +properties:
>> +  compatible:
>> +    const: qcom,glymur-spel
>> +
>> +  reg:
>> +    maxItems: 3
>> +
>> +  reg-names:
>> +    items:
>> +      - const: config
>> +      - const: constraints
>> +      - const: nodes
> 
> Best regards,
> Krzysztof
> 


^ permalink raw reply

* Re: [PATCH v3 0/4] ROCK 4D audio enablement
From: Alexandre Belloni @ 2026-06-23  9:47 UTC (permalink / raw)
  To: Nicolas Frattaroli
  Cc: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Heiko Stuebner, kernel, linux-input, devicetree, linux-kernel,
	linux-arm-kernel, linux-rockchip, Krzysztof Kozlowski,
	Cristian Ciocaltea
In-Reply-To: <wE1x9P2vQlC8kihOSm9uOA@collabora.com>

Hello Nicolas,

I guess Dmitry is the one that would take patches 1 to 3. You should
probably resend once the merge window has closed.

On 11/05/2026 18:21:40+0200, Nicolas Frattaroli wrote:
> Hi Alexandre, and other maintainers,
> 
> On Wednesday, 8 April 2026 19:49:38 Central European Summer Time Nicolas Frattaroli wrote:
> > The ROCK 4D uses an ADC input to distinguish between a headphone (i.e.,
> > no mic) and a headset (i.e., with mic). After some searching, it appears
> > that the closest we can get to modelling this is by sending a particular
> > switch input event.
> > 
> > So this series modifies the adc-keys bindings, extends the adc-keys
> > driver to allow sending other input types as well, and then adds the
> > analog audio nodes to ROCK 4D's device tree.
> > 
> > It should be noted that analog capture from the TRRS jack currently
> > results in completely digitally silent audio for me, i.e. no data other
> > than 0xFF. There's a few reasons why this could happen, chief among them
> > that my SAI driver is broken or that the ES8328 codec driver is once
> > again broken. The DAPM routes when graphed out look fine though. So the
> > DTS part is correct, and I can fix the broken capture in a separate
> > follow-up patch that doesn't have to include DT people.
> > 
> > Another possibility is that my phone headset, despite being 4 rings and
> > having a little pin hole at the back of the volume doodad, does not
> > actually have a microphone, but in that case I'd still expect some noise
> > in the PCM. Maybe it's just shy.
> > 
> > Signed-off-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
> > ---
> > Changes in v3:
> > - bindings: use unevaluatedProperties instead of explicitly mentioning
> >   linux,input-type.
> > - Link to v2: https://lore.kernel.org/r/20251215-rock4d-audio-v2-0-82a61de39b4c@collabora.com
> > 
> > Changes in v2:
> > - Drop HDMI audio patch, as it was already merged.
> > - adc-keys: rename "keycode" to "code".
> > - adc-keys: make the keycode (now "code") local a u32 instead of an int
> > - adc-keys: only allow EV_KEY and EV_SW for now. Rename patch
> >   accordingly.
> > - adc-keys: Add another patch to rework probe function error logging.
> > - Link to v1: https://lore.kernel.org/r/20250630-rock4d-audio-v1-0-0b3c8e8fda9c@collabora.com
> > 
> > ---
> > Nicolas Frattaroli (4):
> >       dt-bindings: input: adc-keys: allow all input properties
> >       Input: adc-keys - support EV_SW as well, not just EV_KEY.
> >       Input: adc-keys - Use dev_err_probe in probe function
> >       arm64: dts: rockchip: add analog audio to ROCK 4D
> > 
> >  .../devicetree/bindings/input/adc-keys.yaml        | 17 ++--
> >  arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts    | 90 ++++++++++++++++++++++
> >  drivers/input/keyboard/adc-keys.c                  | 88 ++++++++++-----------
> >  3 files changed, 147 insertions(+), 48 deletions(-)
> > ---
> > base-commit: 8de395f35e79d9168a78504fed495578ec7bac52
> > change-id: 20250627-rock4d-audio-cfc07f168a08
> > 
> > Best regards,
> > --  
> > Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
> > 
> > 
> 
> What's the path forward here? All the patches are reviewed, but it
> has been almost a month without them being applied now.
> 
> Which tree(s) would this be applied to, and who should I poke?
> 
> Thanks :)
> 
> Kind regards,
> Nicolas Frattaroli
> 
> 
> 

-- 
Alexandre Belloni, co-owner and COO, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com

^ permalink raw reply

* [PATCH] dt-bindings: mmc: Convert TI OMAP2420 MMC to DT schema
From: Eduard Bostina @ 2026-06-23  9:43 UTC (permalink / raw)
  To: daniel.baluta, simona.toaca, goledhruva, m-chawdhry, egbostina,
	Ulf Hansson, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	linux-mmc, devicetree, linux-kernel

Convert the Texas Instruments MMC host controller bindings
to DT schema.

Note that the OMAP2420 driver will not work with OMAP2430 or later omaps.
Please see the OMAP HSMMC driver for current OMAPs.

Signed-off-by: Eduard Bostina <egbostina@gmail.com>
---
 .../bindings/mmc/ti,omap2420-mmc.yaml         | 61 +++++++++++++++++++
 .../devicetree/bindings/mmc/ti-omap.txt       | 26 --------
 2 files changed, 61 insertions(+), 26 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/mmc/ti,omap2420-mmc.yaml
 delete mode 100644 Documentation/devicetree/bindings/mmc/ti-omap.txt

diff --git a/Documentation/devicetree/bindings/mmc/ti,omap2420-mmc.yaml b/Documentation/devicetree/bindings/mmc/ti,omap2420-mmc.yaml
new file mode 100644
index 000000000..31a73c130
--- /dev/null
+++ b/Documentation/devicetree/bindings/mmc/ti,omap2420-mmc.yaml
@@ -0,0 +1,61 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mmc/ti,omap2420-mmc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Texas Instruments MMC host controller for OMAP1 and 2420
+
+maintainers:
+  - Eduard Bostina <egbostina@gmail.com>
+
+description: |
+  The MMC Host Controller on TI OMAP1 and 2420 family provides
+  an interface for MMC, SD, and SDIO types of memory cards.
+
+allOf:
+  - $ref: /schemas/mmc/mmc-controller.yaml#
+
+properties:
+  compatible:
+    const: ti,omap2420-mmc
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  ti,hwmods:
+    description: |
+      Name of the hwmod associated to the MMC controller. Must be
+      "msdi<n>" where n is the controller instance starting from 1.
+    $ref: /schemas/types.yaml#/definitions/string-array
+    items:
+      - pattern: "^msdi[0-9]+$"
+
+  dmas:
+    maxItems: 2
+
+  dma-names:
+    items:
+      - const: tx
+      - const: rx
+
+required:
+  - compatible
+  - reg
+  - interrupts
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    mmc@4809c000 {
+        compatible = "ti,omap2420-mmc";
+        ti,hwmods = "msdi1";
+        reg = <0x4809c000 0x80>;
+        interrupts = <83>;
+        dmas = <&sdma 61 &sdma 62>;
+        dma-names = "tx", "rx";
+    };
diff --git a/Documentation/devicetree/bindings/mmc/ti-omap.txt b/Documentation/devicetree/bindings/mmc/ti-omap.txt
deleted file mode 100644
index 02fd31cf3..000000000
--- a/Documentation/devicetree/bindings/mmc/ti-omap.txt
+++ /dev/null
@@ -1,26 +0,0 @@
-* TI MMC host controller for OMAP1 and 2420
-
-The MMC Host Controller on TI OMAP1 and 2420 family provides
-an interface for MMC, SD, and SDIO types of memory cards.
-
-This file documents differences between the core properties described
-by mmc.txt and the properties used by the omap mmc driver.
-
-Note that this driver will not work with omap2430 or later omaps,
-please see the omap hsmmc driver for the current omaps.
-
-Required properties:
-- compatible: Must be "ti,omap2420-mmc", for OMAP2420 controllers
-- ti,hwmods: For 2420, must be "msdi<n>", where n is controller
-  instance starting 1
-
-Examples:
-
-	msdi1: mmc@4809c000 {
-		compatible = "ti,omap2420-mmc";
-		ti,hwmods = "msdi1";
-		reg = <0x4809c000 0x80>;
-		interrupts = <83>;
-		dmas = <&sdma 61 &sdma 62>;
-		dma-names = "tx", "rx";
-	};
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH net] net: ethernet: qualcomm: ppe: Demote from supported and fix maintainer addresses
From: Jie Luo @ 2026-06-23  9:42 UTC (permalink / raw)
  To: Andrew Lunn, Krzysztof Kozlowski
  Cc: Bjorn Andersson, Michael Turquette, Stephen Boyd, Brian Masney,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Andrew Lunn,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Lei Wei, Suruchi Agarwal, Pavithra R, linux-kernel, linux-arm-msm,
	linux-clk, devicetree, netdev
In-Reply-To: <0247dfba-1c14-4fea-aab3-5489a36f35f6@lunn.ch>



On 6/23/2026 4:10 PM, Andrew Lunn wrote:
>> Driver is not supported - in terms of how netdev understands supported
>> commitment - if maintainer does not care to receive the patches for its
>> code, so demote it to "maintained" to reflect true status.
> 
> Maybe "Orphan" would be better, if the listed Maintainer is not doing
> any Maintainer work?
> 
> 	   Andrew	   

Hello Andrew, Krzysztof,
I will continue to maintain the listed drivers, so their status can
remain Supported.

^ permalink raw reply

* Re: [PATCH v4 1/3] iio: adc: Fix incorrect reading when datarate changed in single mode
From: Andy Shevchenko @ 2026-06-23  9:39 UTC (permalink / raw)
  To: Joshua Crofts
  Cc: Jakub Szczudlo, linux-iio, andy, antoniu.miclaus, conor+dt,
	devicetree, dlechner, duje, jic23, jishnu.prakash, jorge.marques,
	krzk+dt, linusw, linux-kernel, marcelo.schmitt, mazziesaccount,
	mike.looijmans, nuno.sa, robh, sakari.ailus, wens
In-Reply-To: <20260623112953.000066cc@gmail.com>

On Tue, Jun 23, 2026 at 11:29:53AM +0200, Joshua Crofts wrote:
> On Tue, 23 Jun 2026 12:16:39 +0300
> Andy Shevchenko <andriy.shevchenko@intel.com> wrote:

...

> > > +	return read_poll_timeout(ads1100_new_data_not_ready, data_ready,
> > > +				 !data_ready, wait_time,
> 
> I'd actually be all for using `data_ready != 0`, to make the condition more
> readable.

I am okay with either. It might be slightly clearer if the comparison is done
for some dynamic counting or so, when 0 is not special.

...

> > > +		PM_RUNTIME_ACQUIRE_AUTOSUSPEND(&data->client->dev, pm);  
> > 
> > > +  
> > 
> > This blank line is not needed as they are coupled, but I don't know if we have
> > an agreed style in IIO for this.
> 
> I'd be surprised if there was an agreed style, as there aren't any IIO drivers
> that use this specific macro (not in mainline at least). Additionally, might I
> suggest using `PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND` as it is more generic?

We have other PM_ACQUIRE_*() macros in the drivers in IIO, so we have some style,
but I haven't checked what is that.

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply

* Re: [PATCH 1/3] arm64: dts: qcom: sm8450: Add IPA support
From: Konrad Dybcio @ 2026-06-23  9:37 UTC (permalink / raw)
  To: esteuwu, Bjorn Andersson, Konrad Dybcio, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Alex Elder
  Cc: linux-arm-msm, devicetree, linux-kernel, netdev
In-Reply-To: <20260622-sm8450-ipa-v1-1-532f0299f96e@proton.me>

On 6/23/26 3:44 AM, Esteban Urrutia via B4 Relay wrote:
> From: Esteban Urrutia <esteuwu@proton.me>
> 
> Add support for IPA in DT while expanding the IMEM region just enough to
> accommodate the modem tables used by IPA.
> As reference, SM8450 uses IPA v5.1.
> 
> Signed-off-by: Esteban Urrutia <esteuwu@proton.me>
> ---

[...]

>  arch/arm64/boot/dts/qcom/sm8450.dtsi | 55 ++++++++++++++++++++++++++++++++----
>  1 file changed, 50 insertions(+), 5 deletions(-)
> 
> diff --git a/arch/arm64/boot/dts/qcom/sm8450.dtsi b/arch/arm64/boot/dts/qcom/sm8450.dtsi
> index 56cb6e959e4e..c904720008fa 100644
> --- a/arch/arm64/boot/dts/qcom/sm8450.dtsi
> +++ b/arch/arm64/boot/dts/qcom/sm8450.dtsi
> @@ -2639,6 +2639,47 @@ adreno_smmu: iommu@3da0000 {
>  			dma-coherent;
>  		};
>  
> +		ipa: ipa@3f40000 {
> +			compatible = "qcom,sm8450-ipa";
> +
> +			iommus = <&apps_smmu 0x5c0 0x0>,
> +				 <&apps_smmu 0x5c2 0x0>;
> +			reg = <0 0x3f40000 0 0x10000>,
> +			      <0 0x3f50000 0 0x5000>,

size = 0xb0000 for the RAM and uC regions that the driver seems
to poke at (at a glance anyway..)

[...]

>  		usb_1_hsphy: phy@88e3000 {
>  			compatible = "qcom,sm8450-usb-hs-phy",
>  				     "qcom,usb-snps-hs-7nm-phy";
> @@ -4970,17 +5011,21 @@ cti@13900000 {
>  			clock-names = "apb_pclk";
>  		};
>  
> -		sram@146aa000 {
> +		sram@146a8000 {
>  			compatible = "qcom,sm8450-imem", "syscon", "simple-mfd";
> -			reg = <0 0x146aa000 0 0x1000>;
> -			ranges = <0 0 0x146aa000 0x1000>;
> +			reg = <0 0x146a8000 0 0x3000>;

base=0x1468_0000
size=0x40_000

Konrad

^ permalink raw reply

* Re: [PATCH v2 3/4] drm/ssd130x: Add SSD135X_FAMILY and SSD1351 support
From: Andy Shevchenko @ 2026-06-23  9:37 UTC (permalink / raw)
  To: Amit Barzilai
  Cc: javierm, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, andy, gregkh, deller, azuddinadam,
	chintanlike, dri-devel, devicetree, linux-kernel, linux-fbdev,
	linux-staging
In-Reply-To: <20260622152506.78627-4-amit.barzilai22@gmail.com>

On Mon, Jun 22, 2026 at 06:25:05PM +0300, Amit Barzilai wrote:
> The Solomon SSD1351 is a 128x128 RGB color OLED controller. It shares
> the SSD133X data path: a column/row addressing window followed by a bulk
> RGB565 pixel write. Add it as a new SSD135X_FAMILY rather than a separate
> driver, reusing the SSD133X plane, CRTC and blit/clear helpers.
> 
> The only data-path difference is that the SSD1351 requires an explicit
> Write RAM command (0x5c) after the address window is programmed, before
> pixel data is accepted, whereas the SSD133X enters data mode implicitly.
> This is emitted from a shared ssd133x_write_pixels() helper so both the
> damage-update and clear-screen paths cover it.
> 
> The SSD1351 also needs its own init sequence (ssd135x_init), dispatched
> via ssd135x_encoder_atomic_enable, and a longer post-reset settle delay.
> The re-map byte is fixed at 0 degrees, 65k color, COM split, BGR
> sub-pixel order; rotation is not supported.
> 
> The SSD1351 is SPI-only, so only the SPI transport match tables gain an
> entry; no new config symbol is needed.

...

> const struct ssd130x_deviceinfo ssd130x_variants[] = {

>  		.default_height = 64,
>  		.format_rgb565 = 1,
>  		.family_id = SSD133X_FAMILY,
> +	},
> +	/* ssd135x family */
> +	[SSD1351_ID] = {
> +		.default_width = 128,
> +		.default_height = 128,
> +		.format_rgb565 = 1,
> +		.family_id = SSD135X_FAMILY,
>  	}

While it's not a problem _in this case_, the rule of thumb is always to have a
trailing comma for non-terminator entry.

...

>  /*
>   * Helper to write data (SSD13XX_DATA) to the device.
>   */
> -static int ssd130x_write_data(struct ssd130x_device *ssd130x, u8 *values, int count)
> +static int ssd130x_write_data(struct ssd130x_device *ssd130x, const u8 *values, int count)
>  {
>  	return regmap_bulk_write(ssd130x->regmap, SSD13XX_DATA, values, count);
>  }

Stray change. If needed, either explain in the commit message or create
a separate patch (depending on the dependencies).

...

>  	unsigned int i;
>  	int ret;
>  
> +	/*
> +	 * The SSD135X family latches command parameters with D/C# HIGH (i.e.
> +	 * clocked in as data), unlike the other families where the opcode and
> +	 * all of its parameters are sent as commands (D/C# LOW). Send the
> +	 * opcode as a command and any following parameter bytes as data.
> +	 */
> +	if (ssd130x->device_info->family_id == SSD135X_FAMILY) {
> +		if (len == 0)
> +			return 0;
> +		ret = regmap_write(ssd130x->regmap, SSD13XX_COMMAND, cmd[0]);
> +		if (ret || len == 1)
> +			return ret;
> +
> +		return ssd130x_write_data(ssd130x, cmd + 1, len - 1);
> +	}

>  	for (i = 0; i < len; i++) {

This loop seems for the len, so it will be the same for both devices as far as
I can see the context. I can't find this piece in the original driver, perhaps
it's some dependency?

>  		ret = regmap_write(ssd130x->regmap, SSD13XX_COMMAND, cmd[i]);
>  		if (ret)

...

> +/*
> + * Variadic wrapper around ssd130x_write_cmds(). The first variadic argument is
> + * the command opcode and the following ones are its options/parameters.
> + */
> +static int ssd130x_write_cmd(struct ssd130x_device *ssd130x, int count,
> +			     /* u8 cmd, u8 option, ... */...)
> +{
> +	u8 buf[8];
> +	va_list ap;
> +	int i;
> +
> +	if (count > ARRAY_SIZE(buf))
> +		return -EINVAL;
> +
> +	va_start(ap, count);

> +	for (i = 0; i < count; i++)

Can be

	for (int i = 0; i < count; i++)

> +		buf[i] = va_arg(ap, int);
> +	va_end(ap);
> +
> +	return ssd130x_write_cmds(ssd130x, buf, count);
> +}

...

> +static int ssd135x_init(struct ssd130x_device *ssd130x)
> +{
> +	/*
> +	 * Horizontal address increment, COM split, reversed COM scan direction,
> +	 * BGR sub-pixel order and 65k (RGB565) color depth. Rotation is not
> +	 * supported, so the remap byte is fixed.
> +	 */
> +	u8 remap = SSD135X_SET_REMAP_65K | SSD135X_SET_REMAP_COM_SPLIT |
> +		   SSD135X_SET_REMAP_COLOR_BGR | SSD135X_SET_REMAP_COM_SCAN;

> +	const u8 cmds[] = {

Why not static?

> +		2, SSD135X_SET_COMMAND_LOCK, 0x12,
> +		2, SSD135X_SET_COMMAND_LOCK, 0xb1,
> +		1, SSD13XX_DISPLAY_OFF,
> +		2, SSD135X_SET_CLOCK_FREQ, 0xf1,
> +		2, SSD135X_SET_MUX_RATIO, ssd130x->height - 1,
> +		3, SSD135X_SET_COL_RANGE, 0x00, ssd130x->width - 1,
> +		3, SSD135X_SET_ROW_RANGE, 0x00, ssd130x->height - 1,
> +		2, SSD135X_SET_DISPLAY_START, 0x00,
> +		2, SSD135X_SET_DISPLAY_OFFSET, 0x00,
> +		2, SSD135X_SET_GPIO, 0x00,
> +		2, SSD135X_SET_FUNCTION, 0x01,
> +		2, SSD135X_SET_PHASE_LENGTH, 0x32,
> +		4, SSD135X_SET_VSL, 0xa0, 0xb5, 0x55,
> +		2, SSD135X_SET_PRECHARGE, 0x17,
> +		2, SSD135X_SET_VCOMH_VOLTAGE, 0x05,
> +		4, SSD135X_SET_CONTRAST, 0xc8, 0x80, 0xc8,
> +		2, SSD135X_SET_CONTRAST_MASTER, 0x0f,
> +		2, SSD135X_SET_PRECHARGE2, 0x01,
> +		1, SSD135X_SET_DISPLAY_NORMAL,
> +		2, SSD13XX_SET_SEG_REMAP, remap,

> +		0,

No trailing comma for the terminator entry.

> +	};
> +
> +	/*
> +	 * ssd130x_power_on() issues a short reset pulse, but the SSD1351 is not
> +	 * ready to accept commands immediately afterwards. Give the controller
> +	 * time to settle before sending the init sequence.
> +	 */

Any reference to the datasheet?

> +	msleep(120);
> +
> +	return ssd130x_run_cmd_seq(ssd130x, cmds);
> +}

...

> +/*
> + * Write a run of pixel data to the controller's display RAM. The SSD135X
> + * family requires an explicit Write RAM command once the address window has
> + * been set, before any pixel data is accepted; the SSD133X family enters data
> + * mode implicitly after the column/row range is programmed.
> + */
> +static int ssd133x_write_pixels(struct ssd130x_device *ssd130x,
> +				u8 *data_array, unsigned int count)
> +{
> +	if (ssd130x->device_info->family_id == SSD135X_FAMILY) {

> +		int ret = ssd130x_write_cmd(ssd130x, 1, SSD135X_WRITE_RAM);
> +
> +		if (ret < 0)
> +			return ret;

This style is discouraged as it's harder to maintain. Better to split
assignment and definition

		int ret;

		ret = ssd130x_write_cmd(ssd130x, 1, SSD135X_WRITE_RAM);
		if (ret < 0)
			return ret;

> +	}
> +
> +	return ssd130x_write_data(ssd130x, data_array, count);
> +}

...

> static const struct drm_plane_helper_funcs ssd130x_primary_plane_helper_funcs[]

>  		.atomic_check = ssd133x_primary_plane_atomic_check,
>  		.atomic_update = ssd133x_primary_plane_atomic_update,
>  		.atomic_disable = ssd133x_primary_plane_atomic_disable,
> +	},
> +	[SSD135X_FAMILY] = {
> +		DRM_GEM_SHADOW_PLANE_HELPER_FUNCS,
> +		.atomic_check = ssd133x_primary_plane_atomic_check,
> +		.atomic_update = ssd133x_primary_plane_atomic_update,
> +		.atomic_disable = ssd133x_primary_plane_atomic_disable,
>  	}

As per another similar case.

>  };

...

> static const struct drm_encoder_helper_funcs ssd130x_encoder_helper_funcs[] = {

>  	[SSD133X_FAMILY] = {
>  		.atomic_enable = ssd133x_encoder_atomic_enable,
>  		.atomic_disable = ssd130x_encoder_atomic_disable,
> +	},
> +	[SSD135X_FAMILY] = {
> +		.atomic_enable = ssd135x_encoder_atomic_enable,
> +		.atomic_disable = ssd130x_encoder_atomic_disable,
>  	}
>  };

Ditto.

...


> diff --git a/drivers/gpu/drm/solomon/ssd130x.h b/drivers/gpu/drm/solomon/ssd130x.h
> index b0b487c06e04..da89d4455270 100644
> --- a/drivers/gpu/drm/solomon/ssd130x.h
> +++ b/drivers/gpu/drm/solomon/ssd130x.h
> @@ -26,7 +26,8 @@
>  enum ssd130x_family_ids {
>  	SSD130X_FAMILY,
>  	SSD132X_FAMILY,
> -	SSD133X_FAMILY
> +	SSD133X_FAMILY,
> +	SSD135X_FAMILY

Ditto, and this is exactly the whole point why non-terminator entries should
have a trailing comma.

>  };

...

>  enum ssd130x_variants {

>  	SSD1327_ID,
>  	/* ssd133x family */
>  	SSD1331_ID,
> +	/* ssd135x family */
> +	SSD1351_ID,
>  	NR_SSD130X_VARIANTS

See the difference? Here is terminator, which is clear. The above cases are
not.

>  };

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply

* Re: [PATCH v4 1/3] iio: adc: Fix incorrect reading when datarate changed in single mode
From: Joshua Crofts @ 2026-06-23  9:29 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Jakub Szczudlo, linux-iio, andy, antoniu.miclaus, conor+dt,
	devicetree, dlechner, duje, jic23, jishnu.prakash, jorge.marques,
	krzk+dt, linusw, linux-kernel, marcelo.schmitt, mazziesaccount,
	mike.looijmans, nuno.sa, robh, sakari.ailus, wens
In-Reply-To: <ajpO9zaZbIl3x1uC@ashevche-desk.local>

On Tue, 23 Jun 2026 12:16:39 +0300
Andy Shevchenko <andriy.shevchenko@intel.com> wrote:

> > +	return read_poll_timeout(ads1100_new_data_not_ready, data_ready,
> > +				 !data_ready, wait_time,

I'd actually be all for using `data_ready != 0`, to make the condition more
readable.

...

> > +		PM_RUNTIME_ACQUIRE_AUTOSUSPEND(&data->client->dev, pm);  
> 
> > +  
> 
> This blank line is not needed as they are coupled, but I don't know if we have
> an agreed style in IIO for this.

I'd be surprised if there was an agreed style, as there aren't any IIO drivers
that use this specific macro (not in mainline at least). Additionally, might I
suggest using `PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND` as it is more generic?

-- 
Kind regards

CJD

^ permalink raw reply

* Re: [PATCH v4 3/3] iio: adc: Add ti-ads1110 support to ti-ads1100 driver
From: Andy Shevchenko @ 2026-06-23  9:23 UTC (permalink / raw)
  To: Jakub Szczudlo
  Cc: linux-iio, andy, antoniu.miclaus, conor+dt, devicetree, dlechner,
	duje, jic23, jishnu.prakash, jorge.marques, joshua.crofts1,
	krzk+dt, linusw, linux-kernel, marcelo.schmitt, mazziesaccount,
	mike.looijmans, nuno.sa, robh, sakari.ailus, wens
In-Reply-To: <20260622221550.374235-4-jakubszczudlo40@gmail.com>

On Tue, Jun 23, 2026 at 12:15:50AM +0200, Jakub Szczudlo wrote:
> Add ADS1110 support that have faster datarate than ADS1100, it also uses
> internal voltage reference of 2.048V for measurement.

...

>  config TI_ADS1100
> -	tristate "Texas Instruments ADS1100 and ADS1000 ADC"
> +	tristate "Texas Instruments ADS1100 and similar single channel I2C ADC"
>  	depends on I2C
>  	help
> -	  If you say yes here you get support for Texas Instruments ADS1100 and
> -	  ADS1000 ADC chips.
> +	  If you say yes here you get support TI ADS1100 and similar single
> +	  channel I2C Analog to Digital Converters.

User won't know what similar are really supported. The rule of thumb is to add
the list of supported here as

	  - ADS1000 (...perhaps some very short spec info...)
	  - ADS1100 (...perhaps some very short spec info...)


>  	  This driver can also be built as a module. If so, the module will be
>  	  called ti-ads1100.

...

> +static int ads1100_get_vref_milivolts(struct ads1100_data *data)
> +{
> +	if (data->ads_config->has_internal_vref_only)
> +		return ADS1110_INTERNAL_REF_mV;
> +
> +	return regulator_get_voltage(data->reg_vdd) / MILLI;

For now we used "(MICRO / MILLI)" instead of "MILLI", to show the unit
conversion.

> +}

...

>  	if (ret < 0) {
>  		dev_err(&data->client->dev, "I2C read fail: %d\n", ret);
>  		return ret;

> +	} else if (ret < 2) {

Redundant 'else'.

> +		dev_err(&data->client->dev, "Short I2C read\n");
> +		return -EIO;
>  	}

...

> -	microvolts = regulator_get_voltage(data->reg_vdd);
> +	microvolts = ads1100_get_vref_milivolts(data) * (MICRO / MILLI);

See above, here you correctly used the existing pattern, the above is
inconsistent and needs to be addressed.

...

> +	model = i2c_get_match_data(client);
> +	if (!model)
> +		return dev_err_probe(dev, -EINVAL,
> +				     "Can't get device data from firmware\n");
> +
> +	data->ads_config = (struct ads1100_config *)model;

You can't drop const like this. If you need to apply modification,
use devm_kmemdup(). Otherwise it won't work correctly if you have two different
sensors of the same driver in the system.

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply

* Re: [PATCH v1 2/2] ASoC: codecs: add Qualcomm WSA885X I2C codec driver
From: Prasad Kumpatla @ 2026-06-23  9:20 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: Srinivas Kandagatla, linux-arm-msm, linux-sound, devicetree,
	linux-kernel, linux-gpio, Srinivas Kandagatla, Liam Girdwood,
	Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jaroslav Kysela, Takashi Iwai, Linus Walleij
In-Reply-To: <CAMRc=Mf2oujn6MstGqKg1JCu3hbPD5zHhCB-Zke_hu8LYCz-Xg@mail.gmail.com>


On 6/11/2026 3:19 PM, Bartosz Golaszewski wrote:
> On Wed, 10 Jun 2026 17:57:08 +0200, Prasad Kumpatla
> <prasad.kumpatla@oss.qualcomm.com> said:
>> Add an ASoC codec driver for the Qualcomm WSA885X smart speaker
>> amplifier accessed over I2C.
>>
>> The driver provides the control-side support needed for playback
>> bring-up, including register programming, serial interface setup, clock
>> handling, mute and gain control, reset handling and interrupt support.
>>
>> Program the init table during codec initialization and reapply it only
>> after an explicit device reset so the static device configuration is
>> not rewritten on every playback start. Also program the TDM control
>> slot-count field from the runtime slot configuration so the same codec
>> path can be used with 2-slot, 4-slot, or 8-slot Audio IF backends.
>>
>> Keep the stream-time power-state sequencing in the DAI callbacks and
>> use normal regmap access for the control path.
>>
>> Signed-off-by: Prasad Kumpatla <prasad.kumpatla@oss.qualcomm.com>
>> ---
> ...
>
>> diff --git a/sound/soc/codecs/wsa885x-i2c.c b/sound/soc/codecs/wsa885x-i2c.c
>> new file mode 100644
>> index 000000000..a7d8f8d48
>> --- /dev/null
>> +++ b/sound/soc/codecs/wsa885x-i2c.c
>> @@ -0,0 +1,1643 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
>> + */
>> +
>> +/* WSA885X I2C codec driver */
>> +
>> +#include <linux/gpio/consumer.h>
>> +#include <linux/bitfield.h>
>> +#include <linux/i2c.h>
>> +#include <linux/module.h>
>> +#include <linux/regmap.h>
>> +#include <linux/property.h>
>> +#include <linux/regulator/consumer.h>
>> +#include <linux/slab.h>
>> +#include <sound/core.h>
>> +#include <sound/pcm.h>
>> +#include <sound/pcm_params.h>
>> +#include <sound/soc-dapm.h>
>> +#include <sound/soc.h>
>> +#include <sound/tlv.h>
>> +#include <linux/interrupt.h>
> Can you keep the headers in alphabetical order?

Hi Bart,

Thanks for review the patch and the feedback.

Ack, Will update

>
> ...
>
>> +
>> +#define WSA885X_FU21_VOL_STEPS 124
>> +#define WSA885X_USAGE_MODE_MAX 8
>> +#define WSA885X_INIT_TABLE_MAX_ITEMS 256
> Add newline.
Ack, Will update.
>
> ...
>
>> +
>> +static int wsa885x_apply_init_table(struct wsa885x_i2c_priv *wsa885x)
>> +{
>> +	int i;
>> +	int ret;
> I'd put it on the same line (elsewhere too) but that's personal preference.
Ack, I will make them to a single line.
>
>> +
>> +	if (!wsa885x || !wsa885x->regmap)
>> +		return -EINVAL;
>
> You have a lot of these checks but this can't really happen, can it?
Ack, I will cleanup and remove the all unnecessary checks and update in 
next version
>
>> +
>> +	if (!wsa885x->init_table_size)
>> +		return 0;
>> +
>> +	if (!wsa885x->init_table)
>> +		return -EINVAL;
>> +
>> +	for (i = 0; i < wsa885x->init_table_size / 2; i++) {
>> +		u32 reg = wsa885x->init_table[2 * i];
>> +		u32 val = wsa885x->init_table[2 * i + 1];
>> +
>> +		if (wsa885x->batt_conf == WSA885X_BATT_2S && reg == WSA885X_SPK_TOP_LF_CH1_CTRL11)
>> +			continue;
>> +
>> +		if (wsa885x->batt_conf == WSA885X_BATT_2S && reg == WSA885X_SPK_TOP_LF_CH2_CTRL11)
>> +			continue;
>> +
>> +		ret = regmap_write(wsa885x->regmap, reg, val);
>> +		if (ret)
>> +			return ret;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int wsa885x_hw_init(struct wsa885x_i2c_priv *wsa885x)
>> +{
>> +	static const struct reg_sequence regs[] = {
>> +		{ WSA885X_DIG_CTRL1_SPMI_PAD_GPIO2_CTL, 0x2e },
>> +		{ WSA885X_DIG_CTRL1_INTR_MODE, 0x01 },
>> +		{ WSA885X_DIG_CTRL1_PIN_CT, 0x04 },
>> +	};
>> +	int ret;
>> +
>> +	if (!wsa885x || !wsa885x->regmap)
>> +		return -EINVAL;
>> +
>> +	ret = wsa885x_apply_init_table(wsa885x);
>> +	if (ret)
>> +		return ret;
>> +
>> +	if (wsa885x->batt_conf == WSA885X_BATT_2S) {
>> +		ret = wsa885x_2s_conf(wsa885x);
>> +		if (ret)
>> +			return ret;
>> +	}
>> +
>> +	return regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs));
>> +}
>> +
>> +static int wsa885x_unmask_interrupts(struct wsa885x_i2c_priv *wsa885x)
>> +{
>> +	static const struct reg_sequence regs[] = {
>> +		{ WSA885X_INTR_MASK0, 0x00 },
>> +		{ WSA885X_INTR_MASK0 + 1, 0x00 },
>> +		{ WSA885X_INTR_MASK0 + 2, 0xf8 },
>> +	};
>> +
>> +	if (!wsa885x || !wsa885x->regmap)
>> +		return -EINVAL;
>> +
>> +	return regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs));
>> +}
>> +
>> +static int wsa885x_wait_for_pde_state(struct wsa885x_i2c_priv *wsa885x, int ps)
>> +{
>> +	int act_ps = -1, cnt = 0, clock_valid = -1;
>> +	int rc = 0;
>> +
>> +	if (!wsa885x || !wsa885x->regmap)
>> +		return -EINVAL;
>> +
>> +	if (ps < 0 || ps > 3)
>> +		return -EINVAL;
>> +
>> +	do {
>> +		usleep_range(1000, 1500);
>> +		rc = regmap_read(wsa885x->regmap,
>> +				 WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS,
>> +				 &act_ps);
>> +		if (rc) {
>> +			dev_err(wsa885x->dev, "PDE state read failed: %d\n", rc);
>> +			return rc;
>> +		}
>> +		if (act_ps == ps)
>> +			return 0;
>> +	} while (++cnt < 5);
> Newline.
Ack.
>
>> +	if (regmap_read(wsa885x->regmap,
>> +			WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID,
>> +			&clock_valid))
>> +		dev_err(wsa885x->dev,
>> +			"PDE power state %d request failed, actual_ps %d, clock_valid read failed\n",
>> +			ps, act_ps);
>> +	else
>> +		dev_err(wsa885x->dev,
>> +			"PDE power state %d request failed, actual_ps %d, clock_valid:%d\n",
>> +			ps, act_ps, clock_valid);
>> +
>> +	return -ETIMEDOUT;
>> +}
>> +
>> +static int wsa885x_codec_hw_params(struct snd_pcm_substream *substream,
>> +				   struct snd_pcm_hw_params *params,
>> +				   struct snd_soc_dai *dai)
>> +{
>> +	struct wsa885x_i2c_priv *wsa885x;
>> +	u8 pcm_rate, cs21_sample_rate_idx, cs24_sample_rate_idx;
>> +
>> +	(void)substream;
> Do we warn about unused arguments in the kernel now?

Right, this is unnecessary. I'll drop the unused parameter cast.

Ack.

>
> ...
>
>> +
>> +static int wsa885x_stereo_gain_offset_get(struct snd_kcontrol *kcontrol,
>> +					  struct snd_ctl_elem_value *ucontrol)
>> +{
>> +	struct snd_soc_component *component;
>> +	struct wsa885x_i2c_priv *wsa885x;
>> +	int val;
>> +
>> +	if (!kcontrol || !ucontrol)
>> +		return -EINVAL;
>> +
>> +	component = snd_kcontrol_chip(kcontrol);
>> +	if (!component)
>> +		return -EINVAL;
>> +
>> +	wsa885x = snd_soc_component_get_drvdata(component);
>> +	if (!wsa885x)
>> +		return -EINVAL;
>> +
>> +	val = wsa885x->stereo_vol_db + 84;
>> +	if (val < 0 || val > WSA885X_FU21_VOL_STEPS)
>> +		return -ERANGE;
>> +
>> +	ucontrol->value.integer.value[0] = val;
>> +	return 0;
>> +}
>> +
>> +static int wsa885x_stereo_gain_offset_put(struct snd_kcontrol *kcontrol,
>> +					  struct snd_ctl_elem_value *ucontrol)
>> +{
>> +	struct snd_soc_component *component;
>> +	struct wsa885x_i2c_priv *wsa885x;
>> +	long val;
>> +
>> +	if (!kcontrol || !ucontrol)
>> +		return -EINVAL;
>> +
>> +	component = snd_kcontrol_chip(kcontrol);
>> +	if (!component)
>> +		return -EINVAL;
>> +
>> +	wsa885x = snd_soc_component_get_drvdata(component);
>> +	if (!wsa885x)
>> +		return -EINVAL;
>> +
>> +	val = ucontrol->value.integer.value[0];
>> +
>> +	if (val < 0 || val > WSA885X_FU21_VOL_STEPS) {
>> +		dev_err(component->dev, "%s: Invalid range, Val: %ld\n", __func__, val);
>> +		return -EINVAL;
>> +	}
>> +	wsa885x->stereo_vol_db = (int)val - 84;
>> +	return 0;
>> +}
>> +
>> +static int wsa885x_i2c_usage_modes_get(struct snd_kcontrol *kcontrol,
>> +				       struct snd_ctl_elem_value *ucontrol)
>> +{
>> +	struct snd_soc_component *component;
>> +	struct wsa885x_i2c_priv *wsa885x_i2c;
>> +
>> +	if (!kcontrol || !ucontrol)
>> +		return -EINVAL;
>> +
>> +	component = snd_kcontrol_chip(kcontrol);
>> +	if (!component)
>> +		return -EINVAL;
>> +
>> +	wsa885x_i2c = snd_soc_component_get_drvdata(component);
>> +	if (!wsa885x_i2c)
>> +		return -EINVAL;
>> +
>> +	if (wsa885x_i2c->usage_mode > WSA885X_USAGE_MODE_MAX)
>> +		return -ERANGE;
>> +
>> +	ucontrol->value.integer.value[0] = wsa885x_i2c->usage_mode;
>> +
>> +	return 0;
>> +}
>> +
>> +static int wsa885x_i2c_usage_modes_put(struct snd_kcontrol *kcontrol,
>> +				       struct snd_ctl_elem_value *ucontrol)
>> +{
>> +	struct snd_soc_component *component;
>> +	struct wsa885x_i2c_priv *wsa885x_i2c;
>> +	long val;
>> +
>> +	if (!kcontrol || !ucontrol)
>> +		return -EINVAL;
>> +
>> +	component = snd_kcontrol_chip(kcontrol);
>> +	if (!component)
>> +		return -EINVAL;
>> +
>> +	wsa885x_i2c = snd_soc_component_get_drvdata(component);
>> +	if (!wsa885x_i2c)
>> +		return -EINVAL;
>> +
> You seem to be repeating the same sequence in multiple functions just to get
> the address of wsa885x_i2c. Can you factor it out into a separate helper and
> save some lines?
Ack.
>
>> +	val = ucontrol->value.integer.value[0];
>> +
>> +	if (val < 0 || val > WSA885X_USAGE_MODE_MAX)
>> +		return -EINVAL;
>> +
>> +	wsa885x_i2c->usage_mode = val;
>> +
>> +	return 0;
>> +}
>> +
>> +static int wsa885x_i2c_rx_slot_mask_get(struct snd_kcontrol *kcontrol,
>> +					struct snd_ctl_elem_value *ucontrol)
>> +{
>> +	struct snd_soc_component *component;
>> +	struct wsa885x_i2c_priv *wsa885x_i2c;
>> +	u32 mask;
>> +
>> +	if (!kcontrol || !ucontrol)
>> +		return -EINVAL;
>> +
>> +	component = snd_kcontrol_chip(kcontrol);
>> +	if (!component)
>> +		return -EINVAL;
>> +
>> +	wsa885x_i2c = snd_soc_component_get_drvdata(component);
>> +	if (!wsa885x_i2c)
>> +		return -EINVAL;
>> +
>> +	mask = wsa885x_i2c->rx_slot_mask;
>> +	if (!wsa885x_is_valid_rx_slot_mask(mask))
>> +		return -ERANGE;
>> +
>> +	ucontrol->value.integer.value[0] = mask;
>> +
>> +	return 0;
>> +}
>> +
>> +static int wsa885x_i2c_rx_slot_mask_put(struct snd_kcontrol *kcontrol,
>> +					struct snd_ctl_elem_value *ucontrol)
>> +{
>> +	struct snd_soc_component *component;
>> +	struct wsa885x_i2c_priv *wsa885x_i2c;
>> +	long mask;
>> +
>> +	if (!kcontrol || !ucontrol)
>> +		return -EINVAL;
>> +
>> +	component = snd_kcontrol_chip(kcontrol);
>> +	if (!component)
>> +		return -EINVAL;
>> +
>> +	wsa885x_i2c = snd_soc_component_get_drvdata(component);
>> +	if (!wsa885x_i2c)
>> +		return -EINVAL;
>> +
>> +	mask = ucontrol->value.integer.value[0];
>> +
>> +	if (!wsa885x_is_valid_rx_slot_mask(mask))
>> +		return -EINVAL;
>> +
>> +	wsa885x_i2c->rx_slot_mask = mask;
>> +
>> +	return 0;
>> +}
>> +
> ...
>
>> +				/* INTR_CLEAR registers are write-only; use regmap_write
>> +				 * instead of regmap_update_bits to avoid the read-modify-write
>> +				 * that regmap_update_bits performs on non-readable registers.
>> +				 */
> /*
>   */
>
> style comments please
Ack. will update
>
> ...
>
>> +	ret = devm_add_action_or_reset(dev, wsa885x_gpio_powerdown, wsa885x);
>> +	if (ret)
>> +		return dev_err_probe(dev, ret, "devm_add_action_or_reset failed\n");
>> +
>> +	i2c_set_clientdata(client, wsa885x);
> I don't see a corresponding i2c_get_clientdata(). Do you really need it?

It is currently not being used, so storing the client data is unnecessary.

I'll either remove it or add it together with the code that requires 
i2c_get_clientdata() in a future versions.

Thanks,

Prasad

>
> ...
>
> Bart

^ permalink raw reply

* Re: [PATCH 3/6] drm/tiny: Add DRM driver for Solomon SSD16xx e-paper display controllers
From: Thomas Zimmermann @ 2026-06-23  9:17 UTC (permalink / raw)
  To: Devarsh Thakkar, 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: <e14de1da-5d1e-4086-a713-246ebc15603e@ti.com>

Hi,

sorry, I've lost track of what the most recent status is here.

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.

>
> 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?

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");
>>>>
>>>
>>
>

-- 
--
Thomas Zimmermann
Graphics Driver Developer
SUSE Software Solutions Germany GmbH
Frankenstr. 146, 90461 Nürnberg, Germany, www.suse.com
GF: Jochen Jaser, Andrew McDonald, Werner Knoblich, (HRB 36809, AG Nürnberg)



^ permalink raw reply

* Re: [PATCH v4 1/3] iio: adc: Fix incorrect reading when datarate changed in single mode
From: Andy Shevchenko @ 2026-06-23  9:16 UTC (permalink / raw)
  To: Jakub Szczudlo
  Cc: linux-iio, andy, antoniu.miclaus, conor+dt, devicetree, dlechner,
	duje, jic23, jishnu.prakash, jorge.marques, joshua.crofts1,
	krzk+dt, linusw, linux-kernel, marcelo.schmitt, mazziesaccount,
	mike.looijmans, nuno.sa, robh, sakari.ailus, wens
In-Reply-To: <20260622221550.374235-2-jakubszczudlo40@gmail.com>

On Tue, Jun 23, 2026 at 12:15:48AM +0200, Jakub Szczudlo wrote:
> When device is suspended and it is in single mode then changing
> datarate doesn't make it actual wait for new measurement, so to
> be sure that read after change is correct functions that changes
> datarate and gain will wait for new data.

...

> +/* Timeout based on the minimum sample rate of 8 SPS (7.5s) */
> +#define ADS1100_MAX_DRDY_TIMEOUT_US	7500000

Not sure if the multiplier will look good here

#define ADS1100_MAX_DRDY_TIMEOUT_US	(7500 * USEC_PER_MSEC)

(comment might need an update to use 7500 ms, but see above).

...

> +static bool ads1100_new_data_not_ready(struct ads1100_data *data)
> +{
> +	int ret;
> +	u8 buffer[3];
> +
> +	ret = i2c_master_recv(data->client, (char *)&buffer, sizeof(buffer));
> +	if (ret < 0) {
> +		dev_err(&data->client->dev, "I2C read fail: %d\n", ret);
> +		return true;
> +	} else if (ret < 3) {

sizeof()

> +		dev_err(&data->client->dev, "Short I2C read\n");
> +		return true;
> +	}
> +
> +	return FIELD_GET(ADS1100_CFG_ST_BSY, buffer[2]);
> +}

...

> +static int ads1100_poll_data_ready(struct ads1100_data *data)
> +{
> +	int ret;
> +	u8 buffer[3];
> +	bool data_ready;
> +	int datarate = ads1100_data_rate[FIELD_GET(ADS1100_DR_MASK, data->config)];
> +	/* To be sure we wait 5 times more than datarate */
> +	unsigned long wait_time = DIV_ROUND_CLOSEST(MICRO, 5 * datarate);

First of all, reversed xmas tree order can be better (especially
when something is assigned). Second, use units in the variable name,
wait_time_us. And at last, use USEC_PER_SEC instead of MICRO
(this will need time.h to be included if not yet).

> +	/* To be sure that polled value will have value after config change */
> +	ret = i2c_master_recv(data->client, (char *)&buffer, sizeof(buffer));
> +	if (ret < 0) {
> +		dev_err(&data->client->dev, "I2C read fail: %d\n", ret);
> +		return ret;
> +	}

> +	return read_poll_timeout(ads1100_new_data_not_ready, data_ready,
> +				 !data_ready, wait_time,
> +				 ADS1100_MAX_DRDY_TIMEOUT_US, false, data);

Why not readx_poll_timeout()? It's a short cut for the one-argument "read"
function.

> +}

...

>  	ads1100_set_config_bits(data, ADS1100_PGA_MASK, ffs(gain) - 1);
>  
> -	return 0;
> +	ret = ads1100_poll_data_ready(data);
> +
> +	return ret;

Is it specifically done due to next patch? But this one is marked as Fix and
will go deep back in the releases. For them this will look unjustified. Just
use

	return ads1100_...;

here.


>  static int ads1100_set_data_rate(struct ads1100_data *data, int chan, int rate)
>  {
>  	unsigned int i;
>  	unsigned int size;
> +	int ret;
>  
>  	size = data->supports_data_rate ? ARRAY_SIZE(ads1100_data_rate) : 1;
>  	for (i = 0; i < size; i++) {
> -		if (ads1100_data_rate[i] == rate)
> -			return ads1100_set_config_bits(data, ADS1100_DR_MASK,
> -						       FIELD_PREP(ADS1100_DR_MASK, i));

> +		if (ads1100_data_rate[i] != rate)
> +			continue;

This will look better if you break here and add a check

	if (i == size)
		return -EINVAL;

	... then your new code...

	return ads1100_...;


> +		PM_RUNTIME_ACQUIRE_AUTOSUSPEND(&data->client->dev, pm);

> +

This blank line is not needed as they are coupled, but I don't know if we have
an agreed style in IIO for this.

> +		ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
> +		if (ret)
> +			return ret;
> +
> +		ret = ads1100_set_config_bits(data, ADS1100_DR_MASK,
> +					      FIELD_PREP(ADS1100_DR_MASK, i));
> +		if (ret)
> +			return ret;

> +		ret = ads1100_poll_data_ready(data);
> +
> +		return ret;

As per above.

>  	}
>  
>  	return -EINVAL;

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply

* Re: [PATCH v1 2/2] ASoC: codecs: add Qualcomm WSA885X I2C codec driver
From: Prasad Kumpatla @ 2026-06-23  9:13 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Srinivas Kandagatla, Liam Girdwood, Mark Brown, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Jaroslav Kysela, Takashi Iwai,
	Linus Walleij, Bartosz Golaszewski, Srinivas Kandagatla,
	linux-arm-msm, linux-sound, devicetree, linux-kernel, linux-gpio
In-Reply-To: <20260611-straight-refined-beetle-e2c934@quoll>


On 6/11/2026 3:09 PM, Krzysztof Kozlowski wrote:
> On Wed, Jun 10, 2026 at 09:27:08PM +0530, Prasad Kumpatla wrote:
>> +};
>> +
>> +static void wsa885x_gpio_set(struct wsa885x_i2c_priv *wsa885x, bool val)
>> +{
>> +	if (!wsa885x || !wsa885x->sd_n)
> How wsa885x can be NULL?
>
> This wrapper is pointless. Avoid creating abstraction layers over single
> call to standard kernel interfaces.

Hi Krzysztof,

Thanks for reviewing and comments on patch.

Agree. The NULL check is unnecessary, and the helper does not add any 
meaningful abstraction.

I'll remove the wrapper and use the GPIO API directly in the next revision.

>
>> +		return;
>> +
>> +	gpiod_set_value_cansleep(wsa885x->sd_n, val);
>> +}
>> +
> ...
>
>> +
>> +static void wsa885x_gpio_powerdown(void *data)
>> +{
>> +	struct wsa885x_i2c_priv *wsa885x = data;
>> +
>> +	if (!wsa885x)
>> +		return;
> How is this possible?

No, I will remove all the unnecessary checks in the next version of patch.


>
>> +
>> +	wsa885x_gpio_set(wsa885x, true);
>> +}
>> +
> ...
>
>> +	if (count > 0) {
>> +		if (count % 2) {
>> +			dev_err(dev, "%s: Invalid number of elements in %s (%d)\n",
>> +				__func__, init_table_prop, count);
>> +			return -EINVAL;
>> +		}
>> +		if (count > WSA885X_INIT_TABLE_MAX_ITEMS) {
>> +			dev_err(dev, "%s: %s has too many elements (%d > %u)\n",
>> +				__func__, init_table_prop, count,
>> +				WSA885X_INIT_TABLE_MAX_ITEMS);
>> +			return -EINVAL;
>> +		}
>> +		wsa885x->init_table_size = count;
>> +
>> +		wsa885x->init_table = devm_kcalloc(dev, wsa885x->init_table_size,
>> +						   sizeof(*wsa885x->init_table), GFP_KERNEL);
>> +		if (!wsa885x->init_table)
>> +			return -ENOMEM;
>> +
>> +		if (device_property_read_u32_array(dev, init_table_prop,
>> +						   wsa885x->init_table,
>> +						   wsa885x->init_table_size)) {
>> +			dev_err(dev, "%s: Failed to read %s\n",
>> +				__func__, init_table_prop);
>> +			return -EINVAL;
>> +		}
>> +	}
>> +
>> +	ret = device_property_read_u32(dev, "qcom,battery-config",
>> +				       &wsa885x->batt_conf);
>> +	if (ret) {
>> +		wsa885x->batt_conf = WSA885X_BATT_1S;
>> +	} else if (wsa885x->batt_conf != WSA885X_BATT_1S &&
>> +		   wsa885x->batt_conf != WSA885X_BATT_2S) {
>> +		return dev_err_probe(dev, -EINVAL,
>> +				     "Invalid battery config %u (expected 1S or 2S)\n",
>> +				     wsa885x->batt_conf);
>> +	}
>> +
>> +	for (i = 0; i < WSA885X_SUPPLIES_NUM; i++)
>> +		wsa885x->supplies[i].supply = wsa885x_supply_name[i];
>> +
>> +	ret = devm_regulator_bulk_get(dev, WSA885X_SUPPLIES_NUM, wsa885x->supplies);
>> +	if (ret)
>> +		return dev_err_probe(dev, ret, "Failed to get regulators\n");
>> +
>> +	ret = regulator_bulk_enable(WSA885X_SUPPLIES_NUM, wsa885x->supplies);
>> +	if (ret)
>> +		return dev_err_probe(dev, ret, "Failed to enable regulators\n");
>> +
>> +	ret = devm_add_action_or_reset(dev, wsa885x_regulator_disable, wsa885x);
> Why you cannot simply use devm_regulator_get_enable?
Ack, will use.
>
>> +	if (ret)
>> +		return dev_err_probe(dev, ret, "devm_add_action_or_reset failed\n");
>> +
>> +	wsa885x->sd_n = devm_gpiod_get(dev, "powerdown", GPIOD_OUT_HIGH);
>> +	if (IS_ERR(wsa885x->sd_n))
>> +		return dev_err_probe(dev, PTR_ERR(wsa885x->sd_n),
>> +							 "Shutdown Control GPIO not found\n");
> Messed/misaligned indentation.
Ack, Will update
>
>> +
>> +	wsa885x_gpio_set(wsa885x, false);
>> +
>> +	ret = devm_add_action_or_reset(dev, wsa885x_gpio_powerdown, wsa885x);
>> +	if (ret)
>> +		return dev_err_probe(dev, ret, "devm_add_action_or_reset failed\n");
>> +
>> +	i2c_set_clientdata(client, wsa885x);
>> +
>> +	wsa885x->intr_pin = devm_gpiod_get(dev, "interrupt", GPIOD_IN);
>> +	if (IS_ERR(wsa885x->intr_pin))
>> +		return dev_err_probe(dev, PTR_ERR(wsa885x->intr_pin),
>> +							 "Interrupt GPIO not found\n");
>> +
>> +	ret = wsa885x_register_irq(wsa885x);
>> +	if (ret)
>> +		return dev_err_probe(dev, ret, "wsa885x irq registration failed\n");
>> +
>> +	ret = devm_snd_soc_register_component(dev, component_driver,
>> +					      wsa885x_i2c_dai,
>> +					      ARRAY_SIZE(wsa885x_i2c_dai));
>> +	if (ret)
>> +		return dev_err_probe(dev, ret, "Codec component registration failed\n");
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct of_device_id wsa885x_i2c_dt_match[] = {
>> +	{
>> +		.compatible = "qcom,wsa885x-i2c",
>> +	},
>> +	{}
>> +};
>> +
>> +static const struct i2c_device_id wsa885x_id_i2c[] = {
>> +	{"wsa885x_i2c", 0},
> Used named initializers.
Ack, Will update
>
>> +	{}
>> +};
>> +
>> +MODULE_DEVICE_TABLE(i2c, wsa885x_id_i2c);
>> +MODULE_DEVICE_TABLE(of, wsa885x_i2c_dt_match);
> Don't come with own coding style. Each above goes IMMEDIATELY after the table.

Agreed. I'll place each MODULE_DEVICE_TABLE() immediately after its 
associated table to match the existing kernel style.

Thanks,

Prasad


>
> Best regards,
> Krzysztof
>

^ permalink raw reply

* Re: [PATCH net] net: ethernet: qualcomm: ppe: Demote from supported and fix maintainer addresses
From: Jie Luo @ 2026-06-23  9:08 UTC (permalink / raw)
  To: Andrew Lunn, Krzysztof Kozlowski
  Cc: Bjorn Andersson, Michael Turquette, Stephen Boyd, Brian Masney,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Andrew Lunn,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Lei Wei, Suruchi Agarwal, Pavithra R, linux-kernel, linux-arm-msm,
	linux-clk, devicetree, netdev
In-Reply-To: <0247dfba-1c14-4fea-aab3-5489a36f35f6@lunn.ch>



On 6/23/2026 4:10 PM, Andrew Lunn wrote:
> Emails to the maintainer of Qualcomm PPE Ethernet driver (Luo Jie
> <quic_luoj@quicinc.com>) bounce permanently (full mailbox), because the
> "quicinc.com" addresses were deprecated for public work.  All Qualcomm
> contributors are aware of that and were asked to fix their addresses.
> 
> Driver is not supported - in terms of how netdev understands supported
> commitment - if maintainer does not care to receive the patches for its
> code, so demote it to "maintained" to reflect true status.
> 
> Fix all occurences of Luo Jie email address to preferred and working
> domain.

Thanks a lot for fixing my email address and for the help!
Acked-by: Luo Jie <jie.luo@oss.qualcomm.com>


^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox